首页 文章

如何在Bash脚本中迭代参数

提问于
浏览
723

我有一个复杂的命令,我想制作一个shell / bash脚本 . 我可以轻松地用 $1 来写它:

foo $1 args -o $1.ext

我希望能够将多个输入名称传递给脚本 . 什么是正确的方法呢?

当然,我想处理其中包含空格的文件名 .

6 回答

  • 212

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

    #this prints all arguments
    while test $# -gt 0
    do
        echo $1
        shift
    done
    
  • 3

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

    for var in "$@"
    do
        echo "$var"
    done
    

    这将遍历每个参数并在单独的行上打印出来 . $ @的行为类似于$ *,但是当引用时,如果参数中有空格,则它们会被正确分解:

    sh test.sh 1 2 '3 4'
    1
    2
    3 4
    
  • 1191
    aparse() {
    while [[ $# > 0 ]] ; do
      case "$1" in
        --arg1)
          varg1=${2}
          shift
          ;;
        --arg2)
          varg2=true
          ;;
      esac
      shift
    done
    }
    
    aparse "$@"
    
  • 52

    请注意,罗伯特的答案是正确的,它也适用于 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编程环境中读到过这个 .

    bashhelp for 文件中:

    for NAME [in WORDS ...;] do COMMANDS;完成如果'在词汇......;'不存在,然后'in'$ @“'被假设 .

  • 8

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

    argc=$#
    argv=($@)
    
    for (( j=0; j<argc; j++ )); do
        echo ${argv[j]}
    done
    
  • 111

    重写VonC现在删除的答案 .

    Robert Gamble的简洁回答直接涉及这个问题 . 这个放大了包含空格的文件名的一些问题 .

    另见:${1:+"$@"} in /bin/sh

    Basic thesis: "$@" 是正确的, $* (未引用)几乎总是错误的 . 这是因为 "$@" 在参数包含空格时工作正常,而在 $* 不工作时工作方式与 $* 相同 . 在某些情况下, "$*" 也可以,但 "$@" 通常(但不总是)在同一个地方工作 . 不带引号, $@$* 是等价的(而且几乎总是错误的) .

    那么, $*$@"$*""$@" 之间有什么区别?他们都与'all the arguments to the shell'有关,但他们做了不同的事情 . 如果没有引用, $*$@ 会做同样的事情 . 他们将每个'word'(非空白序列)视为一个单独的参数 . 引用的表单完全不同,但是: "$*" 将参数列表视为单个空格分隔的字符串,而 "$@" 几乎完全按照在命令行中指定的参数处理参数 . 当没有位置参数时, "$@" 根本没有扩展; "$*" 扩展为一个空字符串 - 是的,这是有区别的,虽然很难察觉它 . 在引入(非标准)命令 al 之后,请在下面查看更多信息 .

    Secondary thesis: 如果您需要使用空格处理参数然后将它们传递给其他命令,那么您有时需要非标准工具来协助 . (或者你应该谨慎使用数组: "${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
        $
    

    为什么没有't that work? It doesn'工作,因为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中的'command line arguments':

    set -- -new -opt and "arg with space"
    

    这设置了4个选项,' -new ', ' -opt ', ' and ', and ' arg with space ' .
    ]

    嗯,这是一个相当长的答案 - 或许解释是更好的术语 . 可根据要求提供 escape 的源代码(发送电子邮件至gmail dot com的firstname dot lastname) . 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程序不生成任何内容 .

相关问题