ChatGPT解决这个技术问题 Extra ChatGPT

如何在单引号字符串中转义单引号

假设您有一个 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 的答案。
双引号的行为与 *nix 中的单引号(包括 Bash 和 Perl 等相关工具)中的单引号完全不同,因此在单引号出现问题时替换双引号并不是一个好的解决方案。双引号指定 $... 变量将在执行前被替换,而单引号指定 $... 将按字面意思处理。
如果您正在考虑,我使用了双引号,但它仍然无法正常工作,请再次获取您的脚本。

C
Craig Gidney

如果您真的想在最外层使用单引号,请记住您可以粘合两种引号。例子:

 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.new{run Directory.new'"'"''"'"'}"'
当然不是最易读的解决方案。它在不需要的地方过度使用单引号。
我认为 '\'' 在大多数情况下都比 '"'"' 更具可读性。事实上,前者在单引号字符串中几乎总是明显不同,因此只需将其在语义上映射到“它是一个转义的引号”含义,就像在双引号字符串中对 \" 所做的那样。而后者混合成一行报价单,在许多情况下需要仔细检查才能正确区分。
j
jan-glx

我总是用以下序列替换每个嵌入的单引号:'\''(即:引号反斜杠引号),它关闭字符串,附加一个转义的单引号并重新打开字符串。

我经常在我的 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 -xecho "here's a string",您会看到 bash 执行 echo 'here'\''s a string'。 (set +x 返回正常行为)
1
11 revs, 3 users 96%

由于 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' 不同
G
Gringo Suave

我在他的博客上没有看到该条目(请链接?),但根据 gnu reference manual

