在多行命令内的bash脚本中进行注释

如何对脚本中的以下各行进行注释?

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}

如果我尝试添加评论,如:

cat ${MYSQLDUMP} | \ # Output MYSQLDUMP File

我得到:

#: not found

可以在这里发表评论吗?

329594 次浏览

这会有一些开销,但从技术上讲,它确实回答了您的问题:

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

特别是对于管道,有一种没有开销的清洁解决方案:

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

请参阅堆栈溢出问题如何为多行命令添加行注释

反斜杠对#进行转义,将其解释为文字字符而不是注释字符。

尾随的反斜杠必须是行中的最后一个字符,才能将其解释为继续命令。它后面不允许有注释,甚至不允许有空格。

您应该能够在命令之间放置注释行。

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

正如DigitalRoss所指出的,当行以|结束时,尾部的反斜杠是不必要的。您可以将注释放在|之后的行上:

 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}

$IFS评论黑客

此黑客程序在$IFS上使用参数扩展,用于分隔命令中的单词:

$ echo foo${IFS}bar
foo bar

同样地:

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

使用它,您可以将注释放在带有Contination的命令行上:

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

但评论需要在\之前继续。

请注意,参数扩展是在注释中执行的:

$ 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

罕见的例外

唯一罕见的失败情况是,如果$IFS先前以精确文本启动,则通过扩展删除该字符(即,在#字符之后):

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

注意:最后一个foobar没有空格,说明了该问题。

因为默认情况下$IFS只包含空格,所以它是ABC_1的_,您不太可能遇到此问题。


这要归功于@PJH的评论,它引发了这一答案。

下面是一个bash脚本,它结合了前面几个注释的思想和习惯用法,通过示例提供了具有${__:+ <comment text>}的一般形式的内联注释。

尤其是

  • <comment text>可以是多行
  • <comment text>不是参数展开的
  • 不会产生任何子流程(因此注释是高效的)

<comment text>有一个限制,即,必须保护不平衡的大括号'}'和括号')'(即,'\}''\)')。

对本地bash环境有一个要求:

  • 必须取消设置ABC_0_参数名称

任何其他语法上有效的bash参数名称都将取代__,前提是名称没有设置值。

下面是一个示例脚本。

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

除了DigitalRoss的示例之外,如果您更喜欢$()而不是`的反引号,则可以使用以下另一种形式

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

当然,您也可以将冒号语法与反引号一起使用:

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

附加说明

$(#comment)不起作用的原因是,一旦它看到#,它就会将该行的其余部分视为注释,包括右括号:comment)。所以括号永远不会闭合。

反引号的解析方式不同,即使在#之后,反引号也会检测到结束反引号。

管道连接命令的首选编码风格是

command1 \
| command2 \
| ...

正如@吉姆格里沙姆和其他人所建议的,注释行的一种方法是

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

另一种不调用子shell的方法是使用bash的{ list; }结构,该结构始终有效。所以在这里:

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

而不是你尝试过的:

cat ${MYSQLDUMP} | \ # Output MYSQLDUMP File

其他人提到,这应该是可行的:

cat ${MYSQLDUMP} |   # Output MYSQLDUMP File

但是,由于分割线并不总是以管道(|)结束,因此你可以把评论放在他们自己的线上,就像这样

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

只是不要在字符串中间执行此操作:

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

在你的情况下,你可以使用这种方法:

cat ${MYSQLDUMP} | \
# Output MYSQLDUMP File

扩展示例:

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

...或者混合使用两种方法:

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

(我认为这两种方法都有效,因为shell脚本文件是逐行解析的,就像CLI输入一样。)

最后注释:

  • 务必记住,使用行继续符(\)时,其_应为ABC_0(即使是一个被遗忘的尾随空间也会毁了你的夜晚)

  • 如果要从命令行手动输入,请仅使用第二种方法,(每个注释都有自己的行)(如果您打算使用命令历史记录功能)。

  • 如果使用历史记录并希望保留注释,请不要使用这两种方法中的任何一种-请使用此问题的不同答案中的一种。

这里的答案是上下文。您希望能够注释很长的参数行和很长的复杂管道,但遗憾的是,语法并不以同样的方式工作。

但是有一个3步过程可以让您详细地注释几乎任何内容,而不会有`#...`的子shell开销,也不会有非常复杂地引用${IFS#...}的地雷:

把所有的东西都放到一个函数中,这样你就可以使用local变量。

您正在编写一个足够复杂的shell脚本,无论如何都要有函数,对吗?为了使用命名数组语法,我们需要给所有东西命名,但我们也不想污染全局shell名称空间,因此将它们整理到局部变量声明中。

将长命令放入数组

数组不需要\行延续,因此您可以像平常一样使用注释,如下所示:

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

在定义所有数组后放置管道

如果重定向很复杂,您可能需要解释它,但如果您有一个很长的参数列表,它可能会妨碍您。因此,使用几个名称简短的local变量,这些变量足够短,可以容纳您的注释,然后使用重定向或管道来分隔行,而不是使用反斜杠:

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

这样,重定向永远不会太长,以至于您的评论不适合。您可以选择这些变量名,因为它们是ABC_0的_,所以您不需要使它们在整个脚本中唯一。

把它们放在一起

当你把它们放在一起时,它看起来像这样:

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
}

注意:此答案中的所有内容都经过zsh的检查,因此虽然它(可能?)不能在POSIXsh中工作,但它并不完全是bash特定的。