如何在单引号string中转义单引号?
比方说,你有一个bash alias
如:
alias rxvt='urxvt'
这工作正常。
然而:
alias rxvt='urxvt -fg '#111111' -bg '#111111''
将不会工作,也不会:
alias rxvt='urxvt -fg \'#111111\' -bg \'#111111\''
那么一旦你已经逃脱了引号,你怎么最终匹配一个string中的开头和结尾的引号呢?
alias rxvt='urxvt -fg'\''#111111'\'' -bg '\''#111111'\''
看起来很笨拙,虽然它会代表相同的string,如果你被允许像这样连接它们。
如果您真的想在最外层使用单引号,请记住您可以粘合两种引用。 例:
alias rxvt='urxvt -fg '"'"'#111111'"'"' -bg '"'"'#111111'"'" # ^^^^^ ^^^^^ ^^^^^ ^^^^ # 12345 12345 12345 1234
解释如何'"'"'
被解释为'
: '
-
'
结束使用单引号的第一个引号。 -
"
用双引号开始第二个引号。 -
'
引用的字符。 -
"
结束第二个引号,用双引号。 -
'
使用单引号开始第三个引号。
如果不在(1)和(2)之间,或者在(4)和(5)之间放置任何空格,shell将把该string解释为一个长整数的单词。
我总是把每个embedded式单引号replace为: '\''
(即引号反斜杠引号引用),它将closuresstring,附加一个转义的单引号并重新打开string。 我经常在我的Perl脚本中调用“quotify”函数来为我做这个。 步骤是:
s/'/'\\''/g # Handle each embedded quote $_ = qq['$_']; # Surround result with single quotes.
这几乎照顾所有情况。
当您将eval
引入到您的shell脚本时,生活变得更加有趣。 你基本上不得不重新引用所有的东西!
例如,创build一个名为quotify的Perl脚本,其中包含上述语句:
#!/usr/bin/perl -pl s/'/'\\''/g; $_ = qq['$_'];
然后用它来生成一个正确引用的string:
$ 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 'alias rxvt='\''urxvt -fg '\''\'\'''\''#111111'\''\'\'''\'' -bg '\''\'\'''\''#111111'\''\'\'''\'''\'''
对于Bash这应该工作:
alias rxvt=$'urxvt -fg \'#111111\' -bg \'#111111\''
从man bash
:
$'string'forms的单词被特殊处理。 字扩展为string,反斜杠转义字符被ANSI C标准指定。 反斜杠转义序列(如果存在)按如下方式解码:
\\ backslash \' single quote \" double quote \n new line ...
看例子:
> echo $'aa\'bb' aa'bb
有关更多详细信息,请参阅引号和转义:ANSI C像 bash-hackers.org wiki上的string 。
我没有看到他的博客上的条目(请链接?),但根据GNU参考手册 :
用单引号括起来的字符(''')保留引号中每个字符的字面值。 单引号之间可能不会出现单引号,即使前面有反斜杠也是如此。
所以bash不会理解:
alias x='y \'z '
不过,如果用双引号环绕,则可以这样做:
alias x="echo \'y " > x > 'y
我可以确认在单引号string中使用'\''
作为单引号在Bash中起作用,并且可以像在线程中早先的“粘合”参数那样解释。 假设我们有一个带引号的string: 'A '\''B'\'' C'
(这里的所有引号都是单引号)。 如果它传递给回显,它将打印以下内容: A 'B' C
。 在每个'\''
,第一个引号closures了当前的单引号string,下面的\'
将一个单引号粘合到前一个string中( \'
是一种指定单引号而不用引用引号string的方式),最后一个报价打开另一个单引号的string。
在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
。 没有麻烦,永远真的。
两个版本都可以通过使用转义单引号字符(\')进行连接,也可以通过将单引号字符用双引号(“'”)括起来进行连接。
这个问题的作者没有注意到他最后一次逃跑的尝试结束时还有一个额外的单引号('):
alias rxvt='urxvt -fg'\''#111111'\'' -bg '\''#111111'\'' |
|
|
|
| +-STRING–+||+-STRIN-+||+-STR-+||+-STRIN-+
|| || ||
|| || ||
++———++——-++———++| All escaped single quotes | | ?
正如你在前一个漂亮的ASCII艺术中看到的那样,最后一个转义的单引号(\')后面是一个不必要的单引号(')。 使用记事本++中的语法荧光笔可以certificate非常有帮助。
另外一个例子也是如此:
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
由于不能将单引号放在单引号string中,最简单和最易读的选项是使用HEREDOCstring
command=$(cat <<'COMMAND' urxvt -fg '#111111' -bg '#111111' COMMAND ) alias rxvt=$command
在上面的代码中,HEREDOC被发送到cat
命令,并且通过命令replace符号$(..)
将其输出分配给variables
在HEREDOC周围放一个单引号是需要的,因为它在$()
恕我直言,真正的答案是,你不能逃避单引号string内的单引号。
不可能。
如果我们假设我们正在使用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.
您需要使用其他string转义机制之一“或\
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"
在给定的例子中,简单地使用双引号而不是单引号作为外部转义机制:
alias rxvt="urxvt -fg '#111111' -bg '#111111'"
这种方法适用于很多情况,只要将一个固定string传递给一个命令即可:只要检查一下shell如何通过echo
来解释双引号string,并在必要时使用反斜杠来转义字符。
在这个例子中,你会看到双引号足以保护string:
$ echo "urxvt -fg '#111111' -bg '#111111'" urxvt -fg '#111111' -bg '#111111'
这些答案大部分都是针对你所问的具体情况。 有一个普遍的方法,我和朋友已经开发了允许任意引用的情况,以防需要通过多层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
。
简而言之就是这个想法。 你可以用这个做一些非常复杂的事情,但是你必须小心评估的顺序和引用哪些子string。 例如,下面做了错误的事情(对于“错误”的一些定义):
$ (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
分成两个单独的命令, quote_args cd /
和pwd 1>&2
,所以当pwd
命令执行时,CWD仍然是/tmp
。 第二个例子说明了类似的问题globbing。 事实上,所有bash扩展都会出现同样的基本问题。 这里的问题是命令replace不是函数调用:它实际上是评估一个bash脚本,并将其输出作为另一个bash脚本的一部分。
如果您试图简单地将shell运算符转义出来,则会失败,因为传递给bash -c
的结果string只是一串单独引用的string,不会被解释为运算符,如果您回显将会被传递给bash的string:
$ (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
input,这意味着它们需要在$(quote_args ...)
命令replace之外。
因此,您最需要做的就是在命令replace的时候对命令中不打算展开的命令的每个单词进行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
一旦你这样做,整个string是公平的游戏进一步引用任意级别的评价:
$ 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引用,但是在编写脚本时要谨记任意input的关键点是你想引用所有你不是绝对的肯定不需要引用,因为你永远不会知道用户何时会抛出一个Robert'; rm -rf /
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
下面是上面引用的一个真正的答案的详细说明:
有时我会通过ssh使用rsync进行下载,并且必须使用'in TWICE! (OMG!)一次用于bash,一次用于ssh。 交替引用分隔符的原则在这里工作。
例如,假设我们想要得到:Louis Theroux的LA故事…
- 首先你把Louis Theroux用单引号括起来,用双引号加上ssh:“Louis Theroux”
- 然后你用单引号转义双引号'''
- 使用双引号来逃避撇号“'”
- 然后重复#2,使用单引号转义双引号'“'
- 然后用单引号括起LA故事和ssh的双引号:“LA Stories”
瞧! 你结束了这个:
rsync -ave ssh '"Louis Theroux"''"'"'"'"''"s LA Stories"'
这是一个很小的工作很多' – 但是你去了
解决嵌套报价层数太多问题的另一种方法是:
你试图把太多的东西塞进太小的空间,所以使用bash函数。
问题是你试图有太多的嵌套层次,而基本的别名技术不够强大。 使用这样的bash函数来使它成为单引号,双引号返回蜱和parameter passing都是正常的,正如我们所期望的那样:
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和$ 2variables以及单引号,双引号和后退标记,而不用担心别名函数破坏其完整性。
这个程序打印:
el@defiant ~/code $ foobarbaz alien Dyson ring detected @grid 10385 alien Dyson ring detected @grid 10385 ------------- Mon Oct 26 20:30:14 EDT 2015 -------------
这个function:
quote () { local quoted=${1//\'/\'\\\'\'}; printf "'%s'" "$quoted" }
允许引用'
内部'
。 这样使用:
$ quote "urxvt -fg '#111111' -bg '#111111'" 'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''
如果要引用的行变得更加复杂,就像双引号和单引号混合在一起,将string引用到variables中可能会变得非常棘手。 当出现这种情况时,请在脚本内写出需要引用的确切行(与此类似)。
#!/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"'
所有正确引号的string在单引号内。
这是另一个解决scheme。 这个函数将采用一个单独的参数,并使用单引号字符进行适当的引用,正如上面的投票答案所解释的那样:
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'
如果您在Python 2或Python 3中生成shellstring,以下内容可能有助于引用参数:
#!/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!'