将字符括在单引号 (''') 中会保留引号内每个字符的文字值。单引号之间不能出现单引号,即使前面有反斜杠。

所以 bash 不会理解:

alias x='y \'z '

但是,如果用双引号括起来,则可以这样做:

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

正在评估用双引号括起来的内容,因此按照 liori 的建议,只用双引号将单引号括起来似乎是正确的解决方案。
这是问题的实际答案。虽然接受的答案可能会提供解决方案,但它在技术上回答了一个没有被问到的问题。
马修,问题是关于在单引号内转义单引号。这个答案要求用户改变他们的行为,如果你有使用双引号的障碍(如问题标题所示),这个答案将无济于事。虽然它非常有用(尽管很明显),因此值得一票,但接受的答案解决了 Op 提出的确切问题。
@MatthewD.Scholefield:它需要被引用,因为它在别名中。扩展别名时不会有任何双引号。 (不过,最后的空间是不必要的)。
M
Mathias Bynens

我可以确认在单引号字符串中使用 '\'' 作为单引号在 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 的哪些部分指定了这种行为?
l
leftaroundabout

两个版本都可以使用,或者通过使用转义的单引号字符 (\') 进行连接,或者通过将单引号字符括在双引号 ("'") 中进行连接。

问题的作者没有注意到在他最后一次转义尝试的末尾有一个额外的单引号 ('):

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

Upwoted 用于 ASCII 表插图 :)
你是如何生成 unicode 艺术的?很美丽!
k
kenorb

在 shell 中转义引号的简单示例:

$ echo 'abc'\''abc'
abc'abc
$ echo "abc"\""abc"
abc"abc

它是通过完成已经打开的一个 ('),放置转义的一个 (\'),然后打开另一个 (') 来完成的。此语法适用于所有命令。这与第一个答案非常相似。


n
nicerobot

我没有具体解决引用问题,因为,有时候,考虑一种替代方法是合理的。

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 我没发现问题 ;-)
R
Rob Jens

我只是使用 shell 代码.. 例如 \x27\\x22 适用。没有麻烦,真的。


你能举个例子吗?对我来说,它只是打印一个文字 x27 (在 Centos 6.6 上)
@WillSheppard echo -e "\x27 \\x22" 打印 ' "
@WillSheppard 和其他人,这里有一堆我刚刚写的例子:stackoverflow.com/a/65878993/4561887
A
Abbas Gadhia

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

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

alias rxvt=$command

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

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


我希望我之前已经向下滚动了这么远 - 我重新发明了这种方法并来到这里发布它!这比所有其他转义方法更干净、更易读。不是它不适用于某些非 bash shell,例如 dash,它是 Ubuntu upstart 脚本和其他地方的默认 shell。
谢谢!我正在寻找的方法是通过heredoc定义命令并将自动转义命令传递给ssh。顺便说一句,不带引号的 cat <
G
Gabriel Staples

如何使用十六进制和八进制字符转义单引号 (') 和双引号 (")

如果使用像 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 heregs_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"
@cokedude,尝试提出一个新问题。在此处粘贴指向您的新问题的链接,以便我可以帮助您解决问题。
在常规 stackoverlow 或 Unix stackoverflow 中发布更好吗?
@cokedude,我认为两者都可以。我可能只是做常规的 StackOverflow。但是,请务必详细描述您的问题,确保您发布的内容可供任何人运行。解释你做了什么,你看到了什么输出,以及你期望看到或想要发生什么。即使您做了所有这些并使其完美无缺,也要期待一些反对意见。请务必在发布之前搜索已经回答的现有问题。如果您的问题在结束前持续超过 10 分钟,则认为它是成功的。不幸的是,这只是本网站的性质。
t
teknopaul

恕我直言,真正的答案是您不能在单引号字符串中转义单引号。

不可能。

如果我们假设我们正在使用 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"

“真正的答案是你不能在单引号字符串中转义单引号。” 这在技术上是正确的。但是你可以有一个解决方案,以单引号开头,以单引号结尾,中间只包含单引号。 stackoverflow.com/a/49063038
不是通过转义,而是通过串联。
K
Kyle Rose

这些答案中的大多数都针对您要询问的特定案例。我和朋友开发了一种通用方法,允许任意引用,以防您需要通过多层 shell 扩展(例如,通过 ssh、su -cbash -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

等等

考虑到 successsbinpwd 之类的词不需要在 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。谢谢!!!
o
oberlies

在给定的示例中,简单地使用双引号而不是单引号作为外部转义机制:

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

这种方法适用于您只想将固定字符串传递给命令的许多情况:只需检查 shell 如何通过 echo 解释双引号字符串,并在必要时使用反斜杠转义字符。

在示例中,您会看到双引号足以保护字符串:

$ echo "urxvt -fg '#111111' -bg '#111111'"
urxvt -fg '#111111' -bg '#111111'

P
PatchyFog

这是对上面提到的唯一正确答案的详细说明:

有时我会在 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"'

对于一个小家伙来说,这是一项非常艰巨的工作——但是你去吧


w
wisbucky

显然,简单地用双引号括起来会更容易,但其中的挑战在哪里?这是仅使用单引号的答案。我使用变量而不是 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 使用我推荐的 '\'' 方法,而不是人类通常更难阅读的 '"'"' 方法。
a
alecxs

除了@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

希望在遇到类似问题时这会有所帮助


根据 shell 使用 '$'\047'''$'\\047'' 作为 '\'' 的替代品
u
user2297550

需要一个最小的答案,这样人们就可以不用花费很多时间就可以开始工作,因为我不得不筛选那些雄辩的人。

没有办法转义单引号或单引号内的任何其他内容。

以下是,也许令人惊讶的是,一个完整的命令:

$ echo '\'

其输出是:

\

甚至对于 bash 的长期用户来说,反斜杠在单引号内没有任何意义。也没有别的。


E
Eric Leschinski

另一种解决嵌套引用层数过多问题的方法:

您正试图将太多东西塞进太小的空间,因此请使用 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
-------------

J
JasonWoof
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 想知道它做了什么。)


G
George V. Reilly

如果您在 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>..."))
O
Ole Tange

如果你安装了 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"'

单引号内所有正确引用的字符串。


e
exbuddha

这是另一个解决方案。该函数将接受一个参数并使用单引号字符适当地引用它,正如上面投票答案所解释的那样:

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'