移动与捕获目录的模式匹配的文件

移动与捕获目录的模式匹配的文件

考虑:

$ ls
foo  xyfooz.tex
$ find . -maxdepth 1 -type f -name '*foo*' -exec mv {} foo \;
$ ls ./foo*
xyfooz.tex

我试图仅使用 mv 而不是 find 来实现相同的指令序列:

$ ls
foo  xyfooz.tex
$ mv *foo* foo
mv: cannot stat '*foo*': No such file or directory
$ ls ./foo*
xyfooz.tex

另外(编辑),

$ ls -F
foo/  xyfooz.tex*
$ mv -v *foo* foo

mv: cannot move 'foo' to a subdirectory of itself, 'foo/foo'
'xyfooz.tex' -> 'foo/xyfooz.tex'

如何确保不出现“stat”警告。换句话说,我想我应该改进模式以将源限制为文件而不是目录?

GNU bash,4.3.48(1)-发布(x86_64-pc-linux-gnu)

$ cat /etc/os-release
NAME="Linux Mint"
VERSION="18.3 (Sylvia)"

答案1

mv

cannot move 'foo' to a subdirectory of itself在这种情况下是无害的,您可以忽略它。我的意思是mv尽管有警告,但仍会移动其余部分。但是退出状态不会是0,这可能是脚本中的巨大障碍,因为如果您想要中止或采取纠正措施(如果mv不成功),这可能会成为一大障碍。

这已经在评论中说:您可以mv通过使用 重定向 stderr 来静音2>/dev/null;其他警告或错误也将被重定向。

我认为您不能轻易“改进模式以将源限制为文件而不是目录”。您仍然可以采取与文字不匹配的方法foo。以下方法与文件或目录无关;只与名称有关,因为 glob 处理名称;因此在某些情况下可能还不够。

glob*foo*匹配四种分离类型的对象:

  1. foo本身
  2. foo 只带前缀,例如bar-foo– 你可以通过以下方式匹配它们*?foo
  3. 带有前缀和后缀的 foo,例如bar-foo-baz*?foo?*
  4. foo 仅带后缀,例如foo-bazfoo?*

要排除,foo只需要后三个(2-4),因此第一种方法可能是:

mv *?foo *?foo?* foo?* foo/

除非你nullglob设置了 shell 选项,否则任何模式都需要匹配某些内容,否则它将被mv逐字传递。你不希望这样。解决方案是事先运行以下命令:

shopt -s nullglob

此 shell 选项将使任何不匹配的 glob 扩展为无。我建议您dotglob也研究一下该选项。

请注意*?foo*匹配(2-3)。同样*foo?*匹配(3-4)。另外请考虑--告知mv所有后续参数都不是选项。这样,像这样的文件--foo就不会被解释为(未知)选项。这导致两个更好的命令:

mv -- *?foo* foo?* foo/

或者

mv -- *?foo *foo?* foo/

选择哪一个都没关系。只是不要使用mv *?foo* *foo?* foo/;它会将(例如)传递bar-foo-bazmv两次(即作为两个单独的参数),从而当工具第二次尝试移动此对象时会产生错误。

当根本找不到匹配项时,我的mv命令将退化mv foo/并抛出错误。您的原始mv命令(nullglob未设置)将获取文字*foo*并抛出另一个错误。


控制台会话示例:

$ shopt -s nullglob
$ mkdir foo
$ touch bar-foo bar-foo-baz foo-baz xyfooz.tex ./--foo
$ mv -v -- *?foo* foo?* foo/
'bar-foo' -> 'foo/bar-foo'
'bar-foo-baz' -> 'foo/bar-foo-baz'
'--foo' -> 'foo/--foo'
'xyfooz.tex' -> 'foo/xyfooz.tex'
'foo-baz' -> 'foo/foo-baz'
$ 

简单破解

就假设dummy不存在吧。

mv foo dummy
mv *foo* dummy/
mv dummy foo

在基于 inode 的文件系统中,重命名目录应该非常快。已foo/打开文件的程序不应干扰或中断,因为 inode 才是最重要的。但是,如果任何程序需要打开foo/第一个和第三个之间的文件(通过其路径包括)mv,它将失败。可能会发生其他副作用(想象一下在此期间重新创建第三方程序foo/),这就是为什么我将这种方法称为 hack。使用它之前请三思。


无论如何find

区分文件和目录是 的工作find。你用 做的事情find基本上是正确的。你想用 做的事情(以及我上面做的事情)和mvshell 通配符似乎……嗯,最多“不太正确”。这是你的命令:

find . -maxdepth 1 -type f -name '*foo*' -exec mv {} foo \;

find将为每个匹配的文件运行一个单独的mv,整个命令的性能会很差。因此,您可能希望切换到单个mv。如果这是您的思路(并且您的mvfind足够丰富),那么您应该考虑这种“非常正确”的方式:

find . -maxdepth 1 -type f -name '*foo*' -exec mv -t foo/ -- {} +

find与你的命令和/或使用 glob 相比,其优势mv如下:

  • 一个人mv可能会得到多个文件来处理;
  • 但是,如果文件太多,一个命令行无法处理,find则会运行额外的mv进程;
  • 如果没有文件匹配该模式,mv则根本不会运行;
  • 由于--像这样的文件--foo不会被解释为(未知)选项mv

相关内容