比方说,你有一个Bash别名:

alias rxvt='urxvt'

这很好。

然而:

alias rxvt='urxvt -fg '#111111' -bg '#111111''

不管用,也不管用:

alias rxvt='urxvt -fg \'#111111\' -bg \'#111111\''

那么,一旦有转义引号,如何在字符串中匹配开始和结束引号呢?

alias rxvt='urxvt -fg'\''#111111'\'' -bg '\''#111111'\''

看起来很笨拙,但如果允许像这样连接它们,它将表示相同的字符串。


当前回答

如果你已经安装了GNU Parallel,你可以使用它的内部引用:

$ parallel --shellquote
L's 12" record
<Ctrl-D>
'L'"'"'s 12" record'
$ echo 'L'"'"'s 12" record'
L's 12" record

从版本20190222,你甚至可以——shellquote多次:

$ parallel --shellquote --shellquote --shellquote
L's 12" record
<Ctrl-D>
'"'"'"'"'"'"'L'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'s 12" record'"'"'"'"'"'"'
$ eval eval echo '"'"'"'"'"'"'L'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'s 12" record'"'"'"'"'"'"'
L's 12" record

它将在所有受支持的shell(不仅仅是bash)中引用该字符串。

其他回答

我在他的博客上没有看到这个条目(请链接?),但根据gnu参考手册:

