Commenting in a Bash script inside a multiline command
BashSyntaxCommentsBash 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#...}
:
local
variables
Put everything into a function so you can use 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.