Commenting in a Bash script inside a multiline command

BashSyntaxComments

Bash Problem Overview


How can I comment on each line of the following lines from a script?

cat ${MYSQLDUMP} | \
sed '1d' | \
tr ",;" "\n" | \
sed -e 's/[asbi]:[0-9]*[:]*//g' -e '/^[{}]/d' -e 's/""//g' -e '/^"{/d' | \
sed -n -e '/^"/p' -e '/^print_value$/,/^option_id$/p' | \
sed -e '/^option_id/d' -e '/^print_value/d' -e 's/^"\(.*\)"$/\1/' | \
tr "\n" "," | \
sed -e 's/,\([0-9]*-[0-9]*-[0-9]*\)/\n\1/g' -e 's/,$//' | \
sed -e 's/^/"/g' -e 's/$/"/g' -e 's/,/","/g' >> ${CSV}

If I try and add a comment like:

cat ${MYSQLDUMP} | \ # Output MYSQLDUMP File

I get:

#: not found

Is it possible to comment here?

Bash Solutions


Solution 1 - Bash

This will have some overhead, but technically it does answer your question:

echo abc `#Put your comment here` \
     def `#Another chance for a comment` \
     xyz, etc.

And for pipelines specifically, there is a clean solution with no overhead:

echo abc |        # Normal comment OK here
     tr a-z A-Z | # Another normal comment OK here
     sort |       # The pipelines are automatically continued
     uniq         # Final comment

See Stack Overflow question https://stackoverflow.com/q/9522631/#12797512.

Solution 2 - Bash

The trailing backslash must be the last character on the line for it to be interpreted as a continuation command. No comments or even whitespace are allowed after it.

You should be able to put comment lines in between your commands

# output MYSQLDUMP file
cat ${MYSQLDUMP} | \
# simplify the line
sed '/created_at/d' | \
# create some newlines
tr ",;" "\n" | \
# use some sed magic
sed -e 's/[asbi]:[0-9]*[:]*//g' -e '/^[{}]/d' -e 's/""//g' -e '/^"{/d' | \
# more magic
sed -n -e '/^"/p' -e '/^print_value$/,/^option_id$/p' | \
# even more magic
sed -e '/^option_id/d' -e '/^print_value/d' -e 's/^"\(.*\)"$/\1/' | \
tr "\n" "," | \
# I hate phone numbers in my output
sed -e 's/,\([0-9]*-[0-9]*-[0-9]*\)/\n\1/g' -e 's/,$//' | \ 
# one more sed call and then send it to the CSV file
sed -e 's/^/"/g' -e 's/$/"/g' -e 's/,/","/g' >> ${CSV}

Solution 3 - Bash

As DigitalRoss pointed out, the trailing backslash is not necessary when the line woud end in |. And you can put comments on a line following a |:

 cat ${MYSQLDUMP} |         # Output MYSQLDUMP file
 sed '1d' |                 # skip the top line
 tr ",;" "\n" | 
 sed -e 's/[asbi]:[0-9]*[:]*//g' -e '/^[{}]/d' -e 's/""//g' -e '/^"{/d' |
 sed -n -e '/^"/p' -e '/^print_value$/,/^option_id$/p' |
 sed -e '/^option_id/d' -e '/^print_value/d' -e 's/^"\(.*\)"$/\1/' |
 tr "\n" "," |
 sed -e 's/,\([0-9]*-[0-9]*-[0-9]*\)/\n\1/g' -e 's/,$//' |   # hate phone numbers
 sed -e 's/^/"/g' -e 's/$/"/g' -e 's/,/","/g' >> ${CSV}

Solution 4 - Bash

$IFS comment hacks

This hack uses parameter expansion on $IFS, which is used to separate words in commands:

$ echo foo${IFS}bar
foo bar

Similarly:

