这是绝对可行的。
您看到的 Jesse Glick 的答案大致就在那里,但它有几个错误,我还有更多的选择供您考虑,因为这是我不止一次遇到的问题。
首先,您可能已经知道,echo 是一个坏主意,如果目标是可移植性,则应该使用 printf:如果它接收的参数是“-n”,则“echo”在 POSIX 中具有未定义的行为,并且实际上有些echo 的实现将 -n 视为特殊选项,而其他实现仅将其视为打印的普通参数。所以就变成了这样:
esceval()
{
printf %s "$1" | sed "s/'/'\"'\"'/g"
}
或者,不要通过将嵌入的单引号转义为:
'"'"'
..相反,您可以将它们变成:
'\''
..我猜的风格差异(我认为无论哪种方式性能差异都可以忽略不计,尽管我从未测试过)。生成的 sed 字符串如下所示:
esceval()
{
printf %s "$1" | sed "s/'/'\\\\''/g"
}
(这是四个反斜杠,因为双引号会吞掉其中两个,留下两个,然后 sed 吞下一个,只留下一个。就我个人而言,我发现这种方式更具可读性,因此我将在其余涉及的示例中使用它它,但两者应该是等价的。)
但是,我们仍然有一个错误:命令替换将从命令输出中删除至少一个(但在许多 shell 中全部)尾随换行符(不是所有空格,只是换行符)。因此,除非您在参数的最后有换行符,否则上述解决方案有效。然后你会失去那个/那些换行符。修复显然很简单:在实际命令值之后添加另一个字符,然后再从引用/esceval 函数输出。顺便说一句,我们已经需要这样做了,因为我们需要用单引号开始和停止转义参数。您有两种选择:
esceval()
{
printf '%s\n' "$1" | sed "s/'/'\\\\''/g; 1 s/^/'/; $ s/$/'/"
}
这将确保参数已经完全转义,在构建最终字符串时无需添加更多单引号。这可能是您将获得的最接近单个可内联版本的东西。如果你对 sed 依赖没问题,你可以在这里停下来。
如果您对 sed 依赖项不满意,但可以假设您的 shell 实际上是 POSIX 兼容的(仍然有一些,特别是 Solaris 10 及更低版本上的 /bin/sh,它不会能够执行下一个变体 - 但您需要关心的几乎所有外壳都可以做到这一点):
esceval()
{
printf \'
unescaped=$1
while :
do
case $unescaped in
*\'*)
printf %s "${unescaped%%\'*}""'\''"
unescaped=${unescaped#*\'}
;;
*)
printf %s "$unescaped"
break
esac
done
printf \'
}
您可能会注意到这里看似多余的引用:
printf %s "${unescaped%%\'*}""'\''"
..这可以替换为:
printf %s "${unescaped%%\'*}'\''"
我做前者的唯一原因是,曾几何时,Bourne shell 在将变量替换为带引号的字符串时存在错误,其中变量周围的引号并没有完全从变量替换的开始和结束位置开始。因此,这是我偏执的便携习惯。在实践中,你可以做后者,这不会有问题。
如果您不想unescaped
在 shell 环境的其余部分中破坏变量,则可以将该函数的全部内容包装在子 shell 中,如下所示:
esceval()
{
(
printf \'
unescaped=$1
while :
do
case $unescaped in
*\'*)
printf %s "${unescaped%%\'*}""'\''"
unescaped=${unescaped#*\'}
;;
*)
printf %s "$unescaped"
break
esac
done
printf \'
)
}
“但是等等”,你说:“我想在一个命令中对 MULTIPLE 参数执行什么操作?如果我出于某种原因从命令行运行它,我希望作为用户的输出仍然看起来不错且清晰易读。”
永远不要害怕,我为您提供保障:
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"
}
..或同样的事情,但只有外壳版本:
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"
)
}
在最后四个中,您可以折叠一些外部 printf 语句并将它们的单引号向上滚动到另一个 printf - 我将它们分开,因为我觉得当您可以看到单独的开始和结束单引号时它使逻辑更加清晰打印报表。
PS 还有我制作的这个怪物,它是一个 polyfill,它将在前两个版本之间进行选择,具体取决于你的 shell 是否能够支持必要的变量替换语法(虽然看起来很糟糕,因为只有 shell 的版本必须放在一个 eval-ed 字符串中,以防止不兼容的 shell 在看到它时吐出):https ://github.com/mentalisttraceur/esceval/blob/master/sh/esceval.sh