如果我没有对参数进行任何处理,othercmd“$@”可以正常工作,但是我需要提取一些参数并进行处理.
如果我可以假设Bash,那么我可以使用printf%q来计算引用的args版本,我可以稍后评估,但是这不会在例如Ubuntu的破折号(/ bin / sh).
有没有相当于printf%q可以用普通的Bourne shell脚本编写,只使用内置函数和POSIX定义的实用程序,作为我可以复制到脚本中的函数?
例如,一个脚本尝试以相反的顺序使其参数:
#!/bin/sh args= for arg in "$@" do args="'$arg' $args" done eval "ls $args"
适用于许多情况:
$./handle goodbye "cruel world" ls: cannot access cruel world: No such file or directory ls: cannot access goodbye: No such file or directory
但不使用’
$./handle goodbye "cruel'st world" ./handle: 1: eval: Syntax error: Unterminated quoted string
并且以下工作正常但依赖于Bash:
#!/bin/bash args= for arg in "$@" do printf -v argq '%q' "$arg" args="$argq $args" done eval "ls $args"
Jesse Glick看到的答案大概在那里,但是它有几个错误,而且我还有几个替代方案供您考虑,因为这是一个不止一次遇到的问题.
首先,您可能已经知道这一点,回声是一个坏主意,应该使用printf,如果目标是可移植性:“echo”在POSIX中有未定义的行为,如果接收到的参数是“-n”,而在实践中echo treat -n的实现作为一个特殊选项,而其他只将其视为打印的正常参数.所以成为这样
esceval() { printf %s "$1" | sed "s/'/'\"'\"'/g" }
或者,而不是通过将嵌入式单引号转换为:
'"'"'
..你可以把它们变成:
'\''
我猜想(我想象,性能差异可以忽略不计,尽管我从来没有测试过).所得到的sed字符串如下所示:
esceval() { printf %s "$1" | sed "s/'/'\\\\''/g" }
(它是四个反斜杠,因为双引号吞下了两个,并留下两个,然后sed吞下一个,只留下一个.就个人而言,我觉得这样更可读,所以我将在其余的例子中使用它,但两者应该是等效的.)
但是,我们仍然有一个错误:命令替换将从命令输出中删除尾随换行符中的至少一个(但是在许多shell ALL中)(并非所有的空格,特别是换行).所以上面的解决方案是有效的,除非你在参数的最后有换行符.那么你会丢失那些/那些换行符.修复很简单:在您的quote / esceval函数输出之前,在实际的命令值之后添加另一个字符.顺便提一下,我们已经需要这样做,因为我们需要用单引号启动和停止转义参数.老实说,我不明白为什么没有做到这一点.你有两种选择:
esceval() { printf '%s\n' "$1" | sed "s/'/'\\\\''/g; 1 s/^/'/; $s/$/'/" }
这将确保参数已经完全转义,在构建最终字符串时不需要添加更多的单引号.这可能是您最接近的单一内联版本.如果你有一个sed依赖,你可以在这里停下来.
如果你不了解sed依赖,但是假设你的shell实际上是POSIX兼容的(还有一些在那里,特别是Solaris 10及更低版本的/ bin / sh)能够做这个下一个变体 – 但几乎所有的shell你需要关心将这样做很好):
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在将变量替换为引用的字符串时发生错误,其中变量的引用并没有完全开始,而是在变量替换所在的位置.因此,这是我的偏执的可移植习惯.在实践中,你可以做后者,这不会是一个问题.
如果您不想在shell环境的其余部分中破坏变量UNESCAPED,那么可以将该函数的整个内容包装到子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" }
..或同样的事情,但与shell-only版本:
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中 – 我将它们分开,因为当您可以单独查看开始和结束单引号时,我觉得它使逻辑更加清晰打印语句.
附:还有这个我所做的怪物,这是一个polyfill,它会根据你的shell似乎能够支持必要的变量替换语法(之前的两个版本)之间进行选择(看起来很糟糕,因为只有shell版本必须是在一个被评估的字符串中,以防止不相容的shell在它们看到时被禁止):https://github.com/mentalisttraceur/esceval/blob/master/sh/esceval.sh