如何在 Bash 脚本中迭代参数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/255898/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to iterate over arguments in a Bash script
提问by Thelema
I have a complex command that I'd like to make a shell/bash script of. I can write it in terms of $1
easily:
我有一个复杂的命令,我想为其制作一个 shell/bash 脚本。我可以$1
很容易地写成:
foo args -o .ext
I want to be able to pass multiple input names to the script. What's the right way to do it?
我希望能够将多个输入名称传递给脚本。正确的做法是什么?
And, of course, I want to handle filenames with spaces in them.
而且,当然,我想处理带有空格的文件名。
回答by Robert Gamble
Use "$@"
to represent all the arguments:
使用"$@"
来代表所有的参数:
for var in "$@"
do
echo "$var"
done
This will iterate over each argument and print it out on a separate line. $@ behaves like $* except that when quoted the arguments are broken up properly if there are spaces in them:
这将迭代每个参数并将其打印在单独的行上。$@ 的行为与 $* 类似,只是在引用时,如果其中有空格,则参数会被正确分解:
sh test.sh 1 2 '3 4'
1
2
3 4
回答by Jonathan Leffler
Rewrite of a now-deleted answerby VonC.
Robert Gamble's succinct answer deals directly with the question. This one amplifies on some issues with filenames containing spaces.
罗伯特·甘布尔( Robert Gamble) 的简洁回答直接解决了这个问题。这个放大了文件名包含空格的一些问题。
See also: ${1:+"$@"} in /bin/sh
Basic thesis:"$@"
is correct, and $*
(unquoted) is almost always wrong.
This is because "$@"
works fine when arguments contain spaces, and
works the same as $*
when they don't.
In some circumstances, "$*"
is OK too, but "$@"
usually (but not
always) works in the same places.
Unquoted, $@
and $*
are equivalent (and almost always wrong).
基本论点:"$@"
是正确的,并且$*
(未引用)几乎总是错误的。这是因为"$@"
当参数包含空格时工作正常,并且与不包含空格时工作相同$*
。在某些情况下,"$*"
也可以,但"$@"
通常(但不总是)在相同的地方工作。不加引号,$@
并且$*
是等效的(并且几乎总是错误的)。
So, what is the difference between $*
, $@
, "$*"
, and "$@"
? They are all related to 'all the arguments to the shell', but they do different things. When unquoted, $*
and $@
do the same thing. They treat each 'word' (sequence of non-whitespace) as a separate argument. The quoted forms are quite different, though: "$*"
treats the argument list as a single space-separated string, whereas "$@"
treats the arguments almost exactly as they were when specified on the command line.
"$@"
expands to nothing at all when there are no positional arguments; "$*"
expands to an empty string — and yes, there's a difference, though it can be hard to perceive it.
See more information below, after the introduction of the (non-standard) command al
.
那么,是什么样的区别$*
,$@
,"$*"
,和"$@"
?它们都与“外壳的所有参数”有关,但它们做不同的事情。当不带引号的,$*
和$@
做同样的事情。他们将每个“单词”(非空白序列)视为一个单独的参数。但是,引用形式完全不同:"$*"
将参数列表视为单个空格分隔的字符串,而"$@"
将参数几乎完全按照在命令行中指定时的方式处理。
"$@"
当没有位置参数时,完全没有扩展;"$*"
扩展为一个空字符串——是的,这是有区别的,尽管可能很难察觉。在引入(非标准)命令之后,请参阅下面的更多信息al
。
Secondary thesis:if you need to process arguments with spaces and then
pass them on to other commands, then you sometimes need non-standard
tools to assist. (Or you should use arrays, carefully: "${array[@]}"
behaves analogously to "$@"
.)
次要论文:如果您需要处理带空格的参数,然后将它们传递给其他命令,那么有时您需要非标准工具来提供帮助。(或者你应该小心地使用数组:"${array[@]}"
行为类似于"$@"
.)
Example:
例子:
$ 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
$
Why doesn't that work?
It doesn't work because the shell processes quotes before it expands
variables.
So, to get the shell to pay attention to the quotes embedded in $var
,
you have to use eval
:
为什么这不起作用?它不起作用,因为 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
$
This gets really tricky when you have file names such as "He said,
"Don't do this!"
" (with quotes and double quotes and spaces).
当您有诸如“ 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
$
The shells (all of them) do not make it particularly easy to handle such
stuff, so (funnily enough) many Unix programs do not do a good job of
handling them.
On Unix, a filename (single component) can contain any characters except
slash and NUL '\0'
.
However, the shells strongly encourage no spaces or newlines or tabs
anywhere in a path names.
It is also why standard Unix file names do not contain spaces, etc.
shell(所有这些)并没有让处理这些东西变得特别容易,所以(很有趣)许多 Unix 程序在处理它们方面做得不好。在 Unix 上,文件名(单个组件)可以包含除斜杠和 NUL 之外的任何字符'\0'
。但是,shell 强烈建议路径名中的任何地方都没有空格、换行符或制表符。这也是标准 Unix 文件名不包含空格等的原因。
When dealing with file names that may contain spaces and other
troublesome characters, you have to be extremely careful, and I found
long ago that I needed a program that is not standard on Unix.
I call it escape
(version 1.1 was dated 1989-08-23T16:01:45Z).
在处理可能包含空格和其他麻烦字符的文件名时,你必须非常小心,我很久以前就发现我需要一个在 Unix 上不是标准的程序。我称之为escape
(1.1 版的日期为 1989-08-23T16:01:45Z)。
Here is an example of escape
in use - with the SCCS control system.
It is a cover script that does both a delta
(think check-in) and a
get
(think check-out).
Various arguments, especially -y
(the reason why you made the change)
would contain blanks and newlines.
Note that the script dates from 1992, so it uses back-ticks instead of
$(cmd ...)
notation and does not use #!/bin/sh
on the first line.
这是一个escape
使用中的例子- 与 SCCS 控制系统一起使用。它是一个封面脚本,可以执行delta
(think check-in) 和 a
get
(think 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 $ escape $var
'"./my' 'dir"' '"./anotherdir"'
$ escape "$var"
'"./my dir" "./anotherdir"'
$ escape x y z
x y z
$
.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"
(I would probably not use escape quite so thoroughly these days - it is
not needed with the -e
argument, for example - but overall, this is
one of my simpler scripts using escape
.)
(这些天我可能不会如此彻底地使用转义 - 例如,它不需要-e
参数 - 但总的来说,这是我使用escape
. 的更简单的脚本之一。)
The escape
program simply outputs its arguments, rather like echo
does, but it ensures that the arguments are protected for use with
eval
(one level of eval
; I do have a program which did remote shell
execution, and that needed to escape the output of escape
).
该escape
程序只是简单地输出它的参数,而不是像doecho
那样,但它确保参数受到保护以用于
eval
(一个级别的eval
;我确实有一个执行远程 shell 执行的程序,并且需要转义 的输出escape
)。
$ echo "$var"
"./my dir" "./anotherdir"
$ al $var
"./my
dir"
"./anotherdir"
$ al "$var"
"./my dir" "./anotherdir"
$
I have another program called al
that lists its arguments one per line
(and it is even more ancient: version 1.1 dated 1987-01-27T14:35:49).
It is most useful when debugging scripts, as it can be plugged into a
command line to see what arguments are actually passed to the command.
我有另一个程序al
,它每行列出一个参数(它甚至更古老:1987-01-27T14:35:49 的 1.1 版)。它在调试脚本时最有用,因为它可以插入命令行以查看实际传递给命令的参数。
$ 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
$
[Added:And now to show the difference between the various "$@"
notations, here is one more example:
[补充:现在为了显示各种"$@"
符号之间的区别,这里再举一个例子:
set -- -new -opt and "arg with space"
Notice that nothing preserves the original blanks between the *
and */*
on the command line. Also, note that you can change the 'command line arguments' in the shell by using:
请注意,没有任何内容会保留命令行上*
和之间的原始空白*/*
。另请注意,您可以使用以下命令更改 shell 中的“命令行参数”:
#include <stdio.h>
int main(int argc, char **argv)
{
while (*++argv != 0)
puts(*argv);
return(0);
}
This sets 4 options, '-new
', '-opt
', 'and
', and 'arg with space
'.
]
这将设置 4 个选项,“ -new
”、“ -opt
”、“ and
”和“ arg with space
”。
]
Hmm, that's quite a long answer- perhaps exegesisis the better term.
Source code for escape
available on request (email to firstname dot
lastname at gmail dot com).
The source code for al
is incredibly simple:
嗯,这是一个很长的答案——也许解释是更好的术语。escape
可应要求提供源代码(通过电子邮件发送到 gmail dot com 上的 firstname dot lastname)。的源代码al
非常简单:
[ $# != 0 ] && printf "%s\n" "$@"
That's all. It is equivalent to the test.sh
script that Robert Gamble showed, and could be written as a shell function (but shell functions didn't exist in the local version of Bourne shell when I first wrote al
).
就这样。它相当于test.sh
Robert Gamble 展示的脚本,可以写成一个 shell 函数(但是当我第一次编写 时,本地版本的 Bourne shell 中不存在 shell 函数al
)。
Also note that you can write al
as a simple shell script:
另请注意,您可以编写al
一个简单的 shell 脚本:
for i in "$@"
The conditional is needed so that it produces no output when passed no arguments. The printf
command will produce a blank line with only the format string argument, but the C program produces nothing.
需要条件,以便在不传递参数时不产生输出。该printf
命令将生成一个仅包含格式字符串参数的空行,但 C 程序不会生成任何内容。
回答by Alok Singhal
Note that Robert's answer is correct, and it works in sh
as well. You can (portably) simplify it even further:
请注意,罗伯特的答案是正确的,并且也适用sh
。您可以(可移植地)进一步简化它:
for i
is equivalent to:
相当于:
$ 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
I.e., you don't need anything!
也就是说,你不需要任何东西!
Testing ($
is command prompt):
测试($
是命令提示符):
#this prints all arguments
while test $# -gt 0
do
echo ""
shift
done
I first read about this in Unix Programming Environmentby Kernighan and Pike.
我第一次在Kernighan 和 Pike 的Unix Programming Environment 中读到了这个。
In bash
, help for
documents this:
在 中bash
,help for
记录了这一点:
for NAME [in WORDS ... ;] do COMMANDS; done
If
'in WORDS ...;'
is not present, then'in "$@"'
is assumed.
for NAME [in WORDS ... ;] do COMMANDS; done
如果
'in WORDS ...;'
不存在,则'in "$@"'
假定。
回答by nuoritoveri
For simple cases you can also use shift
.
It treats the argument list like a queue. Each shift
throws the first argument out and the
index of each of the remaining arguments is decremented.
对于简单的情况,您也可以使用shift
. 它将参数列表视为队列。每个都shift
抛出第一个参数,并且每个剩余参数的索引递减。
argc=$#
argv=("$@")
for (( j=0; j<argc; j++ )); do
echo "${argv[j]}"
done
回答by baz
You can also access them as an array elements, for example if you don't want to iterate through all of them
您还可以将它们作为数组元素访问,例如,如果您不想遍历所有这些元素
aparse() {
while [[ $# > 0 ]] ; do
case "" in
--arg1)
varg1=
shift
;;
--arg2)
varg2=true
;;
esac
shift
done
}
aparse "$@"
回答by g24l
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:$#}"
}
回答by Rich Kadel
Amplifying on baz's answer, if you need to enumerate the argument list with an index (such as to search for a specific word), you can do this without copying the list or mutating it.
放大 baz 的答案,如果您需要使用索引枚举参数列表(例如搜索特定单词),您可以在不复制列表或对其进行变异的情况下执行此操作。
Say you want to split an argument list at a double-dash ("--") and pass the arguments before the dashes to one command, and the arguments after the dashes to another:
假设您想在双破折号(“--”)处拆分参数列表,并将破折号之前的参数传递给一个命令,并将破折号之后的参数传递给另一个命令:
$ 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
Results should look like this:
结果应如下所示:
#!/bin/bash
# Extract command line options & values with getopt
#
set -- $(getopt -q ab:cd "$@")
#
echo
while [ -n "" ]
do
case "" in
-a) echo "Found the -a option" ;;
-b) param=""
echo "Found the -b option, with parameter value $param"
shift ;;
-c) echo "Found the -c option" ;;
--) shift
break ;;
*) echo " is not an option";;
esac
shift
回答by JimmyLandStudios
getoptUse command in your scripts to format any command line options or parameters.
getopt在脚本中使用 command 来格式化任何命令行选项或参数。
##代码##