【问题标题】:Correct way of quoting command substitution引用命令替换的正确方法
【发布时间】:2021-08-12 14:55:40
【问题描述】:

我有一个简单的 bash 脚本,它只输出给脚本的文件名作为位置参数:

#!/usr/bin/env bash

for file; do
    echo "$file"
done

假设我有带空格的文件(例如“f 1”和“f 2”)。我可以使用通配符调用脚本并获得预期的输出:

$ ./script f*
> f 1
> f 2

但是如果我使用命令替换它不起作用:

$ ./script $(echo f*)
> f
> 1
> f
> 2

当我的命令替换输出多个带空格的文件名时,如何正确引用?

编辑:我最终想要的是以随机顺序将文件名传递给脚本(这比仅仅回显它们的名称稍微复杂一些),例如类似的东西:

./script $(ls f* | shuf)

【问题讨论】:

  • 您的示例只是为了演示还是您实际上是在尝试呼应一个 glob?您的要求不是直接可能的,但通常有一些方法可以解决这个限制,具体取决于您真正想做的事情。
  • $(echo f*)根据IFS拆分成词;像"$(echo f*)" 一样引用它会将输出转换为单个参数。你到底想要做什么——你有一个文件数组想要传递给script,还是传递给你?可能有一些方法可以将您的 glob 转换为数组然后使用它,但我需要更多上下文。
  • 我主要是问,因为所提出的问题的解决方案是“只使用 glob”。
  • 我编辑了我的问题。希望这可以澄清它。
  • 命令变电站产生 a 字符串,而不是一系列值。包含空格的字符串绝不等同于由空格分隔的字符串列表。 shuf 在某种意义上会产生一个以换行符分隔的字符串值,但该值不能保证每一行都对应一个完整的文件名,因为文件名可以包含换行符。

标签: bash command-substitution


【解决方案1】:

使用 GNU shuf 和 Bash 4.3+:

readarray -d '' files < <(shuf --zero-terminated --echo f*)
./script "${files[@]}"

--zero-terminated 可以处理任何文件名,readarray 也使用空字节作为分隔符。

对于较旧的 Bash,其中 readarray 不支持 -d 选项:

while IFS= read -r -d '' f; do
    files+=("$f")
done < <(shuf --zero-terminated --echo f*)
./script "${files[@]}"

在有很多文件的极端情况下,这可能会遇到命令行长度限制;在这种情况下,

shuf --zero-terminated --echo f*

可以替换为

printf '%s\0' f* | shuf --zero-terminated

感谢Socowi 指出--echo

【讨论】:

    【解决方案2】:

    要完全正确是非常困难的。一个简单的尝试是使用%q 说明符到printf,但我相信这是一种bashism。不过,您仍然需要使用eval。例如:

    $ cat a.sh
    #!/bin/sh
    
    for x; do echo $((i++)): "$x"; done
    $ ./a.sh *
    0: a.sh
    1: name
    with
    newlines
    2: name with spaces
    $ eval ./a.sh $(printf "%q " *)
    0: a.sh
    1: name
    with
    newlines
    2: name with spaces
    

    【讨论】:

      【解决方案3】:

      这感觉就像XY Problem。也许你应该解释一下真正的问题,有人可能有更好的解决方案。

      尽管如此,使用您发布的内容,我会说阅读this page on why you shouldn't try to parse ls,因为它有相关点;那么我建议一个数组。

      lst=(f*)
      ./script "${lst[@]}"
      

      如果您将其重新解析为子 shell 的输出,这仍然会失败 -

      ./script  $( echo "${lst[@]}" )  #  still  fails same way
      ./script "$( echo "${lst[@]}" )" # *still* fails same way
      

      思考我们如何让它发挥作用......

      【讨论】:

        【解决方案4】:

        你可以使用xargs:

        $ ls -l
        total 4
        -rw-r--r-- 1 root root  0 2021-08-13 00:23 '  file  1'
        -rw-r--r-- 1 root root  0 2021-08-13 00:23 '  file  2'
        -rw-r--r-- 1 root root  0 2021-08-13 00:23 '  file  3'
        -rw-r--r-- 1 root root  0 2021-08-13 00:23 '  file  4'
        -rwxr-xr-x 1 root root 35 2021-08-13 00:25  script
        $ ./script *file*
          file  1
          file  2
          file  3
          file  4
        $ ls *file* | shuf | xargs -d '\n' ./script
          file  4
          file  2
          file  1
          file  3
        

        如果您的xargs 不支持-d

        $ ls *file* | shuf | tr '\n' '\0' | xargs -0 ./script
          file  3
          file  1
          file  4
          file  2
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-05-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多