假设您有一个 Bash alias
,例如:
alias rxvt='urxvt'
效果很好。
然而:
alias rxvt='urxvt -fg '#111111' -bg '#111111''
不会工作,也不会:
alias rxvt='urxvt -fg \'#111111\' -bg \'#111111\''
那么,一旦你转义了引号,你如何最终匹配字符串中的开始和结束引号?
alias rxvt='urxvt -fg'\''#111111'\'' -bg '\''#111111'\''
看起来很笨拙,尽管如果允许您像这样连接它们,它将代表相同的字符串。
"\""
,因此应尽可能优先使用这些双引号而不是 @liori 的答案。
如果您真的想在最外层使用单引号,请记住您可以粘合两种引号。例子:
alias rxvt='urxvt -fg '"'"'#111111'"'"' -bg '"'"'#111111'"'"
# ^^^^^ ^^^^^ ^^^^^ ^^^^
# 12345 12345 12345 1234
关于如何将 '"'"'
解释为 '
的说明:
' 结束使用单引号的第一个引号。 " 开始第二个引号,使用双引号。' 引号字符。" 结束第二个引号,使用双引号。 ' 使用单引号开始第三个引号。
如果您没有在 (1) 和 (2) 之间或 (4) 和 (5) 之间放置任何空格,shell 会将该字符串解释为一个长单词。
我总是用以下序列替换每个嵌入的单引号:'\''
(即:引号反斜杠引号),它关闭字符串,附加一个转义的单引号并重新打开字符串。
我经常在我的 Perl 脚本中创建一个“引用”函数来为我做这件事。步骤是:
s/'/'\\''/g # Handle each embedded quote
$_ = qq['$_']; # Surround result with single quotes.
这几乎可以解决所有情况。
当您将 eval
引入您的 shell 脚本时,生活会变得更加有趣。您基本上必须再次重新引用所有内容!
例如,创建一个名为 quotify 的 Perl 脚本,其中包含上述语句:
#!/usr/bin/perl -pl
s/'/'\\''/g;
$_ = qq['$_'];
然后用它来生成一个正确引用的字符串:
$ quotify
urxvt -fg '#111111' -bg '#111111'
结果:
'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''
然后可以将其复制/粘贴到别名命令中:
alias rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''
(如果您需要将命令插入到 eval 中,请再次运行 quotify:
$ quotify
alias rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''
结果:
'alias rxvt='\''urxvt -fg '\''\'\'''\''#111111'\''\'\'''\'' -bg '\''\'\'''\''#111111'\''\'\'''\'''\'''
可以复制/粘贴到 eval 中:
eval 'alias rxvt='\''urxvt -fg '\''\'\'''\''#111111'\''\'\'''\'' -bg '\''\'\'''\''#111111'\''\'\'''\'''\'''
set -x
和 echo "here's a string"
,您会看到 bash 执行 echo 'here'\''s a string'
。 (set +x
返回正常行为)
由于 Bash 2.04 语法 $'string'
允许有限的转义集。
从 Bash 4.4 开始,$'string'
还允许使用完整的 C-style escapes 集,这使得之前版本的 $'string'
的行为略有不同。 (以前可以使用 $('string')
形式。)
Bash 2.04 和更新版本中的简单示例:
$> echo $'aa\'bb'
aa'bb
$> alias myvar=$'aa\'bb'
$> alias myvar
alias myvar='aa'\''bb'
在你的情况下:
$> alias rxvt=$'urxvt -fg \'#111111\' -bg \'#111111\''
$> alias rxvt
alias rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''
常见的转义序列按预期工作:
\' single quote
\" double quote
\\ backslash
\n new line
\t horizontal tab
\r carriage return
以下是来自 man bash
(4.4 版)的复制+粘贴相关文档:
$'string' 形式的单词被特殊处理。该单词扩展为字符串,并按照 ANSI C 标准的规定替换反斜杠转义字符。反斜杠转义序列(如果存在)按如下方式解码:
\a alert (bell)
\b backspace
\e
\E an escape character
\f form feed
\n new line
\r carriage return
\t horizontal tab
\v vertical tab
\\ backslash
\' single quote
\" double quote
\? question mark
\nnn the eight-bit character whose value is the octal
value nnn (one to three digits)
\xHH the eight-bit character whose value is the hexadecimal
value HH (one or two hex digits)
\uHHHH the Unicode (ISO/IEC 10646) character whose value is
the hexadecimal value HHHH (one to four hex digits)
\UHHHHHHHH the Unicode (ISO/IEC 10646) character whose value
is the hexadecimal value HHHHHHHH (one to eight
hex digits)
\cx a control-x character
扩展的结果是单引号的,就好像美元符号不存在一样。
有关更多详细信息,请参阅 bash-hackers.org wiki 上的 Quotes and escaping: ANSI C like strings。另请注意,"Bash Changes" 文件 (overview here) 提到了很多与 $'string'
引用机制相关的更改和错误修复。
根据 unix.stackexchange.com How to use a special character as a normal one?,它应该在 bash、zsh、mksh、ksh93 和 FreeBSD 和 busybox sh 中工作(有一些变化)。
echo $'foo\'b!ar'
=> !ar': event not found
> echo $BASH_VERSION
4.2.47(1)-release
> echo $'foo\'b!ar'
foo'b!ar
$'
相关的错误修复,因此最简单的方法可能是在旧系统上自行尝试。
\
来转义 C 样式序列很容易解决。示例:alias foo=$'echo \1'
与 alias boo='echo \1'
不同
我在他的博客上没有看到该条目(请链接?),但根据 gnu reference manual:
将字符括在单引号 (''') 中会保留引号内每个字符的文字值。单引号之间不能出现单引号,即使前面有反斜杠。
所以 bash 不会理解:
alias x='y \'z '
但是,如果用双引号括起来,则可以这样做:
alias x="echo \'y "
> x
> 'y
我可以确认在单引号字符串中使用 '\''
作为单引号在 Bash 中确实有效,并且可以以与线程中前面的“粘合”参数相同的方式解释它。假设我们有一个带引号的字符串:'A '\''B'\'' C'
(这里所有的引号都是单引号)。如果它被传递给 echo,它会打印以下内容:A 'B' C
。在每个 '\''
中,第一个引号关闭当前的单引号字符串,接下来的 \'
将单引号粘合到前一个字符串(\'
是一种指定单引号而不启动带引号的字符串的方法),并且最后一个引号打开另一个单引号字符串。
alias something='A '\''B'\'' C'
确实导致 something
成为单个字符串,因此即使赋值的右侧在技术上不是单个字符串,我认为这并不重要。
'A ' + ' + 'B' + ' + ' C'
。换句话说,在单引号字符串中插入单引号字符的解决方案应该允许我自己创建这样的字符串并打印它。但是,此解决方案在这种情况下不起作用。 STR='\''; echo $STR
。按照设计,BASH 并没有真正允许这样做。
'\''
适用于 bash。您能否指出 gnu.org/software/bash/manual/bashref.html 的哪些部分指定了这种行为?
两个版本都可以使用,或者通过使用转义的单引号字符 (\') 进行连接,或者通过将单引号字符括在双引号 ("'") 中进行连接。
问题的作者没有注意到在他最后一次转义尝试的末尾有一个额外的单引号 ('):
alias rxvt='urxvt -fg'\''#111111'\'' -bg '\''#111111'\''
│ │┊┊| │┊┊│ │┊┊│ │┊┊│
└─STRING──┘┊┊└─STRIN─┘┊┊└─STR─┘┊┊└─STRIN─┘┊┊│
┊┊ ┊┊ ┊┊ ┊┊│
┊┊ ┊┊ ┊┊ ┊┊│
└┴─────────┴┴───┰───┴┴─────────┴┘│
All escaped single quotes │
│
?
正如您在之前的 ASCII/Unicode 艺术作品中看到的那样,最后一个转义的单引号 (\') 后面跟着一个不必要的单引号 (')。使用 Notepad++ 中的语法高亮显示非常有用。
对于像下面这样的另一个示例也是如此:
alias rc='sed '"'"':a;N;$!ba;s/\n/, /g'"'"
alias rc='sed '\'':a;N;$!ba;s/\n/, /g'\'
这两个漂亮的别名实例以非常复杂和模糊的方式显示了如何排列文件。也就是说,从一个包含很多行的文件中,您只会得到一行,其中前几行的内容之间有逗号和空格。为了理解前面的评论,下面是一个例子:
$ cat Little_Commas.TXT
201737194
201802699
201835214
$ rc Little_Commas.TXT
201737194, 201802699, 201835214
在 shell 中转义引号的简单示例:
$ echo 'abc'\''abc'
abc'abc
$ echo "abc"\""abc"
abc"abc
它是通过完成已经打开的一个 ('
),放置转义的一个 (\'
),然后打开另一个 ('
) 来完成的。此语法适用于所有命令。这与第一个答案非常相似。
我没有具体解决引用问题,因为,有时候,考虑一种替代方法是合理的。
rxvt() { urxvt -fg "#${1:-000000}" -bg "#${2:-FFFFFF}"; }
然后您可以将其称为:
rxvt 123456 654321
这个想法是你现在可以在不关心引号的情况下给它取别名:
alias rxvt='rxvt 123456 654321'
或者,如果您出于某种原因需要在所有调用中包含 #
:
rxvt() { urxvt -fg "${1:-#000000}" -bg "${2:-#FFFFFF}"; }
然后您可以将其称为:
rxvt '#123456' '#654321'
那么,当然,别名是:
alias rxvt="rxvt '#123456' '#654321'"
(哎呀,我想我确实提到了引用:)
我只是使用 shell 代码.. 例如 \x27
或 \\x22
适用。没有麻烦,真的。
x27
(在 Centos 6.6 上)
echo -e "\x27 \\x22"
打印 ' "
由于不能将单引号放在单引号字符串中,因此最简单且最易读的选项是使用 HEREDOC 字符串
command=$(cat <<'COMMAND'
urxvt -fg '#111111' -bg '#111111'
COMMAND
)
alias rxvt=$command
在上面的代码中,HEREDOC 被发送到 cat
命令,并通过命令替换符号 $(..)
将其输出分配给变量
需要在 HEREDOC 周围加上单引号,因为它在 $()
内
dash
,它是 Ubuntu upstart 脚本和其他地方的默认 shell。
如何使用十六进制和八进制字符转义单引号 (') 和双引号 (")
如果使用像 echo
这样的东西,我会遇到一些非常复杂、非常奇怪和难以逃避(想想:非常嵌套)的情况,我唯一能做的就是使用八进制或十六进制代码!
以下是一些基本示例,只是为了演示它是如何工作的:
1. 单引号示例,其中 ' 用十六进制 \x27 或八进制 \047(其对应的 ASCII 码)转义:
hex \x27 echo -e "让\x27s 编码!" # OR echo -e '让\x27s 开始编码!'结果:让我们开始编码吧!八进制 \047 echo -e "让\047s 编码!" # OR echo -e 'Let\047s 开始编码!'结果:让我们开始编码吧!
2. 双引号示例,其中 " 用十六进制 \x22 或八进制 \042 (其对应的 ASCII 码)进行转义。
注意:bash
是疯了! Sometimes even the !
char has special meaning,并且必须从双引号中删除,然后转义 "like this"\!
或完全放在单引号 'like this!'
内,而不是放在双引号内。
# 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
或者:
$ echo -e "She said, \x22Let\x27s get coding!\x22"
bash: !\x22: event not found
另一种选择:这允许在同一个 bash 字符串中混合扩展和非扩展
这是另一种转义技术的另一个演示。
首先,阅读 the main answer by @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)")'
注意:对于我有 in my ~/.bash_aliases
file somewhere around here 的 gs_set_title
bash 函数,请参阅 my other answer here。
参考:
https://en.wikipedia.org/wiki/ASCII#Printable_characters https://serverfault.com/questions/208265/what-is-bash-event-not-found/208266#208266 另见我的其他答案:如何使用 echo 写非 ASCII 字符?
!
点。 ssh server "awk 'del=(a&&a--) {print; da=\!a} $0~pattern{if (da) {print "--"; da=0} a=A; if (B) {for(i=NR; i<B+NR; i++) if((i%B) in b) print b[i%B]} {print; da=1}} (B) {if (del) delete b[NR%B]; else b[NR%B]=$0}' B=5 A=2 pattern=Successful file"
恕我直言,真正的答案是您不能在单引号字符串中转义单引号。
不可能。
如果我们假设我们正在使用 bash。
从bash手册...
Enclosing characters in single quotes preserves the literal value of each
character within the quotes. A single quote may not occur
between single quotes, even when preceded by a backslash.
您需要使用其他字符串转义机制之一“或\
alias
没有什么神奇的地方要求它使用单引号。
以下两项均在 bash 中工作。
alias rxvt="urxvt -fg '#111111' -bg '#111111'"
alias rxvt=urxvt\ -fg\ \'#111111\'\ -bg\ \'#111111\'
后者使用 \ 来转义空格字符。
需要单引号的 #111111 也没有什么神奇之处。
以下选项实现了与其他两个选项相同的结果,因为 rxvt 别名按预期工作。
alias rxvt='urxvt -fg "#111111" -bg "#111111"'
alias rxvt="urxvt -fg \"#111111\" -bg \"#111111\""
也可以直接逃避麻烦的#
alias rxvt="urxvt -fg \#111111 -bg \#111111"
这些答案中的大多数都针对您要询问的特定案例。我和朋友开发了一种通用方法,允许任意引用,以防您需要通过多层 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
}
这正是它所说的:它单独引用每个参数(当然是在 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 引用 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
。第二个示例说明了类似的通配问题。实际上,所有 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 引用,并且不对 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
等等
考虑到 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
vagrant ssh -c {single-arg} guest
。 {single-arg}
需要被视为单个 arg,因为 vagrant 将其后的下一个 arg 作为来宾名称。顺序不能更改。但是我需要在 {single-arg}
中传递一个命令及其参数。所以我用你的 quote_args()
引用命令及其参数,并在结果周围加上双引号,它就像一个魅力:vagrant ssh -c "'command' 'arg 1 with blanks' 'arg 2'" guest
。谢谢!!!
在给定的示例中,简单地使用双引号而不是单引号作为外部转义机制:
alias rxvt="urxvt -fg '#111111' -bg '#111111'"
这种方法适用于您只想将固定字符串传递给命令的许多情况:只需检查 shell 如何通过 echo
解释双引号字符串,并在必要时使用反斜杠转义字符。
在示例中,您会看到双引号足以保护字符串:
$ echo "urxvt -fg '#111111' -bg '#111111'"
urxvt -fg '#111111' -bg '#111111'
这是对上面提到的唯一正确答案的详细说明:
有时我会在 ssh 上使用 rsync 进行下载,并且必须使用 ' 转义文件名两次! (天哪!)一次用于 bash,一次用于 ssh。交替引用分隔符的相同原理在这里起作用。
例如,假设我们想要获得:Louis Theroux 的 LA Stories ...
首先,将 Louis Theroux 括在 bash 的单引号和 ssh 的双引号中:'"Louis Theroux"' 然后使用单引号转义双引号 '"' 使用双引号转义撇号 "'" 然后重复 #2 , 使用单引号转义双引号 '"' 然后将 LA Stories 括在 bash 的单引号和 ssh 的双引号中:'"LA Stories"'
看哪!你结束了这个:
rsync -ave ssh '"Louis Theroux"''"'"'"'"''"s LA Stories"'
对于一个小家伙来说,这是一项非常艰巨的工作——但是你去吧
显然,简单地用双引号括起来会更容易,但其中的挑战在哪里?这是仅使用单引号的答案。我使用变量而不是 alias
,这样打印证明更容易,但这与使用 alias
相同。
$ 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
(这与阿德里安的答案基本相同,但我觉得这可以更好地解释它。他的答案最后还有 2 个多余的单引号。)
'\''
方法,而不是人类通常更难阅读的 '"'"'
方法。
除了@JasonWoof 完美答案,我想展示我如何解决相关问题
在我的情况下,用 '\''
编码单引号并不总是足够的,例如,如果一个字符串必须用单引号引起来,但引号的总数会导致奇数
#!/bin/bash
# no closing quote
string='alecxs\'solution'
# this works for string
string="alecxs'solution"
string=alecxs\'solution
string='alecxs'\''solution'
假设字符串是文件名,我们需要将带引号的文件名保存在列表中(如 stat -c%N ./* > list)
echo "'$string'" > "$string"
cat "$string"
但处理此列表将失败(取决于字符串总共包含多少引号)
while read file
do
ls -l "$file"
eval ls -l "$file"
done < "$string"
解决方法:使用字符串操作对引号进行编码
string="${string//$'\047'/\'\$\'\\\\047\'\'}"
# result
echo "$string"
现在它起作用了,因为报价总是平衡的
echo "'$string'" > list
while read file
do
ls -l "$file"
eval ls -l "$file"
done < list
希望在遇到类似问题时这会有所帮助
'$'\047''
或 '$'\\047''
作为 '\''
的替代品
需要一个最小的答案,这样人们就可以不用花费很多时间就可以开始工作,因为我不得不筛选那些雄辩的人。
没有办法转义单引号或单引号内的任何其他内容。
以下是,也许令人惊讶的是,一个完整的命令:
$ echo '\'
其输出是:
\
甚至对于 bash 的长期用户来说,反斜杠在单引号内没有任何意义。也没有别的。
另一种解决嵌套引用层数过多问题的方法:
您正试图将太多东西塞进太小的空间,因此请使用 bash 函数。
问题是你试图有太多的嵌套级别,而基本的别名技术还不够强大,无法容纳。使用这样的 bash 函数来实现,这样单引号、双引号反引号和传入的参数都可以正常处理,就像我们期望的那样:
lets_do_some_stuff() {
tmp=$1 #keep a passed in parameter.
run_your_program $@ #use all your passed parameters.
echo -e '\n-------------' #use your single quotes.
echo `date` #use your back ticks.
echo -e "\n-------------" #use your double quotes.
}
alias foobarbaz=lets_do_some_stuff
然后,您可以使用 $1 和 $2 变量以及单引号、双引号和反引号,而不必担心别名函数会破坏它们的完整性。
该程序打印:
el@defiant ~/code $ foobarbaz alien Dyson ring detected @grid 10385
alien Dyson ring detected @grid 10385
-------------
Mon Oct 26 20:30:14 EDT 2015
-------------
shell_escape () {
printf '%s' "'${1//\'/\'\\\'\'}'"
}
实现说明:
双引号,因此我们可以轻松地输出包装单引号并使用 ${...} 语法
bash 的搜索和替换看起来像:${varname//search/replacement}
我们将 ' 替换为 '\''
'\'' 编码单个 ' 像这样:' 结束单引号 \' 编码一个 ' (需要反斜杠,因为我们不在引号内) ' 再次开始单引号 bash 自动连接字符串,中间没有空格
' 结束单引号
\' 编码一个 ' (需要反斜杠,因为我们不在引号内)
' 再次开始单引号
bash 自动连接字符串之间没有空格
在每个 \ 和 ' 之前都有一个 \ 因为这是 ${...//.../...} 的转义规则。
string="That's "'#@$*&^`(@#'
echo "original: $string"
echo "encoded: $(shell_escape "$string")"
echo "expanded: $(bash -c "echo $(shell_escape "$string")")"
PS 始终编码为单引号字符串,因为它们比双引号字符串简单得多。
这是我的两分钱——如果一个人想要sh
便携,而不仅仅是bash
专用(不过,解决方案效率不是很高,因为它启动了一个外部程序——sed
) :
把它放在你的 PATH 某处的 quote.sh (或只是引用)中:
# this works with standard input (stdin) quote() { echo -n "'" ; sed 's/\(['"'"']['"'"']*\)/'"'"'"\1"'"'"'/g' ; echo -n "'" } case "$1" in -) quote ;; *) echo "usage: cat ... | quote - # single-quotes input for Bourne shell" 2>&1 ;; esac
一个例子:
$ echo -n "G'day, mate!" | ./quote.sh - 'G'"'"'day, mate!'
而且,当然,这会转换回来:
$ echo 'G'"'"'day, mate!' G'day, mate!
解释:基本上我们必须用引号 '
将输入括起来,然后用这个微型怪物替换其中的任何单引号:'"'"'
(以配对 '
结束开头引号, 通过用双引号包裹找到的单引号来转义 - "'"
,然后最后发出一个新的开始单引号 '
,或伪符号:' + "'" + ' == '"'"'
)
一种标准方法是将 sed
与以下替换命令一起使用:
s/\(['][']*\)/'"\1"'/g
不过,一个小问题是,为了在 shell 中使用它,需要在 sed 表达式本身中转义所有这些单引号字符——这会导致类似
sed 's/\(['"'"']['"'"']*\)/'"'"'"\1"'"'"'/g'
(构建此结果的一种好方法是将原始表达式 s/\(['][']*\)/'"\1"'/g
提供给 Kyle Rose 或 George V. Reilly 的脚本)。
最后,期望输入来自 stdin
是有道理的——因为通过命令行参数传递它可能已经太麻烦了。
(哦,也许我们想添加一个小帮助消息,这样当有人运行它时脚本不会挂起,因为 ./quote.sh --help
想知道它做了什么。)
如果您在 Python 2 或 Python 3 中生成 shell 字符串,以下内容可能有助于引用参数:
#!/usr/bin/env python
from __future__ import print_function
try: # py3
from shlex import quote as shlex_quote
except ImportError: # py2
from pipes import quote as shlex_quote
s = """foo ain't "bad" so there!"""
print(s)
print(" ".join([shlex_quote(t) for t in s.split()]))
这将输出:
foo ain't "bad" so there!
foo 'ain'"'"'t' '"bad"' so 'there!'
print(shlex_quote(r"..<nasty string>..."))
如果你安装了 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
)。
这个功能:
quote ()
{
local quoted=${1//\'/\'\\\'\'};
printf "'%s'" "$quoted"
}
允许在 '
中引用 '
。像这样使用:
$ quote "urxvt -fg '#111111' -bg '#111111'"
'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''
如果要引用的行变得更复杂,例如双引号和单引号混合,那么在变量中获取要引用的字符串可能会变得非常棘手。当出现这种情况时,请在脚本中写下您需要引用的确切行(类似于此)。
#!/bin/bash
quote ()
{
local quoted=${1//\'/\'\\\'\'};
printf "'%s'" "$quoted"
}
while read line; do
quote "$line"
done <<-\_lines_to_quote_
urxvt -fg '#111111' -bg '#111111'
Louis Theroux's LA Stories
'single quote phrase' "double quote phrase"
_lines_to_quote_
将输出:
'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''
'Louis Theroux'\''s LA Stories'
''\''single quote phrase'\'' "double quote phrase"'
单引号内所有正确引用的字符串。
这是另一个解决方案。该函数将接受一个参数并使用单引号字符适当地引用它,正如上面投票答案所解释的那样:
single_quote() {
local quoted="'"
local i=0
while [ $i -lt ${#1} ]; do
local ch="${1:i:1}"
if [[ "$ch" != "'" ]]; then
quoted="$quoted$ch"
else
local single_quotes="'"
local j=1
while [ $j -lt ${#1} ] && [[ "${1:i+j:1}" == "'" ]]; do
single_quotes="$single_quotes'"
((j++))
done
quoted="$quoted'\"$single_quotes\"'"
((i+=j-1))
fi
((i++))
done
echo "$quoted'"
}
所以,你可以这样使用它:
single_quote "1 2 '3'"
'1 2 '"'"'3'"'"''
x="this text is quoted: 'hello'"
eval "echo $(single_quote "$x")"
this text is quoted: 'hello'
不定期副业成功案例分享
alias splitpath='echo $PATH | awk -F : '"'"'{print "PATH is set to"} {for (i=1;i<=NF;i++) {print "["i"]",$i}}'"'"
别名字符串中同时存在单引号和双引号时有效!alias serve_this_dir='ruby -rrack -e "include Rack;Handler::Thin.run Builder.new{run Directory.new'"'"''"'"'}"'
'\''
在大多数情况下都比'"'"'
更具可读性。事实上,前者在单引号字符串中几乎总是明显不同,因此只需将其在语义上映射到“它是一个转义的引号”含义,就像在双引号字符串中对\"
所做的那样。而后者混合成一行报价单,在许多情况下需要仔细检查才能正确区分。