562

I know how to write a multi-line command in a Bash script, but how can I add a comment for each line in a multiline command?

CommandName InputFiles      \ # This is the comment for the 1st line
            --option1 arg1  \ # This is the comment for the 2nd line
            --option2 arg2    # This is the comment for the 3nd line

But unfortunately, the comment after continuation character \ will break the command.

4
  • Copy the code block to a commented/annotated block adjacent to it, if you update the code just remember to update the comment block. Commented Nov 12, 2015 at 15:52
  • note: this problem and its solutions also apply to multiline strings. Commented Sep 1, 2017 at 22:15
  • 5
    FYI this is not a duplicate of this question the linked question is asking about a command that uses pipes... vs this question is talking about one command that has many options. not the same thing. Commented Mar 1, 2018 at 20:28
  • Replace the space before the # comment symbol with a newline? You can alternate continuation lines with comment lines in a shell script file with no special tricks, it seems. Commented May 2, 2021 at 0:19

4 Answers 4

948

This is how I do it. Essentially by using Bash's backtick command substitution one can place these comments anywhere along a long command line even if it is split across lines. I have put the echo command in front of your example so that you can execute the example and see how it works:

echo CommandName InputFiles `#1st comment` \
             --option1 arg1 `#2nd comment` \
             --option2 arg2 `#3rd comment`

Another example where you can put multiple comments at different points on one line:

some_cmd --opt1 `#1st comment` --opt2 `#2nd comment` --opt3 `#3rd comment`
Sign up to request clarification or add additional context in comments.

It even works within piped sub-commands: "echo `#1` foo \(newline) | perl -ne `#2` 'print'"... exactly what I needed!
This technique creates a subshell for each inline comment, so these are very expensive comments. They are only suitable on lines that are executed infrequently.
These comments are very expensive because each of them creates a subshell. That makes the technique unusable in some circumstances. A much cheaper, if less readable, alternative that uses the same basic idea is: echo CommandName InputFiles ${IFS# 1st comment} --option1 arg1 ${IFS# 2nd comment} --option2 arg2 ${IFS# 3rd comment}.
Interesting how it only works with backticks but not when using command substitution using $(). Is there any reason why?
@phk "Interesting how it only works with backticks but not when using command substitution using $(). Is there any reason why?" Once $() sees the #, the rest of the line is treated as comments, and the parentheses never gets closed. Backticks parse differently. You can do $(: comment) instead. stackoverflow.com/questions/1455988/commenting-in-a-bash-script/…
186

You could store the arguments in an array:

args=(
    InputFiles           # This is the comment for the 1st line
    --option1 arg1       # This is the comment for the 2nd line
    --option2 arg2       # This is the comment for the 3nd line
    #--deprecated-option # Option disabled
)
CommandName "${args[@]}"

and you can even add blank lines to enhance readability:

args=(

    # This is the comment for the 1st line
    InputFiles

    # This is the comment for the 2nd line
    --option1 arg1

    # This is the comment for the 3nd line
    --option2 arg2

    # Option disabled
    #--deprecated-option

)
CommandName "${args[@]}"

If you need to use other shell operators, such as booleans and pipes, you cannot place them inside the array(s) but you can "glue" them together when you execute:

args1=(
    InputFiles           # This is the comment for the 1st line
    --option1 arg1       # This is the comment for the 2nd line
    --option2 arg2       # This is the comment for the 3nd line
    #--deprecated-option # Option disabled
)
args2=(
    --option arg         # Another comment
    example.config
)
success=(
    # Put the command in the array if it is clearer
    notify-send
    Hooray
    'The thing is done'
)
fail=(
    notify-send
    'Uh oh'
    "The thing didn't work"
)
CommandName1 "${args1[@]}" | CommandName2 "${args2[@]}" 2>> error.log && "${success[@]}" || "${fail[@]}"

@Philipp Hmmm, thanks. It's a good workaround. but I'm afraid it will be a little bit confusing if my --option arg has both ' and ".
@PeterLee: you can use " and ' in arrays as well.
Less hackish is to just store the arguments in the array, then use them like so: CommandName "${args[@]}".
This format is superior to all the others if you wish to be able to comment out entire lines in an argument list. command "${args[@]}" ftw.
this is the best answer for me because it allows me to comment out in bulk and CLI options that don't take a value like --skip-dry-run, because if i was using it like --skip-dry-run ` and i commented it out with #--skip-dry-run ` then it breaks the command, and other solutions aren't easy to bulk replace
115

I'm afraid that, in general, you can't do what you're asking for. The best you can do is a comment on the lines before the command, or one single comment at the end of the command line, or a comment after the command.

You can't manage to intersperse comments inside a command this way. The \s express an intent to merge lines, so for all intents and purposes you're trying to intersperse comments in a single line, which doesn't work anyway because a \ has to be at the end of the line to have that effect.

Not to mention that "The \ s effectively merge those lines" isn't even right, the problem is that the backslash must immediately precede the newline in order to escape it, whereas with cmd \ # comment there's whitespace and a comment in between the backslash and the newline.
I think I disagree, Marwan's answer is clever but feels like an abuse of substitution. If anything I'd say Philipp's answer is closer to something I'd want to do.
There is another way to do it, which doesn't involve subshells via hacking $IFS: see here.
The fact that this one is marked as the accepted answer while Marwan's answer is recommended against on grounds which are irrelevant for 99% (performance) / 100% ("feels wrong") of users coming here looking for solutions is both sad and representative of what's wrong with software development.
Just because a kludge is popular doesn't make it a good idea at all.
22

Based on pjh's comment to another answer to this question, replacing IFS with a variable known to contain no non-whitespace characters.

comment=
who ${comment# This is the command} \
    -u ${comment# This is the argument}

Why aren't the parameter expansions quoted? The variable is initialized with an empty string. When the parameter expansion occurs, the # operator (unrelated to the shell comment character #, but used for the similarity) attempts to strip the actual comment from the parameter value. The result, of course, is still an empty string.

An unquoted parameter expansion undergoes word-splitting and pathname generation. In this case, neither process creates any additional words from an empty string, so the result is still an empty string. Such an empty string is simply discarded without affecting the command in which it appears. The above is precisely equivalent to

who \
  -u

This too is problematic because it relies on not using double-quotes around the expansion, so that it disappears with word-splitting. That goes in counter to how it's far more often necessary to teach people to add the double-quotes, so that word-splitting doesn't mess them up the moment that a whitespace character appears in the data.
I think it's important to understand why quoting is almost always important, as well as understanding why a lack of quoting could be intentional.
(That said, this is a gross hack that I would never actually use it in my own shell scripts.)
If you want to be 100% sure that comment is an empty variable, declare it as readonly comment=. This will even prevent functions to redefine a local comment.
For ${parameter#word} POSIX specifies "word shall be subjected to tilde expansion, parameter expansion, command substitution, and arithmetic expansion" and somewhat vaguely "If word is not needed, it shall not be expanded". If $parameter is empty, bash does not expand word but still parses it. Therefore, the comments need correct shell syntax, even though they are not executed. Example: echo ${comment# 12" (inch) are 1' (foot)} results in unexpected EOF while looking for matching `"'.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.