我有一个复杂的命令,我想把它做成一个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。</sup>。
罗伯特-甘布尔(Robert Gamble)的回答简洁明了,直接处理了问题。
这个答案对含有空格的文件名的一些问题进行了补充。
也请参见。${1:+"$@"} in /bin/sh
基本论点: "$@"
是正确的,而$*
(未引用)几乎总是错误的。
这是因为"$@"
在参数包含空格时工作正常,而且
在参数不包含空格时与$*
的作用相同。
在某些情况下,"$*"
也可以,但"$@"
通常(但不总是)在相同的地方工作。
通常(但不总是)在同样的地方起作用。
不加引号,$@
和$*
是等同的(而且几乎总是错误的)。
那么,$*
、$@
、"$*"
和"$@"
之间有什么区别? 它们都与'shell的所有参数有关',但它们做的是不同的事情。当不引用时,$*
和$@
做同样的事情。 它们把每个'字'(非空格的序列)作为一个单独的参数。 但加引号的形式是完全不同的:"$*"
将参数列表作为单个空格分隔的字符串处理,而"$@"
几乎完全按照命令行上指定的参数处理。
"$@"
在没有位置参数的情况下,完全没有扩展;"$*"
扩展为一个空字符串&mdash;是的,有区别,尽管很难察觉。
更多信息见下文,在介绍了(非标准)命令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
$
贝壳(所有的贝壳)并不特别容易处理这种
的东西,所以(有趣的是)许多Unix程序都不能很好地处理它们。
处理它们。
在Unix中,文件名(单一成分)可以包含任何字符,除了
斜线和NUL '
0'。 然而,shells强烈建议在路径名的任何地方不要有空格或换行符或制表符 路径名中的任何地方。 这也是为什么标准的Unix文件名不包含空格等。 当处理可能包含空格和其他麻烦字符的文件名时 当处理可能包含空格和其他麻烦字符的文件名时,你必须非常小心,我发现 很久以前,我发现我需要一个在Unix上不标准的程序。 我把它叫做
escape(1.1版的日期是1989-08-23T16:01:45Z)。 下面是一个使用
escape的例子--在SCCS控制系统中。 它是一个覆盖脚本,同时进行 "delta"(认为是*check-in*)和
get'(认为是check-out)。
各种参数,特别是-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
下是不需要的。
例如,不需要使用 "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'的程序,每行列出一个参数 (它甚至更古老:1987-01-27T14:35:49的1.1版)。 它在调试脚本时最有用,因为它可以插入到一个 命令行中,以查看实际传递给命令的参数。
$ 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
的源代码可供索取(请发电子邮件至firstname dot
lastname at gmail dot com)。
`al'的源代码简单得令人难以置信。
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return(0);
}
这就是全部。 它相当于Robert Gamble展示的test.sh
脚本,可以写成shell函数(但在我第一次写al
时,本地版本的Bourne shell中不存在shell函数)。
还要注意,你可以把al
写成一个简单的shell脚本。
[ $# != 0 ] && printf "%s\n" "$@"
需要设置条件,以便在没有传递参数时不产生输出。 printf
命令在只有格式字符串参数的情况下会产生一个空行,但C程序什么都不产生。
请注意,Robert的答案是正确的,它在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 "$@"'
。