如何在Bash脚本中迭代参数

我有一个复杂的命令,我想做一个shell/bash脚本。我可以很容易地用0表示:

foo $1 args -o $1.ext

我希望能够向脚本传递多个输入名称。正确的做法是什么?

当然,我想要处理包含空格的文件名。

823632 次浏览

使用"$@"表示所有的参数:

for var in "$@"doecho "$var"done

这将遍历每个参数,并将其打印在单独的行上。$@的行为类似于$*,除了当引用参数时,如果参数中有空格,则会被正确拆分:

sh test.sh 1 2 '3 4'123 4

重写一个现在删除的answer by VonC.

罗伯特赌博的简洁回答直接处理了这个问题。它放大了包含空格的文件名的一些问题

参见:/bin/sh中的${1:+"$@"}

基本理论: "$@"是正确的,而$*(未引用)几乎总是错误的。这是因为当参数包含空格时,"$@"工作正常当它们不存在时,效果与第0条相同。在某些情况下,"$*"也可以,但"$@"通常(但不是总是)在相同的地方工作。不加引号,$@$*是等价的(而且几乎总是错误的) 那么,$*$@"$*""$@"之间的区别是什么?它们都与“shell的所有参数”相关,但它们做的事情不同。当不引用时,$*$@做同样的事情。它们将每个“单词”(非空格序列)视为一个单独的参数。但是,引号形式有很大不同:"$*"将参数列表作为一个空格分隔的字符串,而"$@"几乎完全按照在命令行中指定的参数来处理。当没有位置参数时,"$@"展开为零;"$*"扩展为空字符串—是的,是有区别的,尽管很难察觉。在引入al(非标准)命令后,请参阅下面的更多信息

二级论文:如果你需要用空格和then处理参数将它们传递给其他命令,然后有时需要使用非标准命令辅助工具。(或者你应该小心地使用数组:"${array[@]}"的行为类似于"$@")

例子:

    $ mkdir "my dir" anotherdir$ lsanotherdir      my dir$ cp /dev/null "my dir/my file"$ cp /dev/null "anotherdir/myfile"$ ls -Fltrtotal 0drwxr-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 $varls: "./anotherdir": No such file or directoryls: "./my: No such file or directoryls: 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$
当你的文件名是"他说,“别这样!”"(带引号,双引号和空格)
    $ cp /dev/null "He said, \"Don't do this!\""$ lsHe said, "Don't do this!"       anotherdir                      my dir$ ls -ltotal 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 anotherdirdrwxr-xr-x   3 jleffler  staff  102 Nov  1 14:55 my dir$
shell(所有的shell)并不是特别容易处理这种情况的东西,所以(足够有趣的是)许多Unix程序并没有做得很好处理它们。在Unix上,文件名(单个组件)可以包含除斜杠和null '\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` indeledit)    eflag="-e";;esac
sflag="-s"for arg in "$@"docase "$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\"`";;esacdone
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 zx y z$
我有另一个名为al的程序,它每行列出一个参数(它甚至更古老:版本1.1日期1987-01-27T14:35:49)。它在调试脚本时最有用,因为它可以插入到命令行查看实际传递给命令的参数是什么
    $ echo "$var""./my dir" "./anotherdir"$ al $var"./mydir""./anotherdir"$ al "$var""./my dir" "./anotherdir"$

(< em >补充道:现在,为了显示各种"$@"符号之间的区别,这里还有一个例子:

$ cat xx.shset -xal $@al $*al "$*"al "$@"$ sh xx.sh     *      */*+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my fileHesaid,"Don'tdothis!"anotherdirmydirxx.shanotherdir/myfilemydir/myfile+ al He said, '"Don'\''t' do 'this!"' anotherdir my dir xx.sh anotherdir/myfile my dir/my fileHesaid,"Don'tdothis!"anotherdirmydirxx.shanotherdir/myfilemydir/myfile+ 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!"anotherdirmy dirxx.shanotherdir/myfilemy dir/my file$

请注意,命令行上没有任何内容保留了**/*之间的原始空白。另外,注意你可以在shell中使用以下命令改变“命令行参数”:

set -- -new -opt and "arg with space"
< p >这集4选项,' # 0”,“1”,“2”,和“# 3”。
) < / p > 嗯,这是一个相当长的回答 -也许注释是更好的术语。源代码escape可按要求提供(电子邮件到firstname dot姓氏在gmail.com)。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命令将生成一个只有format字符串参数的空行,但C程序什么也不生成。

请注意,Robert的答案是正确的,它也适用于sh。你可以(可移植地)进一步简化它:

for i in "$@"

等价于:

for i

也就是说,你什么都不需要!

测试($是命令提示符):

$ set a b "spaces here" d$ for i; do echo "$i"; doneabspaces hered$ for i in "$@"; do echo "$i"; doneabspaces hered

我第一次读到Kernighan和Pike写的Unix编程环境

bashhelp for中记录了以下内容:

# 0

如果'in WORDS ...;'不存在,则假设'in "$@"'

对于简单的情况,您也可以使用shift。它将参数列表视为队列。每个shift抛出第一个参数

.

.

.
#this prints all argumentswhile test $# -gt 0doecho "$1"shiftdone
aparse() {while [[ $# > 0 ]] ; docase "$1" in--arg1)varg1=${2}shift;;--arg2)varg2=true;;esacshiftdone}
aparse "$@"

您还可以将它们作为数组元素访问,例如,如果您不想遍历所有这些元素

argc=$#argv=("$@")
for (( j=0; j<argc; j++ )); doecho "${argv[j]}"done

放大baz的回答,如果您需要用索引枚举参数列表(例如搜索特定的单词),您可以在不复制列表或更改列表的情况下完成此操作。

假设你想用双破折号("——")分割一个参数列表,并将破折号之前的参数传递给一个命令,将破折号之后的参数传递给另一个命令:

 toolwrapper() {for i in $(seq 1 $#); do[[ "${!i}" == "--" ]] && breakdone || 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 seconddashes at 5Before dashes: args for first toolAfter dashes: and these are for the second

getopt在脚本中使用命令格式化任何命令行选项或参数。< / p >

#!/bin/bash# Extract command line options & values with getopt#set -- $(getopt -q ab:cd "$@")#echowhile [ -n "$1" ]docase "$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" ;;--) shiftbreak ;;*) echo "$1 is not an option";;esacshift

对参数数量变量$#进行循环也可以。

#! /bin/bash
for ((i=1; i<=$#; i++))doprintf "${!i}\n"done
./test.sh 1 2 '3 4'

输出:

123 4