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

假设你有一个Bashalias,比如:

alias rxvt='urxvt'

效果不错。

但是:

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

不会工作,也不会:

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

那么,一旦您转义了引号,您如何最终匹配字符串中的打开和关闭引号呢?

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

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

655776 次浏览

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

用单引号括起字符('")保留文字值引号中的每个字符。A单引号不能发生在单引号,即使前面有a反斜杠。

所以bash不会理解:

alias x='y \'z '

但是,如果你用双引号包围,你可以这样做:

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

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

 alias rxvt='urxvt -fg '"'"'#111111'"'"' -bg '"'"'#111111'"'"#                     ^^^^^       ^^^^^     ^^^^^       ^^^^#                     12345       12345     12345       1234

解释'"'"'如何解释为'

  1. '结束使用单引号的第一个引号。
  2. "开始第二个引号,使用双引号。
  3. '引用的字符。
  4. "结束第二个引号,使用双引号。
  5. '开始第三个引号,使用单引号。

如果您没有在(1)和(2)之间或(4)和(5)之间放置任何空格,shell会将该字符串解释为一个长单词。

我不是特别针对引用问题,因为,好吧,有时,考虑另一种方法是合理的。

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'"

(哎呀,我想我确实解决了引用:)

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


我经常在我的Perl脚本中创建一个“quotify”函数来为我执行此操作。步骤是:

s/'/'\\''/g    # Handle each embedded quote$_ = qq['$_']; # Surround result with single quotes.

这几乎照顾到所有的情况。

当你在shell脚本中引入eval时,生活会变得更有趣。你本质上必须重新确定一切!

例如,创建一个名为quotify的Perl脚本,其中包含上述语句:

#!/usr/bin/perl -pls/'/'\\''/g;$_ = qq['$_'];

然后使用它来生成一个正确引用的字符串:

$ quotifyurxvt -fg '#111111' -bg '#111111'

结果:

'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

然后可以将其复制/粘贴到alias命令中:

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

(如果您需要将该命令插入到评估中,请再次运行quotify:

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

结果:

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

它可以复制/粘贴到评估中:

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

我可以确认在单引号字符串中使用'\''作为单引号在Bash中确实有效,并且可以以与线程前面的“胶合”参数相同的方式解释。假设我们有一个带引号的字符串:'A '\''B'\'' C'(这里的所有引号都是单引号)。如果它被传递给回显,它会打印以下内容:A 'B' C。在每个'\''中,第一个引号关闭当前的单引号字符串,下面的\'将单引号粘合到前一个字符串(\'是一种指定单引号而不启动引号字符串的方法),最后一个引号打开另一个单引号字符串。

由于Bash2.04语法$'string'允许一组有限的转义。

从Bash 4.4开始,$'string'也允许全套c型转义,使得行为在以前的版本中与$'string'略有不同。(以前可以使用$('string')形式。)

Bash 2.04及更高版本中的简单示例:

  $> echo $'aa\'bb'aa'bb
$> alias myvar=$'aa\'bb'$> alias myvaralias myvar='aa'\''bb'

在您的案例中:

$> alias rxvt=$'urxvt -fg \'#111111\' -bg \'#111111\''$> alias rxvtalias 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 octalvalue nnn (one to three digits)\xHH   the eight-bit character whose value is the hexadecimalvalue HH (one or two hex digits)\uHHHH the Unicode (ISO/IEC 10646) character whose value isthe hexadecimal value HHHH (one to four hex digits)\UHHHHHHHH the Unicode (ISO/IEC 10646) character whose valueis the hexadecimal value HHHHHHHH (one to eighthex digits)\cx    a control-x character

扩展后的结果是单引号,就好像美元符号不存在一样。


有关更多详细信息,请参阅bash-hackers.orgwiki上的引号和转义:ANSI C喜欢字符串。还要注意,“Bash更改”文件(概述这里)提到了很多与$'string'引用机制相关的更改和bug修复。

根据unix.stackexchange.com如何将特殊字符用作普通字符?,它应该在bash、zsh、mksh、ksh93和FreeBSD以及busybox sh中工作(有一些变体)。

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

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

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

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

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

以下是上面提到的一个真正的答案:

有时我将使用rsync在ssh上下载,并且必须转义带有'的文件名两次!(OMG!)一次用于bash,一次用于ssh。交替引用分隔符的相同原理在这里起作用。

例如,假设我们想要:路易斯·塞洛克斯的洛杉矶故事…

  1. 首先,您将路易斯Theroux用单引号表示bash,用双引号表示ssh:"路易斯·塞洛克斯"
  2. 然后你使用单引号来转义双引号'"'
  3. 使用双引号来转义撇号“'”
  4. 然后重复#2,使用单引号来转义双引号'"'
  5. 然后将LA Stories用单引号括起来用于bash,并用双引号括起来用于ssh:'"LA Stories"'

看哪!你得到了这个:

rsync -ave ssh '"Louis Theroux"''"'"'"'"''"s LA Stories"'

这是一个可怕的大量工作为一个小'-但你去

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

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

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

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.TXT201737194201802699201835214
$ rc Little_Commas.TXT201737194, 201802699, 201835214

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

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

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

另一种解决嵌套报价层过多问题的方法:

您试图将太多内容塞进太小的空间,因此请使用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 10385alien Dyson ring detected @grid 10385-------------Mon Oct 26 20:30:14 EDT 2015-------------

此功能:

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; doquote "$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=0while [ $i -lt ${#1} ]; dolocal ch="${1:i:1}"if [[ "$ch" != "'" ]]; thenquoted="$quoted$ch"elselocal single_quotes="'"local j=1while [ $j -lt ${#1} ] && [[ "${1:i+j:1}" == "'" ]]; dosingle_quotes="$single_quotes'"((j++))donequoted="$quoted'\"$single_quotes\"'"((i+=j-1))fi((i++))doneecho "$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'

这些答案大多与你所问的具体情况有关。我和一个朋友开发了一种通用方法,允许任意引用,以防你需要通过多层shell扩展引用bash命令,例如,通过ssh、su -cbash -c等。你需要一个核心原语,在本机bash中:

quote_args() {local sq="'"local dq='"'local space=""local argfor arg; doecho -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

上面的例子:

  1. shell将每个参数单独引用到内部quote_args,然后将结果输出与内部双引号组合成一个参数。
  2. shell引号bash-c和步骤1中已经引用过一次的结果,然后将结果与外部双引号组合成一个参数。
  3. 将该混乱作为参数发送到外部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运算符应用任何额外的引用:

$ (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++ )); doecho -n "$I:<${!I}> " 1>&2doneecho 1>&2}
debug_args_and_run() {debug_args "$@""$@"}

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

$ debug_args_and_run echo a'"'b"'"c arg21:<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

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

这是不可能的。

如果我们假设我们正在使用bash。

从bash手册…

Enclosing characters in single quotes preserves the literal value of eachcharacter within the quotes.  A single quote may not occurbetween 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"

由于不能将单引号放在单引号字符串中,最简单和最可读的选择是使用HEREDOC字符串

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

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

需要在HEREDOC周围放一个引号,因为它在$()

如果您在Python 2或Python 3中生成shell字符串,以下内容可能有助于引用参数:

#!/usr/bin/env python
from __future__ import print_function
try:  # py3from shlex import quote as shlex_quoteexcept ImportError:  # py2from 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!'

显然,简单地用双引号包围会更容易,但是挑战在哪里?这是只使用单引号的答案。我使用变量而不是alias,这样打印证明更容易,但它与使用alias相同。

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

补充说明

关键是您可以关闭单引号并根据需要多次重新打开它。例如foo='a''b'foo='ab'相同。因此您可以关闭单引号,输入字面单引号\',然后重新打开下一个单引号。

故障图

此图通过使用括号显示单引号的打开和关闭位置来清楚地显示。引号不像括号那样“嵌套”。您还可以注意正确应用的颜色突出显示。引用的字符串是栗色的,而\'是黑色的。

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

(这基本上和阿德里安的答案一样,但我觉得这更能说明问题。而且他的答案末尾有两个多余的单引号。)

shell_escape () {printf '%s' "'${1//\'/\'\\\'\'}'"}

实现说明:

  • 双引号,因此我们可以轻松输出包装单引号并使用${...}语法

  • bash的搜索和替换如下所示:${varname//search/replacement}

  • 我们用'\''替换'

  • '\''编码一个',如下所示:

    1. '结束单引号

    2. \'编码'(需要反斜杠,因为我们不在引号内)

    3. '再次开始单引号

    4. bash自动连接之间没有空格的字符串

  • 在每个\'之前都有一个\,因为这是${...//.../...}的转义规则。

string="That's "'#@$*&^`(@#'echo "original: $string"echo "encoded:  $(shell_escape "$string")"echo "expanded: $(bash -c "echo $(shell_escape "$string")")"

P. S.始终编码为单引号字符串,因为它们比双引号字符串简单得多。

如果你安装了GNU并行,你可以使用它的内部引用:

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

从版本20190222开始,您甚至可以多次--shellquote

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

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

这是我的两分钱-如果一个人想要sh可移植,而不仅仅是bash特定(解决方案不是太有效,虽然,因为它启动了一个外部程序-sed):

  • 把这个放在quote.sh(或者只是quote)你的PATH的某个地方:
# 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中使用它,需要转义ed表达式本身中的所有这些单引号字符-导致类似

sed 's/\(['"'"']['"'"']*\)/'"'"'"\1"'"'"'/g'

(构建此结果的一个好方法是将原始表达式s/\(['][']*\)/'"\1"'/g提供给Kyle Rose或George V. Reilly的脚本)。

最后,期望输入来自stdin是有道理的——因为通过命令行参数传递它可能已经太麻烦了。

(哦,也许我们想添加一个小帮助消息,这样当有人以./quote.sh --help的身份运行它时,脚本就不会挂起,不知道它做了什么。)

除了@JasonWoof完美的答案,我想展示我是如何解决相关问题的

在我的情况下,用'\''编码单引号并不总是足够的,例如,如果字符串必须用单引号引用,但引号的总数导致奇数

#!/bin/bash
# no closing quotestring='alecxs\'solution'
# this works for stringstring="alecxs'solution"string=alecxs\'solutionstring='alecxs'\''solution'

让我们假设string是一个文件名,我们需要将引用的文件名保存在列表中(如stat-c%N ./* > 列表

echo "'$string'" > "$string"cat "$string"

但处理此列表将失败(取决于字符串总共包含多少引号)

while read filedols -l "$file"eval ls -l "$file"done < "$string"

解决方法:使用字符串操作对引号进行编码

string="${string//$'\047'/\'\$\'\\\\047\'\'}"
# resultecho "$string"

现在它起作用了,因为引号总是平衡的

echo "'$string'" > listwhile read filedols -l "$file"eval ls -l "$file"done < list

希望这能帮助我们面对类似的问题

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

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

这里有一些基本的例子来演示它是如何工作的:

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

  1. 十六进制\x27

    echo -e "Let\x27s get coding!"# ORecho -e 'Let\x27s get coding!'

    结果:

    Let's get coding!
  2. 八进制\047

    echo -e "Let\047s get coding!"# ORecho -e 'Let\047s get coding!'

    结果:

    Let's get coding!

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

注意:bash是坚果!有时甚至#1字符也有特殊含义,并且必须是从双引号中删除然后转义"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!"

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

$ 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字符串中混合扩展和非扩展

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

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

CMD="gs_set_title"
# 1. 1st technique: escape the $ symbol with a backslash (\) so it doesn't# run and expand the command following itecho "$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 itecho "$CMD ""'"'$(basename "$(pwd)")'"'"

示例输出:

gs_set_title '$(basename "$(pwd)")'gs_set_title '$(basename "$(pwd)")'

注意:对于我的gs_set_title bash函数,我有在我的1号文件里,请参阅我的另一个答案

参考文献:

  1. https://en.wikipedia.org/wiki/ASCII#Printable_characters
  2. https://serverfault.com/questions/208265/what-is-bash-event-not-found/208266#208266
  3. 另见我的答案:如何使用回显编写非ASCII字符?

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

根本没办法在单引号内转义单引号或者别的什么

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

$ echo '\'

其输出为:

\

令人惊讶的是,即使是bash的长期用户,反斜杠在单引号中也没有任何意义。其他任何东西也没有。