183
votes

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?

9
Well, as you noticed, if you do # first, then the \ becomes just part of the comment, but if you do \ first, then the later characters on the line change its meaning away from "line continuation" to "quote". I've thought of one solution, given below. - DigitalRoss

9 Answers

227
votes

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 How to Put Line Comment for a Multi-line Command.

42
votes

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}
7
votes

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}
5
votes

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

5
votes

$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.

2
votes

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 #.

2
votes

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 
------------------------------------------------}
1
votes

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.

0
votes

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
  } \
| ...