假设我有一些可以采用多种选项的实用程序,每个选项后跟一个文件名。例如,我可以将其称为myutil
,myutil -o somefile
,myutil -p anotherfile
,myutil -o somefile -p anotherfile
等....我想编写一个能够调用的包装器POSIX shell脚本myutil
具有任意选项组合(取决于包装器脚本内部的某些条件,与此问题无关)。
我想做类似的事情:
#!/bin/sh
file1=somefile
file2=anotherfile
if [ somecriteria = true ]; then
OPT1="-o $file1"
fi
if [ othercriteria = true ]; then
OPT2="-p $file2"
fi
myutil $OPT1 $OPT2
这很有用 - 只要两个文件名都没有空格:假设两个if
都为真,myutil
得到$ 1 = [ - o],$ 2 = [somefile],$ 3 = [-p] ],$ 4 = [anotherfile]。但是,如果有空格,例如,如果file1="some file"
,$ 1 = [ - o],$ 2 = [some],$ 3 = [file]等等。当然,我想要的是$ 2 = [some file ]
在OPT1和OPT2中的文件名周围放置另一组引号无济于事;例如,如果我将其更改为OPT1="-o \"$file1\""
,那只会得到$ 2 = [“some]和$ 3 = [file”]。在调用myutil
时在$ OPT1和$ OPT2周围加上引号也不起作用:如果我这样做,$ 1 = [-o some file]。
那么,是否有一些技巧可以让这个工作,或者其他一些方法可以做我想要的?我希望这样做能够坚持标准的shell功能,所以没有bash-isms或ksh-isms,请 :)请参阅this以获取标准内容的描述。
答案 0 :(得分:3)
在更多地搞砸了之后,我发现了另一种似乎做我想要的方法:
#!/bin/sh
file1="some file"
file2=anotherfile
if [ somecriteria = true ]; then
OPT1="$file1"
fi
if [ othercriteria = true ]; then
OPT2="$file2"
fi
myutil ${OPT1:+-o "$OPT1"} ${OPT2:+-p "$OPT2"}
如果$ {参数},则构造$ {参数:+ 字}将替换为 word 已设定;如果它没有设置,它会消失。因此,如果$OPT1
未设置,则${OPT1:+-o "$OPT1"}
会消失 - 显然,它不会变成argv
中的空字符串。如果$OPT1
设置为some file
,则上述表达式将替换为-o "some file"
,myutil
将获取$ 1 = [-o],$ 2 = [某个文件],因为我想要
请注意,myutil ${OPT1:+-o} "$OPT1" ${OPT2:+-p} "$OPT2"
不完全按照我的意愿行事,因为如果$OPT1
未设置,则-o
会消失,但"$OPT1"
会消失{{1}}变成空字符串 - $ 1 = [],$ 2 = [-p],$ 3 = [anotherfile]
(根据丹尼斯的建议编辑)
答案 1 :(得分:2)
好像你找到了一个不错的POSIX解决方案。但是,您可以set
使用myutil "$@"
来保持对您的计划的调用#!/bin/sh
file1=somefile
file2=anotherfile
if [ somecriteria = true ]; then
set -- "-o" "$file1"
fi
if [ othercriteria = true ]; then
set -- "$@" "-p" "$file2"
fi
myutil "$@"
。随着可能参数的数量增加,您的解决方案变得有点笨拙。
#!/bin/sh
file1="some file"
file2="another file"
# Default to '1' if not overwritten
: ${x:=1}
: ${y:=1}
if [ $x -eq 1 ]; then
set -- "-o" "$file1"
fi
if [ $y -eq 1 ]; then
set -- "$@" "-p" "$file2"
fi
printf "[%s]" "$@"
echo
$ x=0 y=0 ./opt.sh
[]
$ x=0 y=1 ./opt.sh
[-p][another file]
$ x=1 y=0 ./opt.sh
[-o][some file]
$ x=1 y=1 ./opt.sh
[-o][some file][-p][another file]
{{1}}
答案 2 :(得分:2)
首先,您必须引用此行中的选项 sh myutil.sh "$OPT1" "$OPT2"
这是一个有效的实现,没有在myutil.sh端使用getopts的特殊主题。
此脚本调用myutil.sh:
#!/bin/sh
somecriteria=true
othercriteria=true
file1="some file"
file2="other file"
if [ $somecriteria = true ]; then
OPT1="-o$file1"
fi
if [ $othercriteria = true ]; then
OPT2="-p$file2"
fi
sh myutil.sh "$OPT1" "$OPT2"
这就是myutil.sh的样子:
#!/bin/sh
OPTIND=1
while getopts "o:p:" opt; do
case "$opt" in
o) file1=$OPTARG
;;
p) file2=$OPTARG
;;
esac
done
shift $((OPTIND-1))
echo 'File 1: "'$file1'"'
echo 'File 2: "'$file2'"'
正如您在myutil.sh的输出中所看到的,文件名中的空格被保留:
File 1: "some file"
File 2: "other file"
答案 3 :(得分:1)
为什么要注意简单的引用而不是双引号?
if [ somecriteria = true ]; then
OPT1="-o '$file1'"
fi
if [ othercriteria = true ]; then
OPT2="-p '$file2'"
fi
答案 4 :(得分:1)
我认为你自己的解决方案使用$ {OPT1 + -o“$ OPT1”}是一个很好的解决方案,在我的头脑中我没有看到它在这个用例中有任何问题,但还有另一种使用eval的方法没有人提到过,这更接近原始代码:
#!/bin/sh
FILE1='some file'
FILE2='another file'
if [ somecriteria = true ]; then
OPT1="-o '$FILE1'"
fi
if [ othercriteria = true ]; then
OPT2="-p '$FILE2'"
fi
eval myutil "$OPT1" "$OPT2"
这将产生你想要的东西。
但是如果您的文件名包含单引号作为文字文件名字符串的一部分,则需要小心。
如果您在编写脚本时确切知道文件名的样子,请确保您的文件名的引用文字不会突破您放置文件名的任何转义。
但是,当您处理用户输入或从环境获取输入时,这一点更为重要。 - 例如,如果将$ FILE1定义为abc'; /tmp/malicious_program '
,然后执行eval,它会将myutil行解析为:
myutil -o 'abc'; /tmp/malicious_program '' -p 'another file'
..这是两个独立的命令,可能是一个巨大的安全漏洞,具体取决于相对于创建/ tmp / malicious_program并设置$ FILE1的实体执行此脚本的确切程度。
在这些情况下,如果你愿意引入对sed的依赖,你可以先做这样的事情:
FILE1=\'`printf %s "$FILE1" | sed "s/'/'\\\\''/g"`\'
..这将产生一个漂亮的单引号转义文件名,其中的任何sungle-quotes也被正确转义。
因为在Bourne / POSIX shell中,只有一个引号可以“突破”一个引用的字符串,这就是我在我的例子中使用单引号的原因。以这种方式逃避事情的双引号是可能的,但是你的sed命令必须要复杂得多,因为你需要在双引号内逃避其他几个字符(在我的头顶之外:而不是只是逃避单引号,你逃脱双引号,反斜杠,反引号,美元符号,以及其他可能我没想到的东西。)
PS:因为我发现这个“在shell中转包,然后eval以后”的方法在几种情况下很有用,至少在一个非常必要的地方,我写了一个很小的C程序(和使用sed的等效shell函数)如上所述,将所有参数包装在单引号shell转义中,以防有人想要使用它而不必实现自己的:esceval答案 5 :(得分:0)
可能的实现如下,bash数组教程是here。
#!/bin/sh
function myutil {
local a1=$1; shift;
local a2=$1; shift;
local a3=$1; shift;
local a4=$1; shift;
echo "a1=$a1,a2=$a2,a3=$a3,a4=$a4"
}
file1="some file"
file2="another file"
OPT1=(-o "$file1")
OPT2=(-p "$file2")
myutil "${OPT1[@]}" "${OPT2[@]}"
答案 6 :(得分:0)
使用 getopts http://mywiki.wooledge.org/BashFAQ/035
“POSIX shell(以及其他)提供了可以安全使用的getopts。”