代码之家  ›  专栏  ›  技术社区  ›  T.J. Crowder

如何在bash脚本中将文件名正确地传递给其他程序?

  •  3
  • T.J. Crowder  · 技术社区  · 15 年前

    Bash脚本中应该使用什么成语 在处理文件名时用脚本的参数为另一个程序建立命令行 正确地

    ,我的意思是处理带有空格或奇数字符的文件名,而不会无意中导致另一个程序将它们作为单独的参数处理(或者,在 < > -毕竟,如果 不幸的 文件名字符,如果正确转义-做更糟的事情)。

    正确处理文件名:假设这个脚本( foo bar ,假设在路径中)通过 的输入参数,并将任何看起来像标志的内容移到前面,然后调用 酒吧 :

    #!/bin/bash
    # This is clearly wrong
    
    FILES=
    FLAGS=
    for ARG in "$@"; do
        echo "foo: Handling $ARG"
        if [ x${ARG:0:1} = "x-" ]; then
            # Looks like a flag, add it to the flags string
            FLAGS="$FLAGS $ARG"
        else
            # Looks like a file, add it to the files string
            FILES="$FILES $ARG"
        fi
    done
    
    # Call bar with the flags and files (we don't care that they'll
    # have an extra space or two)
    CMD="bar $FLAGS $FILES"
    echo "Issuing: $CMD"
    $CMD
    

    只是个例子 ;还有很多时候,我们需要对一堆arg执行这个和那个操作,然后将它们传递给其他程序。)

    one
    two
    three and a half
    four < five

    那当然是命令 foo * 任务失败得很惨:

    foo: Handling four < five
    foo: Handling one
    foo: Handling three and a half
    foo: Handling two
    Issuing: bar   four < five one three and a half two

    发布这个命令,结果不会如我们所料。

    那是什么?限制条件:

    1. 我想保留这个成语 尽可能简单
    2. 我在找一个通用的成语,所以我编了一个 酒吧 程序和上面的人工示例,而不是使用一个真实的场景,在这个场景中,人们可能很容易(并且合理地)沿着尝试在目标程序中使用特性的路线前进。
    3. 我想坚持Bash脚本,我不想调用Perl、Python等。
    4. 我可以依赖(其他)标准的*nix实用程序,比如 xargs sed ,或 tr
    5. 如果重要的话,目标程序可能也是Bash脚本,也可能不是。我不认为这有什么关系。。。
    6. 我不仅仅想处理空格,我还想正确处理奇怪的字符。
    7. 如果它不处理嵌入了nul字符的文件名(字面上是字符代码0),我就不介意了。如果有人在他们的文件系统中创建了一个,我不担心处理它,他们已经尝试了 真的很难 把事情搞砸。


    编辑 Ignacio Vazquez-Abrams 给我指了指Bash FAQ entry #50 ,经过一些阅读和实验,似乎表明一种方法是 Bash arrays :

    #!/bin/bash
    # This appears to work, using Bash arrays
    
    # Start with blank arrays
    FILES=()
    FLAGS=()
    for ARG in "$@"; do
        echo "foo: Handling $ARG"
        if [ x${ARG:0:1} = "x-" ]; then
            # Looks like a flag, add it to the flags array
            FLAGS+=("$ARG")
        else
            # Looks like a file, add it to the files array
            FILES+=("$ARG")
        fi
    done
    
    # Call bar with the flags and files
    echo "Issuing (but properly delimited, not exactly as this appears): bar ${FLAGS[@]} ${FILES[@]}"
    bar "${FLAGS[@]}" "${FILES[@]}"
    

    这是正确和合理的吗?或者我是不是依赖于上面的环境因素,这些因素会在以后咬我一口。它 似乎 相当地

    (如果以上是正确的,你想取消删除你的答案,伊格纳西奥,我会接受它,只要我还没有接受任何其他的,虽然我坚持我的声明只链接答案。)

    3 回复  |  直到 8 年前
        1
  •  5
  •   Community Mohan Dere    8 年前

    为什么要“建立”一个命令?使用适当的

    从脚本中选择的行(忽略未更改的行):

    if [[ ${ARG:0:1} == - ]]; then    # using a Bash idiom
    FLAGS+=("$ARG")                   # add an element to an array
    FILES+=("$ARG")
    echo "Issuing: bar \"${FLAGS[@]}\" \"${FILES[@]}\""
    bar "${FLAGS[@]}" "${FILES[@]}"
    

    有关以这种方式使用数组的快速演示:

    $ a=(aaa 'bbb ccc' ddd); for arg in "${a[@]}"; do echo "..${arg}.."; done
    

    输出:

    ..aaa..
    ..bbb ccc..
    ..ddd..
    

    请看 BashFAQ/050 关于将命令放入变量。脚本不起作用的原因是无法引用引用字符串中的参数。如果要在那里加引号,它们将被视为字符串本身的一部分,而不是分隔符。在参数未被引用的情况下,分词完成,包含空格的参数被视为多个参数。在任何情况下,带有“<”、“>”或“|”的参数都不是问题,因为重定向和管道在变量展开之前执行,因此它们被视为字符串中的字符。

    一些附加说明:

    • 使用小写(或混合大小写)变量名以减少它们与shell内置变量冲突的可能性。
    • here ). 但是,在Bash中,使用双括号。它们提供了额外的功能(参见我的回答 here
    • 使用 getopts 就像我建议的那样。尽管我知道这只是一个例子,但您的脚本将无法处理带参数的开关。
    • 这个 for ARG in "$@" for ARG (但我更喜欢更明确版本的可读性)。
        2
  •  1
  •   Gordon Davisson    15 年前

    BashFAQ #50 (也可能 #35 VAR="foo bar baz" ,使用 VAR=("foo" "bar" "baz" );使用数组,而不是 $VAR "${VAR[@]}" . 下面是使用此方法的示例脚本的工作版本:

    #!/bin/bash
    # This is clearly wrong
    
    FILES=()
    FLAGS=()
    for ARG in "$@"; do
        echo "foo: Handling $ARG"
        if [ x${ARG:0:1} = "x-" ]; then
            # Looks like a flag, add it to the flags array
            FLAGS=("${FLAGS[@]}" "$ARG") # FLAGS+=("$ARG") would also work in bash 3.1+, as Dennis pointed out
        else
            # Looks like a file, add it to the files string
            FILES=("${FILES[@]}" "$ARG")
        fi
    done
    
    # Call bar with the flags and files (we don't care that they'll
    # have an extra space or two)
    CMD=("bar" "${FLAGS[@]}" "${FILES[@]}")
    echo "Issuing: ${CMD[*]}"
    "${CMD[@]}"
    

    注意,在 echo "${VAR[*]}" 而不是 [@] 因为这里不需要保留分词。如果您想以明确的形式打印/记录命令,这将更加混乱。

    此外,这也使您无法在build命令中构建重定向或其他特殊的shell选项——如果您添加 >outfile 对于文件数组,它将被视为另一个命令参数,而不是shell重定向。如果您需要以编程方式构建这些,请做好头痛的准备。

        3
  •  0
  •   Å imon Tóth    15 年前

    getopts "file name.txt" ). 奇怪的字符也应该可以工作,假设它们是正确转义的( ls -b

    推荐文章