$ echo foo${IFS#comment}bar
foo bar

Using this, you can put a comment on a command line with contination:

$ echo foo${IFS# Comment here} \
> bar
foo bar

but the comment will need to be before the \ continuation.

Note that parameter expansion is performed inside the comment:

$ ls file
ls: cannot access 'file': No such file or directory
$ echo foo${IFS# This command will create file: $(touch file)}bar
foo bar
$ ls file
file

Rare exception

The only rare case this fails is if $IFS previously started with the exact text which is removed via the expansion (ie, after the # character):

$ IFS=x
$ echo foo${IFS#y}bar
foo bar
$ echo foo${IFS#x}bar
foobar

Note the final foobar has no space, illustrating the issue.

Since $IFS contains only whitespace by default, it's extremely unlikely you'll run into this problem.


Credit to @pjh's comment which sparked off this answer.

Solution 5 - Bash

The backslash escapes the #, interpreting it as its literal character instead of a comment character.

Solution 6 - Bash

In addition to the examples by DigitalRoss, here's another form that you can use if you prefer $() instead of backticks `

echo abc $(: comment) \
     def $(: comment) \
     xyz

Of course, you can use the colon syntax with backticks as well:

echo abc `: comment` \
     def `: comment` \
     xyz

Additional Notes

The reason $(#comment) doesn't work is because once it sees the #, it treats the rest of the line as comments, including the closing parentheses: comment). So the parentheses is never closed.

Backticks parse differently and will detect the closing backtick even after a #.

Solution 7 - Bash

Here is a bash script that combines the ideas and idioms of several previous comments to provide, with examples, inline comments having the general form ${__:+ <comment text>}.

In particular

  • <comment text> can be multi-line
  • <comment text> is not parameter-expanded
  • no subprocesses are spawned (so comments are efficient)

There is one restriction on the <comment text>, namely, unbalanced braces '}' and parentheses ')' must be protected (i.e., '\}' and '\)').

There is one requirement on the local bash environment:

  • the parameter name __ must be unset

Any other syntactically valid bash parameter-name will serve in place of __, provided that the name has no set value.

An example script follows

# provide bash inline comments having the form
#     <code> ${__:+ <comment>} <code> 
#     <code> ${__:+ <multiline
#                   comment>} <code>

# utility routines that obviate "useless use of cat"
function bashcat { printf '%s\n' "$(</dev/stdin)"; }
function scat { 1>&2 bashcat; exit 1; }

# ensure that '__' is unset && remains unset
[[ -z ${__+x} ]] &&  # if '__' is unset
  declare -r __ ||   # then ensure that '__' remains unset 
  scat <<EOF         # else exit with an error
Error: the parameter __='${__}' is set, hence the
  comment-idiom '\${__:+ <comment text>}' will fail
EOF

${__:+ (example of inline comments)
------------------------------------------------
the following inline comment-idiom is supported
    <code> ${__:+ <comment>} <code> 
    <code> ${__:+ <multiline
                   comment>} <code> 
(advisory) the parameter '__' must NOT be set;
  even the null declaration __='' will fail
(advisory) protect unbalanced delimiters \} and \) 
(advisory) NO parameter-expansion of <comment> 
(advisory) NO subprocesses are spawned
(advisory) a functionally equivalent idiom is 
    <code> `# <comment>` <code> 
    <code> `# <multiline
               comment>` <code>
however each comment spawns a bash subprocess
that inelegantly requires ~1ms of computation 
------------------------------------------------}

Solution 8 - Bash

Instead of what you tried:

cat ${MYSQLDUMP} | \ # Output MYSQLDUMP File

Others have mentioned that this should work:

cat ${MYSQLDUMP} |   # Output MYSQLDUMP File

Since split lines won't always end in a pipe (|), though, you can put comments on their own line, like this:

date && \
  # List current directory
  ls -l | awk '{ \
  # Filename is in the ninth column
      # This is just making "ls -l" work mostly like "ls -1"
  print $9 }'

Just don't do so in the middle of a string:

echo " Hello \
 # Localized name for your planet:
world."

In your case, you can use this method:

cat ${MYSQLDUMP} | \
# Output MYSQLDUMP File

Extended example:

# Create .csv file from MySQL dump file
cat ${MYSQLDUMP} |   
# Output MYSQLDUMP File
# and pipe to first sed command
sed '1d' | \
# Pipe output to tr
tr ",;" "\n" | \
# Apply sed expression
sed -e 's/[asbi]:[0-9]*[:]*//g' -e '/^[{}]/d' -e 's/""//g' -e '/^"{/d' | \
    # Apply another two sed expressions
    # (and since whitespace is ignored, you can intent for clarity)
    sed -n -e '/^"/p' -e '/^print_value$/,/^option_id$/p' | \
    # Apply three more sed expressions
    sed -e '/^option_id/d' -e '/^print_value/d' -e 's/^"\(.*\)"$/\1/' | \
    # Use tr to ...
    tr "\n" "," | \
    # Apply yet another two sed expressions
    sed -e 's/,\([0-9]*-[0-9]*-[0-9]*\)/\n\1/g' -e 's/,$//' | \
    # Apply the final three sed expressions
    sed -e 's/^/"/g' -e 's/$/"/g' -e 's/,/","/g' >> ${CSV}

... or mix both methods:

# Create .csv file from MySQL dump file
cat ${MYSQLDUMP} |   # Output MYSQLDUMP File
# and pipe to first sed command
sed '1d' | \
# Pipe output to tr
...

(I believe both methods work since shell script files are parsed line-by-line, as is CLI input.)

Final notes:

  • It is important to remember that the line continuation character (\), when used, should be the last character in that line (even a single forgotten trailing space can ruin your evening).

  • If typing manually from the command line, use only the second method (with each comment on its own line) if you intend on using the command history feature.

  • If using history and want comments preserved, do not use either of these methods - use one from a different answer to this question.

Solution 9 - Bash

My preferred coding style for pipe connected commands is

command1 \
| command2 \
| ...

As @JimGrisham and others suggested, one way to comment lines is

command1 \
| # inline comment
  command2 \
| ...

Another way that does not invoke subshells is using Bash's { list; } construct which works always. So here:

command1 \
| {
    # inline comment
    command2
  } \
| ...

Solution 10 - Bash

The answer here is contextual. You want to be able to annotate long lines of parameters and also long complex pipelines, and sadly the syntax does not work the same way.

But there is a 3-step process that can let you comment almost anything in detail, without the subshell overhead of `#...` or the very complicated quoting land-mines of ${IFS#...}:

Put everything into a function so you can use local variables

You're writing a complex enough shell script to have functions anyway, right? We need to give everything names in order to use the named-array syntax, but we also don't want to pollute the global shell namespace, so tidy them away into local variable declarations.

Put long commands into arrays

Arrays don't need \ line continuations, so you can use comments as normal, like this:

local x=(
    "command-name"
    # here's the command
    "argument" # and here's the first arg
)

Put pipelines after all arrays have been defined

If a redirection is complex, you may want to explain it, but if you have a long list of parameters it might get in your way. So use a couple of short-named local vars that will be short enough to fit your comments, then use redirections or pipelines to break up the lines, not backslashes:

"${x[@]} | # here is my first command
    "${y[@]} | # and here's the second one
    ...

That way the redirection can never get so long that your comments won't fit; you get to choose those variable names, and because they're local you don't need to make them unique for your whole script.

Put it all together

When you get it all together, it looks like this:

f() {
    local produce_text=(
        echo
        # I'm echoing
        "first   argument"
        # newline!
        '
'
        # Here's the first arg.
        "second    argument"
        # Here's the second.
    )
    local number_lines=(
        cat
        # I'm concatenating
        -b
        # with numbered lines
    )
    "${produce_text[@]}" |
        # explaining the pipeline
        "${number_lines[@]}" \
            2>&1
    # done
}

N.B.: Everything in this answer was also checked with zsh, so while it (probably?) won't work in POSIX sh, it's not entirely bash-specific.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBassKozzView Question on Stackoverflow
Solution 1 - BashDigitalRossView Answer on Stackoverflow
Solution 2 - BashmobView Answer on Stackoverflow
Solution 3 - BashmobView Answer on Stackoverflow
Solution 4 - BashTom HaleView Answer on Stackoverflow
Solution 5 - BashtobiasvlView Answer on Stackoverflow
Solution 6 - BashwisbuckyView Answer on Stackoverflow
Solution 7 - BashJohn SidlesView Answer on Stackoverflow
Solution 8 - BashJim GrishamView Answer on Stackoverflow
Solution 9 - BashxebecheView Answer on Stackoverflow
Solution 10 - BashGlyphView Answer on Stackoverflow