我有一个invasive/
包含 60000 个文件的目录。我只想将嵌套子目录中的 1000 个文件invasive/
从invasive2/
我尝试执行以下操作:
find invasive/ -type f -exec mv --backup=numbered -t invasive2/ {} + | head -1000
不幸的是,它确实有效。任何想法 ?
我有一个invasive/
包含 60000 个文件的目录。我只想将嵌套子目录中的 1000 个文件invasive/
从invasive2/
我尝试执行以下操作:
find invasive/ -type f -exec mv --backup=numbered -t invasive2/ {} + | head -1000
不幸的是,它确实有效。任何想法 ?
您必须对输出执行
head
命令find
。像这样的东西(在具有非常有价值的数据的生产环境中,请密集测试)更新:
评论完全正确。我总是忘记像换行符这样的“特殊字符”。我添加了“零终止”。感谢您的评论!
见
man xargs
:-n 1
表示每个命令一个参数-r
表示如果为空则不运行-0
表示传入的参数以零 (\0
)结尾见
man head
:-z
表示传入的参数以零 (\0
)结尾见
man find
:-print0
表示以零结束传出字符串 (\0
)分析
在您的尝试中:
head
根本没有输入,因为find
不打印任何东西。如果你做了
或者
然后
head
会得到一些输入和退出,find
可以得到SIGPIPE
;但一般来说,信号不会在你需要的时候准确发生。这是因为-exec … {} +
用{}
可能的许多路径名替换,一堆路径名。如果
… -print -exec …
-print
许多路径名首先发生行为,这些路径名将形成一堆-exec … {} +
. 如果SIGPIPE
发生,则-exec
不会为该组执行。如果
… -exec … {} + -print
-exec
首先对整个组执行操作,则只有-print
分别为每个路径名打印。SIGPIPE
只有当工具打印一些东西时才会发生,所以它不能中断-exec mv
,它只能中断-print
。如果SIGPIPE
发生,它将阻止find
打印更多路径名;但-exec mv …
已经发生在整个一群人身上。您只想计算成功的移动操作。如果
mv
尝试移动多个文件并成功,则您知道所有文件都已移动。如果失败,那么您将无法轻松知道移动了多少文件。出于这个原因,您需要mv
为每个要移动的文件单独设置一个文件。所以你需要-exec mv … \;
而不是-exec mv … {} +
(除了-exec … {} +
作为测试find
无论如何都没有用,它总是返回true)。另一个复杂因素是您不能确定
-print
每个文件只打印一行(因为路径名可能包含换行符)。一个可靠的解决方案是-printf '\n'
(如果您find
支持-printf
)或-exec printf '\n' \;
.这导致我们得到以下解决方案(虽然有缺陷):
理论上它是这样工作的:
mv
成功时,才会打印换行符。head
在 999 次换行之后终止,即在 999 次成功的移动操作之后。唯一的消失head
不会导致find
立即接收SIGPIPE
。经过 999 次成功的移动操作head
已不再存在,但find
仍然有效。find
仅当它在终止SIGPIPE
后尝试打印某些内容时才会收到。head
这发生在第 1000 次成功的移动操作之后。在实践中,不能保证
head
读取速度足够快并且终止速度足够快以SIGPIPE
准确地在我们需要它时引起。这是上面代码中的缺陷。find
和之间有一个缓冲区head
。它可能会find
设法打印比head
指示阅读更多的行。管道的机制旨在最终终止前面的工具(此处:)find
,而不是精确地在确切的时刻;当我们想要在 1000 次成功的移动操作之后中断我们的操作时,我们不能依赖它。find
以这种方式中继输出
head
并没有缺陷。就像是是一个好的开始,但由于路径名通常可能包含换行符,因此您需要
-print0
(不可移植)、head -z
(也不可移植)等等。如果你想计算成功的移动操作,那么它应该是:code0_that_runs_mv_and_counts
至少在 Bash 中,可以将其构建为 shell 脚本。我的尝试如下。解决方案
注意我曾经
</dev/tty mv …
阻止mv
使用标准输入,以防它提示确认或其他内容。好吧,--backup=numbered
我想它不应该提示;但总的来说它可能而且我们不希望它从我们的find
.上面的代码不可移植,我不太喜欢它。
便携式*解决方案
如果您
find
不支持-print0
或无法使用bash
(或者您只是喜欢更多可移植代码),请考虑以下方法:* AFAIK 这里唯一不可移植的是您
mv
使用的选项。如果您不使用,那么我们可以将其重写为可移植形式。我添加的所有东西都是可移植的,这就是为什么我称这个解决方案为可移植的。--backup=numbered
mv
这就是代码的工作方式:
find
启动sh
并可能将许多路径名作为参数传递给它。可能不止一个sh
开始一个接一个,数量无所谓。sh
尝试mv
在循环中一一归档。成功移动操作后,它会尝试read
从继承自find
.while … | head -n 999
(可以是yes | head -n 999
,但yes
不可移植)恰好生成 999 行。除非我们先用完文件,否则正好 999reads
会成功。第read
1000 次成功的移动操作将是第一个read
失败的操作。失败
read
恰好在第 1000 次成功移动操作之后发生。它会导致两件事:find
($PPID
, ) 的父进程sh
得到SIGPIPE
,因此不会启动更多sh
进程;sh
退出,因此它不会处理更多路径名。笔记
所有片段都旨在移动 1000 个文件;有些包含
1000
,有些包含999
在代码中。您可以调整它们以移动 N 个文件,但请注意代码中是否需要 N 或 N-1。计算成功的移动操作是有道理的,但在某些情况下,它可能会导致潜在的问题。在文件系统之间移动文件时,
mv
创建一个副本,然后删除源。删除失败会导致mv
报告非零退出状态,但副本仍然存在。想象一下,您对您invasive/
来说是只读的。在这种情况下,我们的代码会将常规文件复制到其中,invasive2/
但不会mv
被视为成功。将复制所有常规文件。我用过
bash -c '…' code0_that_runs_mv_and_counts
,find … -exec sh -c '…' find-sh {} +
。如果您对争论感到惊讶code0_that_runs_mv_and_counts
并find-sh
成为争论,那么请阅读What is the second sh insh -c 'some shell code' sh
?