以下解决方案适用于Bourne shell 可以处理的所有输入字符串(包括换行符),在大多数系统上不使用外部命令,并且可移植到所有现代 Bourne-like shell:放入此esceval
函数的每个参数都单独正确打印转义/引用。(我将其命名esceval
为“逃避评估”,以防您想知道。)
esceval()
{
case $# in 0) return 0; esac
while :
do
printf "'"
unescaped=$1
while :
do
case $unescaped in
*\'*)
printf %s "${unescaped%%\'*}""'\''"
unescaped=${unescaped#*\'}
;;
*)
printf %s "$unescaped"
break
;;
esac
done
shift
case $# in 0) break; esac
printf "' "
done
printf "'\n"
}
对我上面的保证进行了更迂腐的阐述:
- 上面的代码可移植到所有具有功能的 shell(今天实际使用的所有 shell)
${foo#bar}
和${foo%%bar}
替换(所有你需要关心的 shell,除非你的目标是 Solaris 10/bin/sh
或类似的古代遗物)。
- 上面的代码不需要 fork/exec 任何新进程,除非
printf
它不是你的 shell 中的内置程序(printf
仅作为外部命令可用的情况很少见,例如sed
,它几乎总是一个外部命令,我已经看到更多精简的系统有 aprintf
但没有 a sed
,如果这很重要的话)。
unescaped
注意:此处发布的版本将一个变量泄漏到全局命名空间local unescaped
(花括号,虽然如果你走那条路,这在视觉上有点不明显,而且大多数 shell 确实为子 shell 分叉了一个额外的过程)。
另一方面,如果您确实需要支持没有那些可变子字符串替换的系统,您可以使用sed
,但您需要小心正确地转义字符串中的换行等棘手的事情:
esceval()
{
case $# in 0) return 0; esac
while :
do
printf "'"
printf %s "$1" | sed "s/'/'\\\\''/g"
shift
case $# in 0) break; esac
printf "' "
done
printf "'\n"
}
关于尾随换行符的最后说明:您应该注意 Bourne shell 从命令替换中删除尾随换行符(大多数删除所有尾随换行符,一些 shell 只删除一个)。换句话说,请对此感到厌倦:
# Literal strings work fine:
esceval 'foo
'
# Quoted variable substitution also works fine:
ln='
'
esceval "foo$ln$ln$ln$ln"
# Breaks - newlines never make it into `esceval`:
esceval "`printf 'foo\n\n\n\n'`"
PS 还有我制作的这个怪物,它是一个 polyfill,它将在前两个版本之间进行选择,具体取决于你的 shell 是否能够支持必要的变量替换语法(虽然看起来很糟糕,因为只有 shell 的版本必须放在一个 eval-ed 字符串中,以防止不兼容的 shell 在看到它时停止):https ://github.com/mentalisttraceur/esceval/blob/master/sh/esceval.sh