如何在单引号字符串中转义单引号
Posted
技术标签:
【中文标题】如何在单引号字符串中转义单引号【英文标题】:How to escape single quotes within single quoted strings 【发布时间】:2010-11-18 00:23:31 【问题描述】:假设你有一个 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'\''
看起来很笨拙,尽管如果允许您像这样连接它们,它会代表相同的字符串。
【问题讨论】:
你知道别名不需要使用单引号吗?双引号更容易。 另见:Difference between single and double quotes in Bash。 嵌套双引号是可转义的,"\""
,因此应尽可能优先使用这些双引号而不是 @liori 的答案。
双引号的行为与 *nix 中的单引号完全不同(包括 Bash 和 Perl 等相关工具),因此在单引号出现问题时替换双引号并不是一个好的解决方案。双引号指定 $... 变量在执行前被替换,而单引号指定 $... 被逐字处理。
如果您在想,我使用了双引号,但它仍然不起作用,请再次获取您的脚本。
【参考方案1】:
如何使用 hex 和 octal 字符转义单引号 ('
) 和双引号 ("
)
如果使用echo
之类的东西,我会遇到一些非常复杂、非常奇怪且难以逃避(想想:非常嵌套)的情况,我唯一能做的就是使用八进制或十六进制代码!
这里有一些基本的例子来演示它是如何工作的:
1。单引号示例,其中 '
用 hex \x27
或 octal \047
(其对应的 ASCII code)转义:
十六进制\x27
echo -e "Let\x27s get coding!"
# OR
echo -e 'Let\x27s get coding!'
结果:
Let's get coding!
八进制\047
echo -e "Let\047s get coding!"
# OR
echo -e 'Let\047s get coding!'
结果:
Let's get coding!
2。双引号示例,其中"
用hex \x22
或八进制 \042
(其对应的ASCII code)转义。
注意:bash
疯了! Sometimes even the !
char has special meaning,并且必须从双引号中删除,然后转义"like this"\!
,或者完全放在单引号'like this!'
内,而不是放在双引号内。
# 1. hex; 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; 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
# 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)")'
注意:对于我的gs_set_title
bash 函数,我有in my ~/.bash_aliases
file somewhere around here,请参阅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
另请参阅我的其他答案:How do I write non-ASCII characters using echo?。
【讨论】:
你能帮忙吗?不知道如何处理这里的!
点。 ssh server "awk 'del=(a&&a--) print; da=\!a $0~patternif (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"
@cokedude,尝试提出一个新问题。在此处粘贴指向您的新问题的链接,以便我帮助您解决问题。
在常规 stackoverlow 或 Unix *** 中发布更好吗?
@cokedude,我认为两者都可以。我可能只是做常规的 ***。但是,请务必详细描述您的问题,确保您发布的内容可供任何人运行。解释你做了什么,你看到了什么输出,以及你期望看到或想要发生什么。即使您做了所有这些并使其完美无缺,也要期待一些反对意见。请务必在发布之前搜索已经回答的现有问题。如果您的问题在结束前持续超过 10 分钟,则认为它是成功的。不幸的是,这就是这个网站的本质。【参考方案2】:
由于 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 中工作(有一些变化)。
【讨论】:
可以使用,但是这里的单引号字符串不是真正的单引号,这个字符串上的内容可能会被shell解释:echo $'foo\'b!ar'
=>!ar': event not found
在我的机器上 > echo $BASH_VERSION
4.2.47(1)-release
> echo $'foo\'b!ar'
foo'b!ar
是的,这就是“可能”的原因,我在 Red hat 6.4 上安装了它,当然是旧的 bash 版本。
Bash ChangeLog 包含许多与$'
相关的错误修复,因此可能最简单的方法是自己在旧系统上尝试。
这确实会将所有 C 样式的序列引入 bash 行,因此某些在 bash 上可以正常工作的字符序列可能会停止工作,因为它们变成了 C 样式的序列。通常通过添加额外的 \
来转义 C 样式序列很容易解决。示例:alias foo=$'echo \1'
与 alias boo='echo \1'
不同【参考方案3】:
除了@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''
代替 '\''
取决于 shell【参考方案4】:
这是我的两分钱——如果一个人想成为sh
-portable,而不仅仅是bash
-specific(不过,解决方案效率不高,因为它启动了一个外部程序——@ 987654323@):
quote.sh
(或只是quote
)你PATH
的某个地方:
# 这适用于标准输入(stdin)
引用()
回声 -n "'" ;
sed 's/\(['"'"']['"'"']*\)/'"'"'"\1"'"'"'/g' ;
回声 -n "'"
案例“$1”在
-) 引用 ;;
*) echo "usage: cat ... | quote - # Bourne shell 的单引号输入" 2>&1 ;;
经社理事会
一个例子:
$ echo -n "G'day,伙计!" | ./quote.sh - 'G'"'"'今天,伙计!当然,这会转换回来:
$ echo 'G'"'"'今天,伙计!' G'day,伙计!解释:基本上我们必须用引号 '
将输入括起来,然后用这个微型怪物替换其中的任何单引号:'"'"'
(以配对结束开头引号'
,将找到的单引号用双引号包裹起来——"'"
,然后最后发出一个新的开头单引号'
,或伪符号:' + "'" + ' == '"'"'
)
执行此操作的一种标准方法是将sed
与以下替换命令一起使用:
s/\(['][']*\)/'"\1"'/g
不过,一个小问题是,为了在 shell 中使用它,需要在 sed 表达式本身中转义所有这些单引号字符——这会导致类似
sed 's/\(['"'"']['"'"']*\)/'"'"'"\1"'"'"'/g'
(构建此结果的一种好方法是将原始表达式 s/\(['][']*\)/'"\1"'/g
提供给 Kyle Rose 或 George V. Reilly 的脚本)。
最后,期望输入来自stdin
是有道理的——因为通过命令行参数传递它可能已经太麻烦了。
(哦,也许我们想添加一个小帮助消息,这样当有人以./quote.sh --help
想知道它的作用时,脚本不会挂起。)
【讨论】:
【参考方案5】:如果您安装了 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
)。
【讨论】:
【参考方案6】:shell_escape ()
printf '%s' "'$1//\'/\'\\\'\''"
实现说明:
双引号,因此我们可以轻松输出环绕单引号并使用$...
语法
bash 的搜索和替换看起来像:$varname//search/replacement
我们将 '
替换为 '\''
'\''
像这样对单个 '
进行编码:
'
结束单引号
\'
编码'
(需要反斜杠,因为我们不在引号内)
'
再次启动单引号
bash 自动连接字符串,中间没有空格
在每个\
和'
之前都有一个\
,因为这是$...//.../...
的转义规则。
string="That's "'#@$*&^`(@#'
echo "original: $string"
echo "encoded: $(shell_escape "$string")"
echo "expanded: $(bash -c "echo $(shell_escape "$string")")"
附:始终编码为单引号字符串,因为它们比双引号字符串简单得多。
【讨论】:
【参考方案7】:两个版本都可以使用,或者通过使用转义的单引号字符 (\') 进行连接,或者通过将单引号字符括在双引号 ("'") 中进行连接。
问题的作者没有注意到他最后一次转义尝试的末尾有一个额外的单引号('):
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
【讨论】:
支持 ASCII 表插图 :) 你是如何生成 unicode 艺术的?很漂亮!【参考方案8】:我总是将每个嵌入的单引号替换为以下序列:'\''
(即:引号反斜杠引号),它关闭字符串,附加一个转义的单引号并重新打开字符串。
我经常在我的 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'\''\'\'''\'''\'''
【讨论】:
但这不是 perl。正如史蒂夫 B 上面指出的那样,通过他对“gnu 参考手册”的引用,您无法在 bash 中在相同类型的引用中转义引号。事实上,不需要在备用引号中转义它们,例如"'" 是一个有效的单引号字符串,而 '"' 是一个有效的双引号字符串,不需要任何转义。 @nicerobot:我添加了一个示例,表明:1)我不尝试在相同类型的引号中转义引号,2)也不在替代引号中,3)使用 Perl自动生成包含嵌入引号的有效 bash 字符串的过程 第一段本身就是我正在寻找的答案。 这也是 bash 所做的,输入set -x
和 echo "here's a string"
,你会看到 bash 执行 echo 'here'\''s a string'
。 (set +x
恢复正常行为)【参考方案9】:
显然,简单地用双引号括起来会更容易,但其中的挑战在哪里?这是仅使用单引号的答案。我使用变量而不是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 个多余的单引号。)
【讨论】:
+1 用于使用'\''
方法,我推荐使用 '"'"'
方法,这通常更难人类阅读。【参考方案10】:
如果您在 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>..."))
【参考方案11】:
我并没有具体解决引用问题,因为有时候考虑替代方法是合理的。
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'"
(哎呀,我想我确实提到了引用:)
【讨论】:
我试图把一些东西放在单引号内,而双引号又是单引号。哎呀。感谢您对“尝试不同的方法”的回答。这造成了不同。 我迟到了 5 年,但你没有在你的最后一个别名中缺少单引号吗? @Julien 我没发现问题 ;-)【参考方案12】:如果您真的想在最外层使用单引号,请记住您可以将两种引号都粘合起来。示例:
alias rxvt='urxvt -fg '"'"'#111111'"'"' -bg '"'"'#111111'"'"
# ^^^^^ ^^^^^ ^^^^^ ^^^^
# 12345 12345 12345 1234
解释'"'"'
如何被解释为'
:
'
结束使用单引号的第一个引号。
"
开始第二个引号,使用双引号。
'
引用的字符。
"
结束第二个引号,使用双引号。
'
开始第三个引号,使用单引号。
如果您没有在 (1) 和 (2) 之间或 (4) 和 (5) 之间放置任何空格,shell 会将该字符串解释为一个长单词。
【讨论】:
alias splitpath='echo $PATH | awk -F : '"'"'print "PATH is set to" for (i=1;i<=NF;i++) print "["i"]",$i'"'"
别名字符串中同时存在单引号和双引号时有效!
我的解释:bash 隐式连接不同引用的字符串表达式。
为我工作,双转义单引号的示例:alias serve_this_dir='ruby -rrack -e "include Rack;Handler::Thin.run Builder.newrun Directory.new'"'"''"'"'"'
当然不是最易读的解决方案。它在不需要的地方过度使用单引号。
我认为'\''
在大多数情况下比'"'"'
更具可读性。事实上,前者在单引号字符串中几乎总是明显不同,因此只需将其在语义上映射到“它是一个转义引号”的含义,就像在双引号字符串中处理 \"
一样。而后者融入一行引号,在许多情况下需要仔细检查才能正确区分。【参考方案13】:
由于不能将单引号放在单引号字符串中,因此最简单且最易读的选项是使用 HEREDOC 字符串
command=$(cat <<'COMMAND'
urxvt -fg '#111111' -bg '#111111'
COMMAND
)
alias rxvt=$command
在上面的代码中,HEREDOC 被发送到cat
命令,并通过命令替换符号$(..)
将其输出分配给一个变量
需要在 HEREDOC 周围加上单引号,因为它在 $()
内
【讨论】:
我希望我之前已经向下滚动了这么远 - 我重新发明了这种方法并来到这里发布它!这比所有其他转义方法更干净、更易读。不是它不适用于某些非 bash shell,例如dash
,它是 Ubuntu upstart 脚本和其他地方的默认 shell。
谢谢!我正在寻找的方法是通过heredoc定义命令并将自动转义命令传递给ssh。 BTW cat
【参考方案14】:
恕我直言,真正的答案是您不能在单引号字符串中转义单引号。
这是不可能的。
如果我们假设我们正在使用 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"
【讨论】:
“真正的答案是你不能在单引号字符串中转义单引号。” 这在技术上是正确的。但是你可以有一个解决方案,它以单引号开头,以单引号结尾,并且中间只包含单引号。 ***.com/a/49063038 不是通过转义,而是通过串联。【参考方案15】:这些答案中的大多数都针对您所询问的具体案例。我和朋友开发了一种通用方法,允许任意引用,以防您需要通过多层 shell 扩展引用 bash 命令,例如,通过 ssh、su -c
、bash -c
等。有一个您需要的核心原语,在本机 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
。谢谢!!!【参考方案16】:
这是另一种解决方案。这个函数将接受一个参数并使用单引号字符适当地引用它,正如上面投票答案所解释的那样:
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'
【讨论】:
【参考方案17】:这个函数:
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"'
单引号内所有正确引用的字符串。
【讨论】:
【参考方案18】:另一种解决嵌套引用层数过多问题的方法:
你试图在太小的空间里塞进太多东西,所以使用 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
-------------
【讨论】:
【参考方案19】:在shell中转义引号的简单示例:
$ echo 'abc'\''abc'
abc'abc
$ echo "abc"\""abc"
abc"abc
完成已打开的一个 ('
),放置转义的一个 (\'
),然后打开另一个 ('
)。此语法适用于所有命令。这与第一个答案非常相似。
【讨论】:
【参考方案20】:我只使用 shell 代码.. 例如\x27
或 \\x22
(如适用)。真的没有麻烦。
【讨论】:
您能举个例子吗?对我来说,它只是打印一个文字x27
(在 Centos 6.6 上)
@WillSheppard echo -e "\x27 \\x22"
打印 ' "
@WillSheppard 和其他人,这里有一堆我刚刚写的例子:***.com/a/65878993/4561887。【参考方案21】:
这里是上面提到的唯一正确答案的详细说明:
有时我会通过 ssh 使用 rsync 进行下载,并且必须两次转义带有 ' 的文件名! (天哪!)一次用于 bash,一次用于 ssh。交替引用分隔符的相同原理在这里起作用。
例如,假设我们想要获得:Louis Theroux 的 LA Stories ...
-
首先将 Louis Theroux 括在 bash 的单引号和 ssh 的双引号中:
'“路易斯·塞鲁斯”'
然后你使用单引号来转义双引号'"'
使用双引号转义撇号“'”
然后重复 #2,使用单引号转义双引号 '"'
然后将 LA Stories 括在 bash 的单引号和 ssh 的双引号中:'"LA Stories"'
看啊!你最终会得到这个:
rsync -ave ssh '"Louis Theroux"''"'"'"'"''"s LA Stories"'
对于一个小'来说,这是一项非常艰巨的工作——但你去吧
【讨论】:
【参考方案22】:在给定的示例中,只是使用双引号而不是单引号作为外部转义机制:
alias rxvt="urxvt -fg '#111111' -bg '#111111'"
这种方法适用于您只想将固定字符串传递给命令的许多情况:只需检查 shell 如何通过echo
解释双引号字符串,并在必要时使用反斜杠转义字符。
在示例中,您会看到双引号足以保护字符串:
$ echo "urxvt -fg '#111111' -bg '#111111'"
urxvt -fg '#111111' -bg '#111111'
【讨论】:
【参考方案23】:我没有看到他博客上的条目(请链接?)但根据gnu reference manual:
用单引号括起来的字符 (''') 保留字面值 引号内的每个字符。一种 单引号之间可能不会出现 单引号,即使前面有 反斜杠。
所以 bash 不会理解:
alias x='y \'z '
但是,如果你用双引号括起来,你可以这样做:
alias x="echo \'y "
> x
> 'y
【讨论】:
muffinresearch.co.uk/archives/2007/01/30/… 正在评估用双引号括起来的内容,因此按照 liori 的建议,只用双引号将单引号括起来似乎是正确的解决方案。 这是问题的实际答案。虽然接受的答案可能会提供解决方案,但从技术上讲,它是在回答一个没有被问到的问题。 马修,问题是关于在单引号内转义单引号。这个答案要求用户改变他们的行为,如果你有使用双引号的障碍(如问题标题所示),这个答案将无济于事。虽然它非常有用(尽管很明显),因此值得一票,但接受的答案解决了 Op 提出的确切问题。 双引号字符串中不需要单引号。【参考方案24】:我可以确认在单引号字符串中使用'\''
作为单引号在 Bash 中确实有效,并且可以以与线程中前面的“粘合”参数相同的方式解释它。假设我们有一个带引号的字符串:'A '\''B'\'' C'
(这里所有的引号都是单引号)。如果它被传递给 echo,它会打印以下内容:A 'B' C
。
在每个'\''
中,第一个引号关闭当前的单引号字符串,下面的\'
将单引号粘合到前一个字符串(\'
是一种指定单引号而不启动带引号的字符串的方法),并且最后一个引号打开另一个单引号字符串。
【讨论】:
这是一种误导,这种语法 '\'' 不会“进入”单引号字符串。在此语句 'A '\''B'\'' C' 中,您将连接 5 个 \ 转义和单引号字符串 @teknopaul 赋值alias something='A '\''B'\'' C'
确实导致 something
成为单个字符串,因此即使赋值的右侧在技术上不是单个字符串,我不认为它很重要。
虽然这在您的示例中有效,但 技术上 并没有为如何在单引号字符串 inside 中插入单引号提供解决方案。你已经解释过了,但是是的,它正在做'A ' + ' + 'B' + ' + ' C'
。换句话说,在单引号字符串中插入单引号字符的解决方案应该允许我自己创建这样的字符串并打印它。但是,此解决方案在这种情况下不起作用。 STR='\''; echo $STR
。按照设计,BASH 并没有真正允许这样做。
@mikhail_b,是的,'\''
适用于 bash。您能否指出gnu.org/software/bash/manual/bashref.html 的哪些部分指定了这种行为?以上是关于如何在单引号字符串中转义单引号的主要内容,如果未能解决你的问题,请参考以下文章