【问题标题】:Bourne/POSIX shell parameter splittingBourne/POSIX shell 参数拆分
【发布时间】:2011-06-12 07:40:59
【问题描述】:

假设我有一些实用程序可以接受多个选项,每个选项后跟一个文件名。例如,我可以将其称为myutilmyutil -o somefilemyutil -p anotherfilemyutil -o somefile -p anotherfile 等...... (取决于包装脚本内部的一些条件,这些条件与此问题无关)。

我想过做这样的事情:

#!/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

这很好用——只要两个文件名都没有空格:假设ifs 都为真,myutil 得到 $1 = [-o]、$2 = [somefile]、$3 = [-p] 和 $4 = [另一个文件]。但是,如果有空格,例如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 了解标准中的内容。

【问题讨论】:

  • 如果你这样设置你的变量:var=true,那么你可以这样写你的条件:if $var; then do something; fi
  • if 语句只是为了更像伪代码 :) [ somecriteria = true ] 比较文字字符串 somecriteriatrue,并且总是错误的——实际代码肯定不是不喜欢这样:)

标签: shell unix sh


【解决方案1】:

在搞砸了更多之后,我发现了另一种似乎可以做我想做的事情:

#!/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"}

结构 ${parameter:+word} 如果 ${parameter} 将被替换为 word已设置;如果没有设置,它就会消失。因此,如果未设置 $OPT1${OPT1:+-o "$OPT1"} 就会消失——值得注意的是,它不会在 argv 中变成空字符串。如果$OPT1 设置为some file,上面的表达式被-o "some file" 替换,myutil 得到 $1 = [-o], $2 = [some file] 我想要的。

请注意myutil ${OPT1:+-o} "$OPT1" ${OPT2:+-p} "$OPT2" 确实 完全按照我的意愿行事,因为如果未设置$OPT1-o 会消失,但"$OPT1" 会变成一个空字符串—$1 = [], $2 = [-p], $3 = [另一个文件]

(根据丹尼斯的建议编辑)

【讨论】:

  • +1 不错的解决方案!尝试每个选项使用一个大括号扩展:${OPT1:+-o "$OPT1"}。它似乎对我有用。
【解决方案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]

【讨论】:

  • 谢谢,但我特别要求不要使用 bash-isms。数组是一个非标准的 bash 扩展。请参阅pubs.opengroup.org/onlinepubs/009695399/utilities/… 了解标准中包含的内容。
  • @Dave 更新了我的答案,为您提供更接近原始代码的 POSIX 解决方案。
  • 是的,这个解决方案似乎比我找到的那个更容易管理。谢谢! :)
【解决方案3】:

首先,您必须在此行中引用选项 sh myutil.sh "$OPT1" "$OPT2"

这是一个没有特定 -isms 的工作实现,它在 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"

【讨论】:

  • 啊,我想我听起来有点像 myutil 是我写的并且可以控制的东西——它实际上不是 my 实用程序;这是一个第三方工具。虽然它是开源的,我可以修补它的参数解析以使用您建议的解决方案,但告诉所有人我的脚本不适用于 stock 实用程序并且他们必须修补它是不切实际的。
  • 不完全。正确的引用必须在调用 myutil 的 your 脚本一侧完成。不过没关系。
【解决方案4】:

为什么要注意单引号而不是双引号?

if [ somecriteria = true ]; then
  OPT1="-o '$file1'"
fi

if [ othercriteria = true ]; then
  OPT2="-p '$file2'"
fi

【讨论】:

  • 因为那也行不通。使用单引号,$1 = [-o], $2 = ['some], $3 = [file']
【解决方案5】:

我认为您自己使用 ${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"`\'

..这将产生一个很好的单引号转义文件名,其中的任何单引号也正确转义。

由于在 Bourne/POSIX shell 中,只有单引号可以“脱离”单引号字符串,这就是我在示例中使用单引号的原因。以这种方式转义双引号是可能的,但是您的 sed 命令必须要复杂得多,因为您需要在双引号内转义其他几个字符(在我的脑海中:您不仅要转义单引号,还可以转义双引号, 反斜杠, 反引号, 美元符号, 可能还有其他我没有想到的东西)。

PS:因为我发现这种“在 shell 中转义,然后再 eval”的方法在某些情况下很有用,至少在完全有必要的情况下,我编写了一个很小的 ​​C 程序(以及使用 sed 的等效 shell 函数),如上所述,将其所有参数包装在单引号外壳转义中,以防有人想使用它而不是必须实现自己的:esceval

【讨论】:

    【解决方案6】:

    可能的实现如下,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[@]}"
    

    【讨论】:

    • 你不应该使用关键字function,尤其是如果你打算使用#!/bin/sh
    • @SiegeX - 是什么让你这么说?这种语法更符合 shell 中处理参数的方式,并且更接近于许多人用作处理大量文本数据的免费工具的 Perl。
    • 对不起,我应该更清楚。原因是function 不符合 POSIX。见this question。相反,请使用myutil ()
    • 数组通常也不符合 POSIX...我正在寻找不需要 bash 的解决方案。
    • /bin/sh 通常不支持数组。
    【解决方案7】:

    使用 getopts http://mywiki.wooledge.org/BashFAQ/035

    “POSIX shell(和其他)提供了可以安全使用的 getopts。”

    【讨论】:

    • 问题不是解析选项,而是将包含空格的文件名作为单个选项传递
    猜你喜欢
    • 2010-09-07
    • 1970-01-01
    • 2012-08-13
    • 2011-01-13
    • 2012-11-09
    • 2014-10-09
    • 2011-01-07
    • 2014-04-12
    • 1970-01-01
    相关资源
    最近更新 更多