用单引号括住字符 (" ')保存的文字值 引号中的每个字符。一个 单引号之间不能出现 单引号,即使前面有 反斜杠。

所以bash不会理解:

别名x='y \'z '

但是,你可以用双引号括起来:

alias x="echo \'y "
> x
> 'y

显然,简单地用双引号括起来会更容易,但这其中的挑战在哪里呢?下面是只用单引号的答案。我用的是变量而不是别名这样更容易打印出来证明,但这和使用别名是一样的。

$ rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'
$ echo $rxvt
urxvt -fg '#111111' -bg '#111111'

解释

关键在于,您可以关闭单引号,并根据需要多次重新打开它。例如,foo='a " b'与foo='ab'相同。所以你可以关闭单引号,放入一个文字单引号\',然后重新打开下一个单引号。

分解图

这个图通过使用括号来显示单引号的开始和结束位置,使之更加清晰。引号不像圆括号那样“嵌套”。你也可以注意颜色的高亮,这是正确的应用。引号中的字符串是栗色的,而\'是黑色的。

'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'    # original
[^^^^^^^^^^] ^[^^^^^^^] ^[^^^^^] ^[^^^^^^^] ^    # show open/close quotes
 urxvt -fg   ' #111111  '  -bg   ' #111111  '    # literal characters remaining

(这基本上和Adrian的答案是一样的,但我觉得这解释得更好。而且他的回答在结尾有两个多余的单引号。)

如何逃脱单引号(')和双引号(")与十六进制和八进制字符

如果使用像echo这样的东西,我有一些非常复杂,非常奇怪和难以逃避(想想:非常嵌套)的情况下,我唯一能做的就是使用八进制或十六进制代码!

下面是一些基本的例子来演示它是如何工作的:

1. 单引号示例,其中'转义为十六进制\x27或八进制\047(其对应的ASCII码):

十六进制\ x27 echo -e“让\x27s开始编码!” #或 echo -e“让\x27s开始编码!” 结果: 让我们开始编码吧! 八进制\ 047 echo -e“让047s开始编码!” #或 echo -e“让047s开始编码!” 结果: 让我们开始编码吧!

2. 双引号示例,其中"转义为十六进制\x22或八进制\042(其对应的ASCII码)。

注意:bash太疯狂了!有时甚至!Char有特殊的含义,必须从双引号内删除,然后转义为“像这样”\!或者完全用单引号括起来,像这样!,而不是在双引号内。

# 1. hex; also escape `!` by removing it from within the double quotes 
# and escaping it with `\!`
$ echo -e "She said, \x22Let\x27s get coding"\!"\x22"
She said, "Let's get coding!"

# OR put it all within single quotes:
$ echo -e 'She said, \x22Let\x27s get coding!\x22'
She said, "Let's get coding!"


# 2. octal; also escape `!` by removing it from within the double quotes 
$ echo -e "She said, \042Let\047s get coding"\!"\042"
She said, "Let's get coding!"

# OR put it all within single quotes:
$ echo -e 'She said, \042Let\047s get coding!\042'
She said, "Let's get coding!"


# 3. mixed hex and octal, just for fun
# also escape `!` by removing it from within the double quotes when it is followed by
# another escape sequence
$ echo -e "She said, \x22Let\047s get coding! It\x27s waaay past time to begin"\!"\042"
She said, "Let's get coding! It's waaay past time to begin!"

# OR put it all within single quotes:
$ echo -e 'She said, \x22Let\047s get coding! It\x27s waaay past time to begin!\042'
She said, "Let's get coding! It's waaay past time to begin!"

注意,如果你在需要的时候没有正确地转义!,就像我上面展示的两种方法一样,你会得到一些奇怪的错误,像这样:

$ echo -e "She said, \x22Let\047s get coding! It\x27s waaay past time to begin!\042"
bash: !\042: event not found

OR:

$ echo -e "She said, \x22Let\x27s get coding!\x22"
bash: !\x22: event not found

另一种选择:这允许在同一个bash字符串中混合展开和非展开

这里是另一个替代逃逸技术的演示。

首先,阅读@liori的主要回答,看看下面的第二种形式是如何工作的。现在,阅读这两种转义字符的方法。下面两个例子的输出是相同的:

CMD="gs_set_title"

# 1. 1st technique: escape the $ symbol with a backslash (\) so it doesn't 
# run and expand the command following it
echo "$CMD '\$(basename \"\$(pwd)\")'"

# 2. 2nd technique (does the same thing in a different way): escape the 
# $ symbol using single quotes around it, and the single quote (') symbol
# using double quotes around it
echo "$CMD ""'"'$(basename "$(pwd)")'"'"

样例输出:

Gs_set_title '$(basename "$(pwd)")' Gs_set_title '$(basename "$(pwd)")'

注意:对于我的gs_set_title bash函数,它在我的~/。Bash_aliases文件在这里,看到我的另一个答案。

引用:

https://en.wikipedia.org/wiki/ASCII#Printable_characters https://serverfault.com/questions/208265/what-is-bash-event-not-found/208266#208266 请参见我在这里的另一个回答:如何使用echo编写非ascii字符?

由于不能在单引号字符串中放入单引号,因此最简单且最易读的选项是使用HEREDOC字符串

command=$(cat <<'COMMAND'
urxvt -fg '#111111' -bg '#111111'
COMMAND
)

alias rxvt=$command

在上面的代码中,HEREDOC被发送给cat命令,该命令的输出通过命令替换符号$(..)分配给一个变量。

需要在HEREDOC周围加上单引号,因为它在$()中

这些答案大部分都是针对你所询问的具体情况。我和一个朋友开发了一种通用方法,允许在需要通过多层shell扩展(例如通过ssh、su -c、bash -c等)引用bash命令时使用任意引号。在原生bash中,你需要一个核心原语:

quote_args() {
    local sq="'"
    local dq='"'
    local space=""
    local arg
    for arg; do
        echo -n "$space'${arg//$sq/$sq$dq$sq$dq$sq}'"
        space=" "
    done
}

这就像它所说的那样:它单独地对每个参数进行shell引用(当然是在bash扩展之后):

$ quote_args foo bar
'foo' 'bar'
$ quote_args arg1 'arg2 arg2a' arg3
'arg1' 'arg2 arg2a' 'arg3'
$ quote_args dq'"'
'dq"'
$ quote_args dq'"' sq"'"
'dq"' 'sq'"'"''
$ quote_args "*"
'*'
$ quote_args /b*
'/bin' '/boot'

它对一层膨胀做了显而易见的事情:

$ bash -c "$(quote_args echo a'"'b"'"c arg2)"
a"b'c arg2

(注意$(quote_args…)周围的双引号是将结果变成bash -c的单个参数所必需的。)它可以更广泛地用于通过多层展开来正确引用:

$ bash -c "$(quote_args bash -c "$(quote_args echo a'"'b"'"c arg2)")"
a"b'c arg2

上面的例子:

shell将每个参数分别引用到内部的quote_args,然后将结果输出合并为带有内部双引号的单个参数。 Shell-quotes bash、-c和步骤1中已经加过一次引号的结果,然后使用外部双引号将结果合并为单个参数。 将这个混乱作为参数发送给外部bash -c。

这是一个简单的想法。你可以用它做一些非常复杂的事情,但是你必须注意求值的顺序以及哪些子字符串被引用。例如,以下做了错误的事情(对于“错误”的一些定义):

$ (cd /tmp; bash -c "$(quote_args cd /; pwd 1>&2)")
/tmp
$ (cd /tmp; bash -c "$(quote_args cd /; [ -e *sbin ] && echo success 1>&2 || echo failure 1>&2)")
failure

在第一个示例中,bash立即展开quote_args cd /;将pwd 1>&2分为两个单独的命令quote_args cd /和pwd 1>&2,因此执行pwd命令时,CWD仍然是/tmp。第二个例子说明了globing的类似问题。实际上,所有bash扩展都存在相同的基本问题。这里的问题是,命令替换不是函数调用:它实际上是计算一个bash脚本,并将其输出用作另一个bash脚本的一部分。

如果你试图简单地转义shell操作符,你会失败,因为传递给bash -c的结果字符串只是一个单独引用的字符串序列,然后不会被解释为操作符,如果你回显将传递给bash的字符串,这很容易看出:

$ (cd /tmp; echo "$(quote_args cd /\; pwd 1\>\&2)")
'cd' '/;' 'pwd' '1>&2'
$ (cd /tmp; echo "$(quote_args cd /\; \[ -e \*sbin \] \&\& echo success 1\>\&2 \|\| echo failure 1\>\&2)")
'cd' '/;' '[' '-e' '*sbin' ']' '&&' 'echo' 'success' '1>&2' '||' 'echo' 'failure' '1>&2'

问题是你引用的太多了。您需要的是将操作符作为外围bash -c的输入不加引号,这意味着它们需要在$(quote_args…)命令替换之外。

因此,在最普遍的意义上,你需要做的是对命令中不打算在命令替换时单独展开的每个单词进行shell-quote,并且不要对shell操作符应用任何额外的引号:

$ (cd /tmp; echo "$(quote_args cd /); $(quote_args pwd) 1>&2")
'cd' '/'; 'pwd' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")
/
$ (cd /tmp; echo "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
'cd' '/'; [ -e *'sbin' ] && 'echo' 'success' 1>&2 || 'echo' 'failure' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
success

一旦你这样做了,整个字符串是公平的游戏,进一步引用任意级别的评估:

$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")"
/
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")"
/
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")")"
/
$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")"
success
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *sbin ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")"
success
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")")"
success

etc.

考虑到像success、sbin和pwd这样的词不需要使用shell引号,这些例子似乎有些过度,但在编写接受任意输入的脚本时,要记住的关键点是,您希望引用所有您不确定是否需要引用的内容,因为您永远不知道用户何时会抛出一个Robert';Rm -rf /。

为了更好地理解幕后发生了什么,你可以使用两个小的帮助函数:

debug_args() {
    for (( I=1; $I <= $#; I++ )); do
        echo -n "$I:<${!I}> " 1>&2
    done
    echo 1>&2
}

debug_args_and_run() {
    debug_args "$@"
    "$@"
}

在执行命令之前枚举命令的每个参数:

$ debug_args_and_run echo a'"'b"'"c arg2
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)"
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'> 
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''> 
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'> 
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'bash'"'"' '"'"'-c'"'"' '"'"''"'"'"'"'"'"'"'"'debug_args_and_run'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'echo'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'a"b'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'c'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'arg2'"'"'"'"'"'"'"'"''"'"''> 
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''> 
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'> 
1:<echo> 2:<a"b'c> 3:<arg2> 
a"b'c arg2