486

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.

dreftymac
  • 31,404
  • 26
  • 119
  • 182
Peter Lee
  • 12,931
  • 11
  • 73
  • 100
  • Copy the code block to a commented/annotated block adjacent to it, if you update the code just remember to update the comment block. –  Nov 12 '15 at 15:52
  • note: this problem and its solutions also apply to multiline strings. – phil294 Sep 01 '17 at 22:15
  • 3
    FYI this is not a duplicate of [this question](https://stackoverflow.com/questions/1455988/commenting-in-a-bash-script) 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. – Trevor Boyd Smith Mar 01 '18 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. – Jim Grisham May 02 '21 at 00:19

4 Answers4

879

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`
Kos
  • 70,399
  • 25
  • 169
  • 233
Marwan Alsabbagh
  • 25,364
  • 9
  • 55
  • 65
  • 10
    It even works within piped sub-commands: "echo \`#1\` foo \\(newline) | perl -ne \`#2\` 'print'"... exactly what I needed! – EdwardTeach Jan 29 '13 at 23:49
  • 3
    For me, it's useful to quickly solve the problem when I want to block the action of certain options in a long multiline command in a script file. – Apostle Feb 12 '14 at 09:54
  • 5
    Just don't use any comments that contain command terminators like `;` or `&`. – chepner Dec 10 '14 at 14:18
  • Is this trick bash specific or does it also work with other shells like dash and csh? – hugomg Dec 10 '14 at 14:25
  • 62
    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. – pjh Dec 10 '14 at 16:52
  • 72
    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}`. – pjh Dec 15 '14 at 11:10
  • 1
    Just a quick note that there is nothing special about the lack of space after the # in these examples. Adding a space is fine if you like to format comments that way (as in the OP) – andybuckley Apr 30 '15 at 11:53
  • 15
    Interesting how it only works with backticks but not when using command substitution using `$()`. Is there any reason why? – phk Sep 02 '16 at 15:24
  • 1
    @pjh: It's worth pointing out that in your solutions quote marks need to be escaped but on the upside the inline comments themselves can be multi-line. – phk Sep 02 '16 at 15:32
  • 8
    @pjh, that doesn't apply to all shells. `dash` optimises out the fork and pipe for command substitutions without commands, and `ksh93` doesn't fork for subshells (and only creates the pipe when an external command needs to be executed) – Stephane Chazelas Dec 02 '16 at 10:08
  • 1
    @WaelJ emphasis on "**abuse**" in "ingenious **abuse** of substitution" – Trevor Boyd Smith Jun 01 '17 at 20:31
  • Even though each creates a subshell, each one of those subshells exits once the comment has been processed. So the only long-term effect would be extending the ~/.bash_history file by one line per comment. This method is great. And it is hardly what I would call abuse. .bash_history isn't necessary in your rsync backups anyway. Bottom line: --> If your primary drive is so sensitive to such small additions then you have already built abuse into your system, whether you use this method or not. It is always teetering on the edge of failing if that is true. – SDsolar Jul 31 '17 at 01:50
  • Alternative way for proper `vim` highlighting: `: 'COMMENT'` instead of `# COMMENT`. – Константин Ван Oct 05 '17 at 07:39
  • 3
    @pjh For potential issues with `${IFS#Comments}` see [here](https://stackoverflow.com/a/47374030/5353461). – Tom Hale Nov 19 '17 at 05:36
  • sorry I accidentally clicked the 'Down Vote' button, and I can not undo it because it is locked, how can I undo? – osexp2000 Sep 08 '18 at 02:33
  • @osexp2003 [I edited the post](https://meta.stackoverflow.com/questions/265858/can-we-remove-vote-lock-in), you can undo now – Kos Oct 15 '18 at 11:04
  • 11
    @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. https://stackoverflow.com/questions/1455988/commenting-in-a-bash-script/56979539#56979539 – wisbucky Jul 10 '19 at 22:46
  • dear heavens no, please don't abuse command substitutions for comments, they're not meant for that. – ilkkachu Feb 17 '21 at 16:53
  • Any examples of using this creative "abuse" with other language? vim's "\ comment seems different with shell's – Good Pen May 31 '22 at 10:42
  • wait, why does this work? won't bash try to run/interpret those commends? – Alexander Mills Sep 13 '22 at 19:49
  • May I ask if this trick works in a long `if` condition checking statement? I tried and I guess not. – midnite May 29 '23 at 17:44
148

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[@]}"
mgutt
  • 5,867
  • 2
  • 50
  • 77
Philipp
  • 48,066
  • 12
  • 84
  • 109
  • 10
    That works for something simple like the OP's example, but it won't support `>` and `<` and `|` and `||` and `&&` and so on. – ruakh Mar 01 '12 at 19:48
  • @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 `"`. – Peter Lee Mar 01 '12 at 19:58
  • @Philipp, I really don't which one should be the correct answer. It seems there is no correct answer. So I upvoted your answer, and marked Perry's as the correct answer. – Peter Lee Mar 05 '12 at 17:15
  • 2
    @PeterLee: you can use `"` and `'` in arrays as well. – Philipp Mar 05 '12 at 22:16
  • 10
    Less hackish is to just store the *arguments* in the array, then use them like so: `CommandName "${args[@]}"`. – chepner Dec 10 '14 at 14:17
  • 4
    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. – Kent Fredric Jan 02 '15 at 11:50
  • 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 – Luke Schoen Jun 15 '23 at 02:49
111

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.

Perry
  • 4,363
  • 1
  • 17
  • 20
  • 10
    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. – Han Seoul-Oh Feb 24 '17 at 23:20
  • 15
    Accepted answer should be Marwan's below. – springloaded Jul 16 '17 at 21:27
  • 5
    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. – alecbz Sep 20 '17 at 18:27
  • There is another way to do it, which doesn't involve subshells via hacking `$IFS`: see [here](https://stackoverflow.com/a/47374030/5353461). – Tom Hale Nov 19 '17 at 05:41
  • 1
    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. – misberner Nov 17 '18 at 21:04
  • The chosen answer should be the one by @MarwanAlsabbagh which now has 728 UpVotes versus this answer which now has only 94 UpVotes – Rakib May 21 '21 at 12:05
  • 1
    Just because a kludge is popular doesn't make it a good idea at all. – Perry Nov 04 '21 at 15:43
  • _"Just because a kludge is popular doesn't make it a good idea at all."_ Thank you! I mean, it _is_ a good idea, in that it's witty and sexy -- and that's precisely why the mob falls for it en masse, like moth to a flame. It's just a not "generally viable" one, unfortunately. (@Philipp's is, though, BTW.) – Sz. Aug 26 '23 at 00:12
21

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
chepner
  • 497,756
  • 71
  • 530
  • 681
  • 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. – ilkkachu Feb 17 '21 at 16:55
  • 1
    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. – chepner Feb 17 '21 at 17:06
  • 1
    (That said, this is a gross hack that I would never actually use it in my own shell scripts.) – chepner Feb 17 '21 at 17:13
  • yes, of course the "why" should also be understood, but sadly not everyone who comes to see a piece of old code completely understands it... And the sh language doesn't seem to be the easiest to understand, even without (ahem) _creative solutions_ like this. That said, I'm happy to read that last sentence in the parenthesis. – ilkkachu Feb 17 '21 at 17:25
  • 1
    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`. – Socowi Aug 24 '21 at 10:20
  • 2
    For `${parameter#word}` [POSIX specifies](https://pubs.opengroup.org/onlinepubs/009604499/utilities/xcu_chap02.html#tag_02_06_02) *"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 `"'``. – Socowi Aug 24 '21 at 11:50