处理多个级别的引用(实际上是多个级别的解析/解释)可能会变得很复杂。请记住以下几点:
- 每个“报价水平”都可能涉及不同的语言。
- 报价规则因语言而异。
- 当处理一个或两个以上的嵌套层时,通常最容易“从下到上”(即,从最内到最外)进行工作。
报价水平
让我们看看您的示例命令。
pgrep -fl java | grep -i datanode | awk '{print $1}'
您的第一个示例命令(上述)使用四种语言:您的外壳,pgrep中的正则表达式,grep中的正则表达式(可能与pgrep中的正则表达式语言不同)和awk。涉及两个级别的解释:外壳程序和每个相关命令在外壳程序之后的一级。报价只有一个明确的级别(将shell报价为awk)。
ssh host …
接下来,您在顶部添加了一个ssh级别。这实际上是另一个shell级别:ssh不会解释命令本身,而是将其交给远程端的shell(通过(例如)sh -c …
),并且该shell解释字符串。
ssh host "sudo su user -c …"
然后,您询问了如何使用su(通过sudo在中间添加另一个shell级别,它不会解释其命令参数,因此我们可以忽略它)。至此,您将进行三个级别的嵌套(awk →shell,shell→shell(ssh),shell→shell(su用户-c),所以我建议使用“自下而上”的方法。您的外壳是Bourne兼容的(例如sh,ash,dash,ksh,bash,zsh等)。其他某种外壳(fish,rc等等)可能需要使用不同的语法,但是该方法仍然适用。
自下而上
- 制定要在最内层表示的字符串。
- 从第二高语言的报价单中选择一种报价机制。
- 根据您选择的报价机制为所需的字符串报价。
- 如何应用哪种报价机制通常会有很多变体。手工操作通常是实践和经验的问题。以编程方式进行操作时,通常最好选择最容易正确的方法(通常是“最文字”(最少的转义符))。
- (可选)将结果带引号的字符串与其他代码一起使用。
- 如果尚未达到所需的引用/解释水平,请使用生成的带引号的字符串(加上任何添加的代码)并将其用作步骤2中的起始字符串。
报价语义变化
这里要记住的是,每种语言(引用级别)可能给相同的引用字符赋予稍微不同的语义(甚至是完全不同的语义)。
大多数语言都有“文字”报价机制,但是它们的字面意义完全不同。类似于Bourne的shell的单引号实际上是文字(这意味着您不能使用它来引述单引号字符本身)。其他语言(Perl,Ruby)的字面性较差,因为它们非直译地(特别是在单引号区域内)解释了一些反斜杠序列,\\
并\'
导致\
和'
,但其他反斜杠序列实际上是文字)。
您将必须阅读每种语言的文档,以了解其引用规则和整体语法。
你的例子
您的示例的最内层是awk程序。
{print $1}
您将把它嵌入到shell命令行中:
pgrep -fl java | grep -i datanode | awk …
我们需要保护(至少)的空间和$
在AWK程序。显而易见的选择是在整个程序的外壳程序中使用单引号。
但是,还有其他选择:
{print\ \$1}
直接逃脱空间, $
{print' $'1}
单引号仅空格和 $
"{print \$1}"
请双引号整体并转义 $
{print" $"1}
仅对空格使用双引号,并且$
这可能会使规则有些弯曲($
在双引号字符串的末尾未转义的是文字),但这似乎在大多数shell中都有效。
如果程序在左花括号和右花括号之间使用逗号,则我们还需要引用或转义逗号或花括号,以避免在某些shell中出现“花括号扩展”。
我们选择'{print $1}'
它并将其嵌入外壳的其余“代码”中:
pgrep -fl java | grep -i datanode | awk '{print $1}'
接下来,您想通过su和sudo运行它。
sudo su user -c …
su user -c …
就像some-shell -c …
(除了在其他UID下运行)一样,所以su只是添加了另一个shell级别。sudo不会解释其参数,因此不会添加任何引用级别。
我们需要另一个shell级别的命令字符串。我们可以再次选择单引号,但是必须对现有的单引号进行特殊处理。通常的方式如下:
'pgrep -fl java | grep -i datanode | awk '\''{print $1}'\'
shell将在这里解释和连接四个字符串:第一个单引号的字符串(pgrep … awk
),一个转义的单引号,一个单引号的awk程序,另一个转义的单引号。
当然,有许多替代方法:
pgrep\ -fl\ java\ \|\ grep\ -i\ datanode\ \|\ awk\ \'{print\ \$1}
逃避重要的一切
pgrep\ -fl\ java\|grep\ -i\ datanode\|awk\ \'{print\$1}
相同,但是没有多余的空格(即使在awk程序中也是如此!)
"pgrep -fl java | grep -i datanode | awk '{print \$1}'"
双重引用整个事情,逃避 $
'pgrep -fl java | grep -i datanode | awk '"'"'{print \$1}'"'"
您的变化;由于使用双引号(两个字符)而不是转义符(一个字符),因此比通常的方法长一点
在第一层使用不同的报价可以在该层进行其他变化:
'pgrep -fl java | grep -i datanode | awk "{print \$1}"'
'pgrep -fl java | grep -i datanode | awk {print\ \$1}'
在sudo / * su *命令行中嵌入第一个版本可以得到以下效果:
sudo su user -c 'pgrep -fl java | grep -i datanode | awk '\''{print $1}'\'
您可以在其他任何单个Shell级别上下文中使用相同的字符串(例如ssh host …
)。
接下来,您在顶部添加了一个ssh级别。这实际上是另一个shell级别:ssh不会解释命令本身,而是将其交给远程端的shell(通过(例如)sh -c …
),并且该shell解释字符串。
ssh host …
过程是相同的:接收字符串,选择一种引用方法,使用它,然后将其嵌入。
再次使用单引号:
'sudo su user -c '\''pgrep -fl java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'
现在有11个字符串被解释和连接起来:'sudo su user -c '
,转义的单引号'pgrep … awk '
,,转义的单引号,转义的反斜杠,两个转义的单引号,单引号awk程序,一个转义的单引号,一个转义的反斜杠以及最后一个转义的单引号。
最终形式如下:
ssh host 'sudo su user -c '\''pgrep -fl java | grep -i datanode | awk '\'\\\'\''{print $1}'\'\\\'
手工键入有点笨拙,但是外壳的单引号的字面性质使自动进行轻微的变化变得容易:
#!/bin/sh
sq() { # single quote for Bourne shell evaluation
# Change ' to '\'' and wrap in single quotes.
# If original starts/ends with a single quote, creates useless
# (but harmless) '' at beginning/end of result.
printf '%s\n' "$*" | sed -e "s/'/'\\\\''/g" -e 1s/^/\'/ -e \$s/\$/\'/
}
# Some shells (ksh, bash, zsh) can do something similar with %q, but
# the result may not be compatible with other shells (ksh uses $'...',
# but dash does not recognize it).
#
# sq() { printf %q "$*"; }
ap='{print $1}'
s1="pgrep -fl java | grep -i datanode | awk $(sq "$ap")"
s2="sudo su user -c $(sq "$s1")"
ssh host "$(sq "$s2")"