Bourne/POSIX shell 参数分割

发布于 2024-10-12 23:01:59 字数 1154 浏览 6 评论 0原文

假设我有一些实用程序可以采用多个选项,每个选项后跟一个文件名。例如,我可以将其称为 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 都为 true,myutil 得到 $1 = [-o], $2 = [某个文件]、$3 = [-p] 和 $4 = [另一个文件]。但是,如果有空格,例如 if file1="some file"、$1 = [-o]、$2 = [some]、$3 = [file] 等。当然,我的想要 2 美元 = [某些文件]。

在 OPT1 和 OPT2 中的文件名周围加上另一组引号没有帮助;例如,如果我将其更改为 OPT1="-o \"$file1\"",则只会得到 $2 = ["some] 和 $3=[file"]。在对 myutil 的调用中在 $OPT1 和 $OPT2 周围加上引号也不起作用:如果我这样做,$1 = [-o some file]。

那么,是否有一些技巧可以让这个工作正常,或者有其他方法可以达到我想要的效果?我希望它坚持标准的 shell 功能,所以请不要使用 bash-isms 或 ksh-isms :) 请参阅 有关标准内容的描述。

Assume I have some utility that can take a number of options, each followed by a filename. E.g., I could call it as myutil, myutil -o somefile, myutil -p anotherfile, myutil -o somefile -p anotherfile, etc.... I want to write a wrapper POSIX shell script that is able to call myutil with arbitrary combinations of options (depending on some conditions internal to the wrapper script, which aren't relevant to this question).

I thought of doing something like:

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

This works great—as long as neither filename has spaces: Assuming both ifs are true, myutil gets $1 = [-o], $2 = [somefile], $3 = [-p], and $4 = [anotherfile]. However, if there are spaces, e.g., if file1="some file", $1 = [-o], $2 = [some], $3 = [file], etc. Of course, what I want is for $2 = [some file].

Putting another set of quotes around the filename in OPT1 and OPT2 doesn't help; e.g., if I change it to OPT1="-o \"$file1\"", that just gets me $2 = ["some] and $3=[file"]. And putting in quotes around $OPT1 and $OPT2 in the call to myutil doesn't work either: if I do that, $1 = [-o some file].

So, is there some trick to get this working, or some other approach that would do what I want? I'd like this to stick to standard shell features, so no bash-isms or ksh-isms, please :) See this for a description of what's in the standard.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

山色无中 2024-10-19 23:01:59

经过更多的修改后,我发现另一种方法似乎可以满足我的要求:

#!/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} 将被替换为 word< /em> 如果设置了 ${参数};如果没有设置,它就会消失。因此,如果 $OPT1 未设置,${OPT1:+-o "$OPT1"} 就会消失——值得注意的是,它不会变成 中的空字符串argv。如果 $OPT1 设置为 some file,则上述表达式将替换为 -o "some file"myutil code> 得到 $1 = [-o], $2 = [some file] 如我所愿。

请注意,myutil ${OPT1:+-o} "$OPT1" ${OPT2:+-p} "$OPT2" 并不完全符合我的要求,因为如果$OPT1未设置,-o消失,但"$OPT1"变成空字符串——$1 = [], $2 = [-p], $3 = [另一个文件]

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

After messing with it more, I found another approach that seems to do what I want:

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

The construct ${parameter:+word} will be substituted with word if ${parameter} is set; if it's not set, it goes away. So if $OPT1 is unset, ${OPT1:+-o "$OPT1"} disappears—notably, it doesn't turn into an empty string in argv. If $OPT1 is set to some file, the above expression is substituted with -o "some file", and myutil gets $1 = [-o], $2 = [some file] as I want.

Note that myutil ${OPT1:+-o} "$OPT1" ${OPT2:+-p} "$OPT2" does not do exactly what I want, because if $OPT1 is unset, the -o goes away, but "$OPT1" turns into an empty string—$1 = [], $2 = [-p], $3 = [anotherfile]

(Edited per Dennis's suggestion)

拒绝两难 2024-10-19 23:01:59

看来您找到了一个不错的 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]

Seems like you found a decent POSIX solution. You could, however, use set to maintain a call to your program as myutil "$@". Your solution gets a bit unwieldy as the number of possible parameters grow.

#!/bin/sh
file1=somefile
file2=anotherfile

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

if [ othercriteria = true ]; then
  set -- "$@" "-p" "$file2"
fi

myutil "$@"

Example

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

Output

$ 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]
爱她像谁 2024-10-19 23:01:59

首先,您必须在这一行中引用选项 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"

First of all, you'll have to quote the options in this line sh myutil.sh "$OPT1" "$OPT2"

And here's a working implementation with no particular -isms that uses getopts on the myutil.sh side.

This script calls 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"

And this is what myutil.sh could look like:

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

As you can see in the output of myutil.sh, spaces in the filenames are preserved:

File 1: "some file"
File 2: "other file"
最冷一天 2024-10-19 23:01:59

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

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

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

Why note simple quote instead of double quote ?

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

if [ othercriteria = true ]; then
  OPT2="-p '$file2'"
fi
木緿 2024-10-19 23:01:59

我认为您自己使用 ${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 转义中包装,然后再评估”方法在多种情况下很有用,至少在一种完全必要的情况下,我编写了一个小型 C 程序(以及使用 sed 的等效 shell 函数),它包装了所有它的参数在单引号 shell 中转义,如上所述,以防有人想要使用它,而不必实现自己的: 埃塞瓦尔

I think your own solution using ${OPT1+-o "$OPT1"} is a good one, and off the top of my head I don't see any problems with it in this usecase, but there's another approach using eval that no one mentioned, which is even closer to your original code:

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

This will produce what you want.

But you need to be careful in case your filenames contain single-quotes as part of the literal filename string.

If you know exactly what your filenames look like when writing the script, just ensure your filename's quote literals don't break out of whatever escaping you put around the file name.

But this is even more important when you're handling user input, or otherwise getting input from the environment. - for example, if $FILE1 is defined as abc'; /tmp/malicious_program ' and then you do the eval, it will parse the myutil line into:

myutil -o 'abc'; /tmp/malicious_program '' -p 'another file'

..which is two separate commands, and could be a massive security hole, depending on how exactly this script is being executed relative to the entity which created /tmp/malicious_program and set $FILE1.

In those cases, if you are willing to introduce a dependency on sed, you can do something like this first:

FILE1=\'`printf %s "$FILE1" | sed "s/'/'\\\\''/g"`\'

..this will produce a nice, single-quote escaped filename, with any sungle-quotes inside it properly escaped too.

Since in Bourne/POSIX shell nothing but a single quote can "break out of" a single quoted string, this is why I use single-quoting in my example. Double quote escaping things this way is possible, but your sed command has to be much more complex since there's several other characters you need to escape inside double quotes (off the top of my head: instead of just escaping singe quotes, you escape double quotes, backslashes, backticks, dollar signs, and possibly other stuff I'm not thinking of).

P.S.: because I found this "wrap in shell escaping, then eval later" approach useful in several cases, in at least one where it was downright necessary, I wrote a tiny C program (and equivalent shell function using sed) which wraps all of its arguments in single quote shell escaping as described above, in case anyone wants to use it instead of having to implement your own: esceval

夜光 2024-10-19 23:01:59

可能的实现如下,bash 数组教程位于此处

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

Possible implementation is below, bash arrays tutorial is 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[@]}"
身边 2024-10-19 23:01:59

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

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

Use getopts http://mywiki.wooledge.org/BashFAQ/035

"The POSIX shell (and others) offer getopts which is safe to use instead."

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文