比方说,你有一个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'\''

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


当前回答

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

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'

其他回答

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

用单引号括住字符 (" ')保存的文字值 引号中的每个字符。一个 单引号之间不能出现 单引号,即使前面有 反斜杠。

所以bash不会理解:

别名x='y \'z '

但是,你可以用双引号括起来:

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

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


我经常在Perl脚本中创建一个“quotify”函数来为我做这件事。步骤如下:

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

这基本上解决了所有情况。

当您在shell脚本中引入eval时,生活会变得更加有趣。你必须重新对所有东西进行报价!

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

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

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

$ quotify
urxvt -fg '#111111' -bg '#111111'

结果:

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

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

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

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

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

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

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

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

对一个人来说,这是一件可怕的事情——但你明白了吗