如何迭代Bash脚本中的参数

我有一个复杂的命令,我想制作一个shell / bash脚本。 我可以很容易地把它写成$1

 foo $1 args -o $1.ext 

我希望能够将多个input名称传递给脚本。 什么是正确的方法来做到这一点?

而且,当然,我想处理其中有空格的文件名。

使用"$@"来表示所有参数:

 for var in "$@" do echo "$var" done 

这将迭代每个参数并将其单独打印出来。 $ @的行为与$ *相似,除了引用时,如果参数中有空格,则正确分解参数:

 sh test.sh 1 2 '3 4' 1 2 3 4 

重写VonC现在删除的答案 。

罗伯特·甘宝(Robert Gamble)的简洁回答直接解决了这个问题。 这个放大了一些与包含空格的文件名的问题。

另请参阅: / bin / sh中的$ {1:+“$ @”}

基础论文: "$@"是正确的, $* (无引号)几乎总是错的。 这是因为当参数包含空格时, "$@"可以正常工作,而当它们不包含空格时,它的作用与$*相同。 在某些情况下, "$*"也可以,但是"$@"通常(但不总是)在相同的地方工作。 不加引号, $@$*是等价的(并且几乎总是错的)。

那么, $*$@"$*""$@"之间的区别是什么? 他们都与“壳的所有论据”有关,但他们做了不同的事情。 当没有引用时, $*$@做同样的事情。 他们将每个“单词”(非空白序列)视为一个单独的参数。 引用的forms是完全不同的: "$*"将参数列表视为单个空格分隔的string,而"$@"将参数与命令行上指定的参数几乎完全对应。 当没有位置参数时, "$@"扩展到什么都没有; "$*"扩展为空string – 是的,有一个区别,虽然它可能很难察觉。 在引入(非标准)命令之后,请参阅下面的更多信息。

二级论文:如果你需要用空格处理参数,然后把它们传递给其他命令,那么你有时需要非标准的工具来协助。 (或者你应该小心使用数组: "${array[@]}"行为类似于"$@" 。)

例:

  $ mkdir "my dir" anotherdir $ ls anotherdir my dir $ cp /dev/null "my dir/my file" $ cp /dev/null "anotherdir/myfile" $ ls -Fltr total 0 drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 my dir/ drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 anotherdir/ $ ls -Fltr * my dir: total 0 -rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 my file anotherdir: total 0 -rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 myfile $ ls -Fltr "./my dir" "./anotherdir" ./my dir: total 0 -rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 my file ./anotherdir: total 0 -rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 myfile $ var='"./my dir" "./anotherdir"' && echo $var "./my dir" "./anotherdir" $ ls -Fltr $var ls: "./anotherdir": No such file or directory ls: "./my: No such file or directory ls: dir": No such file or directory $ 

为什么不行? 它不起作用,因为shell在扩展variables之前会处理引号。 所以,为了让shell注意embedded在$var的引号,你必须使用eval

  $ eval ls -Fltr $var ./my dir: total 0 -rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 my file ./anotherdir: total 0 -rw-r--r-- 1 jleffler staff 0 Nov 1 14:55 myfile $ 

如果你有文件名,例如“ He said, "Don't do this!" “(用引号和双引号和空格),这会变得非常棘手。

  $ cp /dev/null "He said, \"Don't do this!\"" $ ls He said, "Don't do this!" anotherdir my dir $ ls -l total 0 -rw-r--r-- 1 jleffler staff 0 Nov 1 15:54 He said, "Don't do this!" drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 anotherdir drwxr-xr-x 3 jleffler staff 102 Nov 1 14:55 my dir $ 

这些shell(所有这些)并不是特别容易处理这些东西,所以很多Unix程序都不能很好地处理它们。 在Unix上,文件名(单个组件)可以包含除斜杠和NUL '\0'以外的任何字符。 但是,shell强烈鼓励在path名称的任何地方不使用空格或换行符或制表符。 这也是为什么标准的Unix文件名不包含空格等等

当处理可能包含空格和其他麻烦字符的文件名时,你必须非常小心,而且很早以前我发现我需要一个在Unix上不是标准的程序。 我把它叫做escape (版本1.1的date是1989-08-23T16:01:45Z)。

