我有一个shell变量(我们将调用x
),其中包含一个带有shell元字符的字符串。例如,我可能有
abc "def's" ghi
由
设定x='abc "def'\''s" ghi'
我想从该字符串构建一个shell命令(存储在一个文件中,不执行)。我有什么选择?
echo "prog $x" >>file # Doesn't work
echo "prog '$x'" >>file # Doesn't work
echo "prog \"$x\"" >>file # Doesn't work
目前的解决方案使用sed
y=`echo "$x" | sed 's/\([^a-zA-Z0-9._\-\/]\)/\\\\\1/g'`
echo "prog $y" >>file
输出如下(虽然等效输出也可以接受):
prog abc\ \ \"def\'s\"\ \ ghi
问题在于需要完成的地方数量正在增加。有没有人有更好的解决方案?
注意:
sh
(而不是bash
),任何sh
可能会合理地混淆(例如bash
仿真模式中的sh
。 perl
是不可接受的,但使用无处不在的其他unix工具答案 0 :(得分:3)
sh
有功能。
# quote() - Creates a shell literal
# Usage: printf '%s\n' "...$( quote "..." )..."
quote() {
printf \'
printf %s "$1" | sed "s/'/'\\\\''/g"
printf \'
}
测试:
$ x='abc "def'\''s" ghi'
$ printf '%s\n' "$x"
abc "def's" ghi
$ printf '%s\n' "prog `quote "$x"`"
prog 'abc "def'\''s" ghi'
$ printf '%s\n' "prog $( quote "`pwd`" )"
prog '/home/ikegami/foo bar'
$ printf '%s\n' "$( quote "a'b" )"
'a'\''b'
$ printf '%s\n' "$( quote '-n' )"
'-n'
$ printf '%s\n' "$( quote '\\' )"
'\\'
$ printf '%s\n' "$( quote 'foo
bar' )"
'foo
bar'
带有多个参数的版本:
# quote() - Creates a shell literal
# Usage: printf '%s\n' "$( quote "..." "..." "..." )"
quote() {
prefix=''
for p in "$@" ; do
printf "$prefix"\'
printf %s "$p" | sed "s/'/'\\\\''/g"
printf \'
prefix=' '
done
}
答案 1 :(得分:1)
以下解决方案适用于Bourne shell可以处理的 ALL 输入字符串(包括换行符字符),在大多数系统上不使用外部命令,并且可移植到所有现代类似Bourne的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"
}
对我上述保证的更多迂腐细节:
${foo#bar}
和${foo%%bar}
替换(除非你的目标是Solaris,否则你需要关注的所有shell) 10's /bin/sh
或类似的古代文物。)printf
不是shell中的内置函数(printf
仅作为外部命令可用相当罕见,而不是实例sed
几乎总是一个外部命令,我看到更多的精简系统有printf
但没有sed
,如果这很重要的话。 / LI>
醇>
注意:此处发布的版本会将一个变量泄漏到全局命名空间(unescaped
),但您可以通过声明local unescaped
(如果您的shell支持)或通过包装正文来轻松解决此问题。子shell中的函数(括号 - 它们甚至可以替换花括号,但如果你走这条路线,这在视觉上是不明显的,并且大多数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'`"
P.S。我也做了这个怪物,这是一个polyfill,它将在前两个版本之间进行选择,具体取决于你的shell是否能够支持必要的变量替换语法(虽然它看起来很糟糕,因为shell只有版本必须是在eval-ed字符串中,以防止不兼容的shell在看到它时发生barfing):https://github.com/mentalisttraceur/esceval/blob/master/sh/esceval.sh