我有一个复杂的命令,我想制作一个shell/bash脚本.我可以$1
轻松地写出来:
foo $1 args -o $1.ext
我希望能够将多个输入名称传递给脚本.什么是正确的方法呢?
当然,我想处理其中包含空格的文件名.
使用"$@"
来代表所有的参数:
for var in "$@" do echo "$var" done
这将迭代每个参数并在单独的行上打印出来.$ @的行为类似于$*,但是当引用时,如果参数中有空格,则它们会被正确分解:
sh test.sh 1 2 '3 4' 1 2 3 4
重写现在缺失的答案被VonC.
Robert Gamble的简洁回答直接涉及这个问题.这个放大了包含空格的文件名的一些问题.
另请参阅:/ bin/sh中的$ {1:+"$ @"}
基本论点: "$@"
是正确的,$*
(未引用)几乎总是错误的.这是因为"$@"
当参数包含空格时工作正常,并且与$*
它们不包含空格时的工作方式相同.在某些情况下,"$*"
也可以,但"$@"
通常(但不总是)在同一个地方工作.未加引号,$@
并且$*
是等价的(而且几乎总是错的).
那么,是什么样的区别$*
,$@
,"$*"
,和"$@"
?它们都与'shell的所有参数'有关,但它们做了不同的事情.什么时候不引用,$*
并$@
做同样的事情.他们将每个'单词'(非空白的序列)视为一个单独的参数.引用的表单完全不同,但是:"$*"
将参数列表视为单个空格分隔的字符串,而"$@"
对参数的处理几乎与在命令行中指定时完全相同.
"$@"
当没有位置参数时,它会扩展到任何东西; "$*"
扩展为一个空字符串 - 是的,这是有区别的,虽然很难察觉它.在引入(非标准)命令后,请参阅下面的更多信息al
.
次要论文:如果你需要用空格处理参数然后将它们传递给其他命令,那么你有时需要非标准的工具来帮助.(或者你应该谨慎使用数组:"${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在扩展变量之前处理引号.所以,要让shell注意嵌入的引号$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强烈建议路径名中的任何位置都不要有空格或换行符或制表符.这也是标准Unix文件名不包含空格等的原因.
处理可能包含空格和其他麻烦字符的文件名时,你必须非常小心,我很久以前就发现我需要一个在Unix上不标准的程序.我称之为escape
(版本1.1的日期为1989-08-23T16:01:45Z).
这是一个escape
使用的例子- 使用SCCS控制系统.这是一个封面脚本,同时执行delta
(思考签到)和
get
(认为签出).各种论点,特别是-y
(你进行更改的原因)将包含空格和换行符.请注意,该脚本的日期是1992年,因此它使用反向$(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"
(这些天我可能不会非常彻底地使用escape - -e
例如,参数不需要它- 但总的来说,这是我使用的更简单的脚本之一escape
.)
该escape
程序只是输出它的参数,而不是像echo
它一样,但它确保参数受到保护以供使用
eval
(一级eval
;我确实有一个程序执行远程shell执行,并且需要转义输出escape
).
$ escape $var '"./my' 'dir"' '"./anotherdir"' $ escape "$var" '"./my dir" "./anotherdir"' $ escape x y z x y z $
我有另一个程序叫做al
每行一个列出它的参数(它更古老:版本1.1日期1987-01-27T14:35:49).它在调试脚本时最有用,因为它可以插入命令行以查看实际传递给命令的参数.
$ 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个选项,' -new
',' -opt
',' and
'和' arg with space
'.
]
嗯,这是一个相当长的答案 - 也许解释是更好的术语.escape
可根据要求提供的源代码(通过电子邮件发送到gmail dot com的firstname dot lastname).源代码al
非常简单:
#includeint main(int argc, char **argv) { while (*++argv != 0) puts(*argv); return(0); }
就这样.它相当于test.sh
Robert Gamble所展示的脚本,并且可以编写为shell函数(但是当我第一次编写时,本地版本的Bourne shell中不存在shell函数al
).
另请注意,您可以编写al
为简单的shell脚本:
[ $# != 0 ] && printf "%s\n" "$@"
需要条件,以便在不传递参数时不产生输出.该printf
命令将生成一个只有格式字符串参数的空行,但C程序不生成任何内容.
请注意,罗伯特的答案是正确的,它也适用sh
.您可以(便携地)进一步简化它:
for i in "$@"
相当于:
for i
即,你什么都不需要!
测试($
是命令提示符):
$ set a b "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
抛出第一个参数,剩下的每个参数的数量递减.
#this prints all arguments
while test $# -gt 0
do
echo "$1"
shift
done
您还可以将它们作为数组元素进行访问,例如,如果您不想迭代所有这些元素
argc=$# argv=("$@") for (( j=0; j
我认为argv =($ @)行应为argv =(“ $ @”)其他带有空格的明智args,无法正确处理