为什么 xargs 中的这个函数有错误?

为什么 xargs 中的这个函数有错误?

为什么这个命令有错误? get_year 函数可以工作。

xargs -I {} -r -n1 cp {} ${Dst}/videos/$(bash -c 'get_year {}')

错误

ls: cannot access '{}': No such file or directory
ls: cannot access '{}': No such file or directory
ls: cannot access '{}': No such file or directory
ls: cannot access '{}': No such file or directory
ls: cannot access '{}': No such file or directory
ls: cannot access '{}': No such file or directory
ls: cannot access '{}': No such file or directory

get_year 函数

get_year() {
ls -l --time-style=full $1 | tr -s ' ' | cut -d' ' -f6 | cut -d'-' -f 1 

}
export -f get_year

答案1

为什么?

占位符/替换字符串例如{}应该在命令后被替换xargs,即使不正确地放置在单引号 Bash 命令字符串内(这可能很危险...请参阅下面的注释部分的解释)将在子 shell 中运行,例如:

$ printf '%s\n' 'line1' 'line2' 'line3' | xargs -I {} -r -n1 bash -c 'echo "This is {}"'
This is line1
This is line2
This is line3

但是,正确且更安全的方法是将其作为位置参数传递给 Bash 脚本,如下所示:

$ printf '%s\n' 'line1' 'line2' 'line3' | xargs -I {} -r -n1 bash -c 'echo "This is $1"' bash {}
This is line1
This is line2
This is line3

也就是说,如果你将 Bash 脚本放在命令替换中($( ... )) 语法,则命令替换部分会在命令之前执行xargs,即在为占位{}符分配任何值之前执行,因此,它将被处理为文字{}

如果您在 Bash 中启用命令跟踪,如下所示:

$ set -x

然后无需命令替换即可运行:

$ printf '%s\n' 'line1' 'line2' 'line3' | xargs -I {} -r -n1 bash -c 'echo "This is {}"'
+ xargs -I '{}' -r -n1 bash -c 'echo "This is {}"'
+ printf '%s\n' line1 line2 line3
This is line1
This is line2
This is line3

然后,使用命令替换:

$ printf '%s\n' 'line1' 'line2' 'line3' | xargs -I {} -r -n1 $(bash -c 'echo "This is {}"')
+ printf '%s\n' line1 line2 line3
++ bash -c 'echo "This is {}"'
+ xargs -I '{}' -r -n1 This is '{}'
xargs: This: No such file or directory

现在你应该很明显了,该xargs命令正在尝试执行并将参数传递给输出命令替换,因为命令替换得到在 Bash 的命令行上进行了早期扩展

如何?

尽管将命令替换部分移到了 Bash 命令字符串中,以便它在子 shell 中运行(命令替换)来自另一个子壳(Bash 命令字符串)在另一个子壳内(为管道的该部分创建)...因此,推迟执行(远离当前 shell 命令行解析的视角)直到占位符{}被分配了一个值xargs...虽然这可能有效,但我宁愿不这样做。

ls没有讨论你在函数中对输出的明显解析以及可能通过管道传输的类似输出ls,我宁愿不推荐...请参阅这里有更多解释

我建议不要使用管道,也不要使用xargs,而是使用 shell 循环来处理文件,如下所示:

for f in *; do
  if [ -f "$f" ]; then
    y=$(get_year "$f")
    cp -v -- "$f" "${Dst}"/videos/"$y"
    fi
  done

评论

  • 可能过于技术性的通知xargs:在您的情况下,选项-Ieg中的占位符/替换字符串{}实际上并没有被 shell 本身扩展,而是被替换并通过写入标准输出将其替换为命令行本身上的“相同”位置......可以通过以下方式xargs观察到:strace

    $ printf '%s\n' 'line' | strace -f -e 'write' xargs -I {} echo {}
    strace: Process 37373 attached
    [pid 37373] write(1, "line\n", 5line
    )       = 5
    [pid 37373] +++ exited with 0 +++
    --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=37373, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
    +++ exited with 0 +++
    

    ... 这就是为什么在 shell 脚本中包含占位符/替换字符串可能会很危险,因为它会允许恶意或破坏性的命令注入。

    比较一下例如:

    $ printf '%s\n' '; seq 1 9' '; echo ~' | xargs -I {} bash -c 'echo {}'
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    /home/ubuntu
    

    ...命令在其中执行,例如seq 1 9,shell扩展发生,例如~扩展到你的主目录...想象一下,如果其中一个注入的命令恰好是; rm -rf ~

    将其与将占位符/替换字符串作为位置参数正确传递给 shell 脚本的情况进行比较:

    $ printf '%s\n' '; seq 1 9' '; echo ~' | xargs -I {} bash -c 'echo "$1"' bash {}
    ; seq 1 9
    ; echo ~
    

    ...没有执行任何注入的命令,也没有发生shell扩展。

  • 选项-I-n是互斥的,所以您可能不想一起使用它们,尽管如果参数是,-n那么1可能不会造成任何损害......也-I意味着您可能根本-L 1不需要使用。-n 1

答案2

您遇到的错误是由于占位符{}未被 扩展xargs。要解决此问题,您需要-I正确使用 选项xargs。占位符{}将被传递给的输入参数替换,xargs 因此它看起来如下:

xargs -I {} -r -n1 bash -c 'cp "{}" "${Dst}/videos/$(get_year "{}")"' -- {}

相关内容