比方说,你有一个Bash别名:

alias rxvt='urxvt'

这很好。

然而:

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

不管用,也不管用:

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

那么,一旦有转义引号,如何在字符串中匹配开始和结束引号呢?

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

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


当前回答

如果你在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!'

其他回答

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

问题的作者没有注意到,在他最后一次尝试转义时,多了一个单引号('):

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

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

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

由于Bash 2.04语法$'string'允许一个转义限制集。

自Bash 4.4以来,$'string'还允许完整的c风格转义,使得$'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上的引用和转义:ANSI C like字符串。还要注意,“Bash Changes”文件(概述在这里)提到了很多与$'string'引用机制相关的更改和错误修复。

如何将一个特殊字符作为一个普通字符使用?它应该工作(与一些变化)在bash, zsh, mksh, ksh93和FreeBSD和busybox sh。

如果你已经安装了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)中引用该字符串。

下面是我的观点——如果一个人想要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想知道它是做什么的。)