Bourne / POSIX shell参数拆分

时间:2011-01-20 08:57:58

标签: shell unix sh

假设我有一些可以采用多种选项的实用程序,每个选项后跟一个文件名。例如,我可以将其称为myutilmyutil -o somefilemyutil -p anotherfilemyutil -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以获取标准内容的描述。

7 个答案:

答案 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。”