这里是一个escape的例子 – 与SCCS控制系统。 这是一个封面脚本,可以同时执行delta (认为签入 )和get (认为签出 )。 各种各样的论点,特别是 – (你为什么-y这个改变)将包含空白和换行符。 请注意,脚本的date从1992年开始,所以它使用back-tick而不是$(cmd ...)表示法,并且不在第一行使用#!/bin/sh

 : "@(#)$Id: delget.sh,v 1.8 1992/12/29 10:46:21 jl Exp $" # # Delta and get files # Uses escape to allow for all weird combinations of quotes in arguments case `basename $0 .sh` in deledit) eflag="-e";; esac sflag="-s" for arg in "$@" do case "$arg" in -r*) gargs="$gargs `escape \"$arg\"`" dargs="$dargs `escape \"$arg\"`" ;; -e) gargs="$gargs `escape \"$arg\"`" sflag="" eflag="" ;; -*) dargs="$dargs `escape \"$arg\"`" ;; *) gargs="$gargs `escape \"$arg\"`" dargs="$dargs `escape \"$arg\"`" ;; esac done eval delta "$dargs" && eval get $eflag $sflag "$gargs" 

(我现在可能不会像现在这样彻底地使用转义 – 比如说-e参数不需要,但总的来说,这是使用escape简单脚本之一。)

escape程序只是简单的输出它的参数,就像echo一样,但是它保证了参数被eval保护(一个eval级别;我有一个远程shell执行的程序,并且需要转义输出escape )。

  $ escape $var '"./my' 'dir"' '"./anotherdir"' $ escape "$var" '"./my dir" "./anotherdir"' $ escape xyz xyz $ 

我有另外一个名为al程序,每行列出一个参数(更古老:1987-01-27T14:35:49版本1.1)。 在debugging脚本时,它是最有用的,因为它可以插入到命令行中,以查看实际传递给命令的参数。

  $ echo "$var" "./my dir" "./anotherdir" $ al $var "./my dir" "./anotherdir" $ al "$var" "./my dir" "./anotherdir" $ 

[ 补充:现在为了显示各种"$@"符号之间的区别,下面是一个例子:

 $ cat xx.sh set -x al $@ al $* al "$*" al "$@" $ sh xx.sh * */* + al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file He said, "Don't do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file + al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my file He said, "Don't do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file + al 'He said, "Don'\''t do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file' He said, "Don't do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file + al 'He said, "Don'\''t do this!"' anotherdir 'my dir' xx.sh anotherdir/myfile 'my dir/my file' He said, "Don't do this!" anotherdir my dir xx.sh anotherdir/myfile my dir/my file $ 

请注意,没有任何东西保留命令行上**/*之间的原始空白。 另外,请注意,您可以使用以下命令更改shell中的“命令行参数”:

 set -- -new -opt and "arg with space" 

这设置了4个选项,'- -opt ','- -opt ',' and ',' arg with space '。
]

嗯,这是一个相当长的答案 – 也许注意是更好的术语。 根据请求提供escape源代码(在Gmail点com上以电子​​邮件的forms发送给firstname dot lastname)。 al的源代码非常简单:

 #include <stdio.h> int main(int argc, char **argv) { while (*++argv != 0) puts(*argv); return(0); } 

就这样。 它相当于Robert Gamble展示的test.sh脚本,可以写成一个shell函数(但是当我第一次写al时,shell函数在本地版本的Bourne shell中不存在)。

另外请注意,您可以将al编写为一个简单的shell脚本:

 [ $# != 0 ] && printf "%s\n" "$@" 

条件是需要的,所以不传递任何参数时不产生输出。 printf命令将只产生一个空行,只有格式string参数,但C程序什么都不产生。

请注意,罗伯特的答案是正确的,它也适用于sh 。 你可以(可移植地)进一步简化它:

 for i in "$@" 

相当于:

 for i 

也就是说,你不需要任何东西!

testing( $是命令提示符):

 $ set ab "spaces here" d $ for i; do echo "$i"; done a b spaces here d $ for i in "$@"; do echo "$i"; done a b spaces here d 

我首先在Kernighan和Pike的Unix编程环境中读到这个。

bashhelp for文档:

for NAME [in WORDS ... ;] do COMMANDS; done

如果'in WORDS ...;' 不存在,则假定'in "$@"'

对于简单的情况,你也可以使用shift 。 它将参数列表视为一个队列,每个shift抛出第一个参数,剩下的每个参数的个数都会减less。

 #this prints all arguments while test $# -gt 0 do echo $1 shift done 
 aparse() { while [[ $# > 0 ]] ; do case "$1" in --arg1) varg1=${2} shift ;; --arg2) varg2=true ;; esac shift done } aparse "$@" 

你也可以以数组元素的forms访问它们,例如,如果你不想遍历所有的元素

 argc=$# argv=($@) for (( j=0; j<argc; j++ )); do echo ${argv[j]} done