为什么文件移动/复制功能在使用“*”通配符时一次只能移动一个文件?

nev*_*nd9 1 command-line shell bash function wildcards

function mv1 { mv -n "$1" "targetdir" -v |wc -l ;}
mv1 *.png
Run Code Online (Sandbox Code Playgroud)

它只会移动.png它找到的第一个文件,而不是所有文件。

如何使命令适用于所有与通配符匹配的文件?

Gil*_*il' 6

mv1 *.png首先将通配符模式扩展*.png为匹配的文件名列表,然后将该文件名列表传递给函数。

然后,在函数内部$1意味着:将第一个参数传递给函数,在它包含空格的地方拆分它,并替换任何包含通配符并通过匹配文件名列表匹配至少一个文件名的空格分隔部分。听起来很复杂?确实如此,而且这种行为只是偶尔有用,而且经常有问题。这种拆分和匹配行为仅$1在双引号之外发生时才会发生,因此解决方法很简单:使用双引号。除非你有充分的理由不这样做,否则总是在变量替换周围加上双引号

例如,如果当前目录包含两个文件A* algorithm.pnggraph1.png,则作为函数的第一个参数和第二个参数mv1 *.png传递。然后拆分为和。模式匹配,并且不包含通配符。所以,在函数结束时行驶的论据,,,和。如果您将函数更正为A* algorithm.pnggraph1.png$1A*algorithm.pngA*A* algorithm.pngalgorithm.pngmv-nA* algorithm.pngalgorithm.pngtargetdir-v

function mv1 { mv -n "$1" "targetdir" -v |wc -l ;}
Run Code Online (Sandbox Code Playgroud)

然后它将正确移动第一个文件。

要处理所有参数,请告诉 shell 处理所有参数,而不仅仅是第一个。您可以使用"$@"表示传递给函数的参数的完整列表。

function mv1 { mv -n "$@" "targetdir" -v |wc -l ;}
Run Code Online (Sandbox Code Playgroud)

这几乎是正确的,但如果文件名碰巧以字符开头,它仍然会失败-,因为mv会将该参数视为一个选项。传递--mv告诉它“在这一点之后没有更多选择”。这是大多数命令支持的非常常见的约定。

function mv1 { mv -n -v -- "$@" "targetdir" |wc -l ;}
Run Code Online (Sandbox Code Playgroud)

剩下的问题是,如果mv失败,该函数返回成功状态,因为管道左侧的命令的退出状态被忽略。在 bash(或 ksh)中,您可以使用set -o pipefail使管道失败。请注意,设置此选项可能会导致在同一 shell 中运行的其他代码失败,因此您应该在函数中本地设置它,这从 bash 4.4 开始是可能的。

function mv1 {
  local -
  set -o pipefail
  mv -n -v -- "$@" "targetdir" | wc -l
}
Run Code Online (Sandbox Code Playgroud)

在早期版本中,设置pipefail会很脆弱,因此最好进行PIPESTATUS显式检查。

function mv1 {
  mv -n -v -- "$@" "targetdir" | wc -l
  ((!${PIPESTATUS[0] && !${PIPESTATUS[1]}}))
}
Run Code Online (Sandbox Code Playgroud)