Answers:
使用"$@"
来代表所有的参数:
for var in "$@"
do
echo "$var"
done
这将遍历每个参数并将其打印在单独的行上。$ @的行为与$ *相似,不同之处在于,如果使用引号将参数括起来,则它们之间如果有空格,则会被正确分解:
sh test.sh 1 2 '3 4'
1
2
3 4
$var
我能够执行参数。
shift
抛弃$1
,并向下移动所有后续元素。
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
$
为什么不起作用?它不起作用,因为外壳程序在扩展变量之前先处理引号。因此,要使外壳程序注意嵌入在其中的引号$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上,文件名(单个组件)可以包含除slash和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"
(这几天我可能不会这么彻底地使用过转义- -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
”。
]
嗯,这是一个很长的答案 -也许训ege是更好的说法。escape
可根据要求提供源代码(在gmail dot com上发送电子邮件至firstname dot lastname)。的源代码al
非常简单:
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return(0);
}
就这样。它等效于test.sh
Robert Gamble展示的脚本,可以作为shell函数编写(但是,当我第一次编写时,shell函数在本地版本的Bourne 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 "$@"'
假定为。
"$@"
含义,一旦知道了什么for i
意思,它的可读性就不比for i in "$@"
。
for i
会更好,因为它可以节省击键次数。我比较了for i
和的可读性for i in "$@"
。
对于简单的情况,您也可以使用shift
。它将参数列表视为队列。每个shift
参数都将第一个参数抛出,并且其余每个参数的索引都会递减。
#this prints all arguments
while test $# -gt 0
do
echo "$1"
shift
done
shift
在for循环中执行操作,则该元素仍然是数组的一部分,而shift
按预期工作。
echo "$1"
保留参数字符串值中的间距。另外,(一个小问题)这是破坏性的:如果您已将所有参数都移开了,则无法重用这些参数。通常,这不是问题-无论如何,您只处理一次参数列表。
--file myfile.txt
那么此解决方案会更好,例如,$ 1是参数,$ 2是值,并且需要跳过另一个参数时调用shift两次
您也可以将它们作为数组元素访问,例如,如果您不想遍历所有元素
argc=$#
argv=("$@")
for (( j=0; j<argc; j++ )); do
echo "${argv[j]}"
done
./my-script.sh param1 "param2 is a quoted param"
:-使用argv =($ @),您将获得[param1,param2],-使用argv =(“ $ @”),您将获得[param1,param2是带引号的参数]
aparse() {
while [[ $# > 0 ]] ; do
case "$1" in
--arg1)
varg1=${2}
shift
;;
--arg2)
varg2=true
;;
esac
shift
done
}
aparse "$@"
[ $# != 0 ]
,更好。在上面,#$
应$#
为while [[ $# > 0 ]] ...
扩大baz的答案,如果您需要使用索引枚举参数列表(例如搜索特定单词),则无需复制列表或对其进行变异即可做到这一点。
假设要在双破折号(“-”)处分割参数列表,并将破折号前的参数传递给一个命令,并将破折号后的参数传递给另一个命令:
toolwrapper() {
for i in $(seq 1 $#); do
[[ "${!i}" == "--" ]] && break
done || return $? # returns error status if we don't "break"
echo "dashes at $i"
echo "Before dashes: ${@:1:i-1}"
echo "After dashes: ${@:i+1:$#}"
}
结果应如下所示:
$ toolwrapper args for first tool -- and these are for the second
dashes at 5
Before dashes: args for first tool
After dashes: and these are for the second
getopt在脚本中使用命令来格式化任何命令行选项或参数。
#!/bin/bash
# Extract command line options & values with getopt
#
set -- $(getopt -q ab:cd "$@")
#
echo
while [ -n "$1" ]
do
case "$1" in
-a) echo "Found the -a option" ;;
-b) param="$2"
echo "Found the -b option, with parameter value $param"
shift ;;
-c) echo "Found the -c option" ;;
--) shift
break ;;
*) echo "$1 is not an option";;
esac
shift