如何迭代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编程环境中读到这个。
在bash
, help 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