按上次修改的顺序复制文件

按上次修改的顺序复制文件

我正尝试按照特定顺序将一堆文件从目录移动到我的手机中,这样就不会弄乱手机中的顺序。

我所能做的最好的就是使用这个命令:

find . -printf "%T@ %p\n" | sort -n | sed 's@^.*/@@' | xargs -I{} cp "{}" '/run/user/1000/gvfs/mtp:host=SAMSUNG_SAMSUNG_Android_RF8NA3063PJ/Internal storage/Music/music'

或者这个

stat -c "%Y/%n" *.mp3 | sort -t/ -k1,1n |sed 's@^.*/@@' | xargs -I{} cp "{}" '/run/user/1000/gvfs/mtp:host=SAMSUNG_SAMSUNG_Android_RF8NA3063PJ/Internal storage/Music/music'

问题是,一旦文件名包含撇号,我就会收到以下错误:xargs:不匹配的单引号;默认情况下,引号对 xargs 来说是特殊的,除非您使用 -0 选项

我知道当我使用“find”命令时,我需要添加 -print0,但是这样做

find . -print0 "%T@ %p\n" | sort -n | sed 's@^.*/@@' | xargs -I{} cp "{}" '/run/user/1000/gvfs/mtp:host=SAMSUNG_SAMSUNG_Android_RF8NA3063PJ/Internal storage/Music/music'

给我一个错误:查找:路径必须在表达式之前:'%T @%p \ n'

将命令转换为这样可以工作,但会打乱顺序

find * -print0 -printf "%T@ %p\n" | sort -n | sed 's@^.*/@@'

有什么帮助吗?

答案1

在这个答案中,我将以下命令视为我们的起点:

find . -printf "%T@ %p\n" | sort -n | sed 's@^.*/@@' | xargs -I{} cp …

然后你写:

我知道当我使用“查找”命令时,我需要添加一个-print0

然后你尝试:

find . -print0 "%T@ %p\n" | …

或者:

find * -print0 -printf "%T@ %p\n" | …

我认为主要的误解在于“我需要添加-print0,你不需要添加-print0。在-print0出现和似乎被添加的情况下(例子),它真的取代隐含的-print。重点是-print是默认操作,在许多情况下您不必键入它。然后,如果您想用 替换它-print0,您可以附加-print0。 的存在-print0会抑制默认的-print。看起来好像-print0是添加了,但从逻辑上讲它替换了-print

在您的情况下,-printf …已经抑制了默认的。这意味着您的命令中-print没有。如果您添加(就像您所做的那样),您将拥有和 ,并且每个都会打印一些内容。-print-print0-printf …-print0

您不想简单地用 替换您的-printf …,因为对您来说,使用代替(显式或隐式)-print0的意义在于您使用的这个。您想-printf …-print%T调整你的-printf …,因此新的相-printf …对于旧的就像是相对于的-printf …完全一样。-print0-print

-print0和有什么区别-print?前者使用一个尾随空字节作为终止符,而后者使用一个尾随换行符。-print0相当于-printf '%p\0'-print相当于-printf '%p\n'

您的 -printf "%T@ %p\n"代码确实使用了一个尾随换行符,它是-print-like 。要使其-print0类似于 并保留%T,您只需将其更改\n\0

find . -printf '%T@ %p\0'

然后你需要制作你的sortsedxargs处理以空字符结尾的行。希望如果你的find足够丰富以支持-print0-printf那么你的其他工具也能够处理以空字符结尾的数据。我假设你cp支持-t。实际上我假设 GNU 工具。

这是命令:

find . -iname '*.mp3' -type f -printf '%T@ %p\0' \
| sort -zn | sed -z 's@^[^ ]* @@' \
| xargs -r0 cp -t '/run/user/1000/gvfs/mtp:host=SAMSUNG_SAMSUNG_Android_RF8NA3063PJ/Internal storage/Music/music'

注意我更改了sed代码。您的代码删除了直到最后一个/(.*是贪婪的!) 的所有内容,因此只有最后一个组件保留下来,即文件名(除了.没有 的地方/,所以所有内容都保留下来!)。如果子目录中有匹配的文件,-iname '*.mp3' -type f则仅传递最后一个组件将cp失败。我的代码删除了直到第一个空格的所有内容,即 产生的片段%T@。 产生的所有内容都%p保留下来。这意味着:

  • 子目录中的文件可以被复制,
  • 但是名称冲突(如果有的话)会导致稍后的cp覆盖,因此最终目标位置可能会丢失某些文件;
  • 以 开头的路径名组件-是安全的(即我们不需要双划线) 因为所有路径名都以 开头.

我使用是-iname '*.mp3'因为您在其他地方使用了*.mp3。我使用是-type f因为这样做是明智的(您当然不想处理目录路径并将它们传递给cp)。

我相信如果在您的语言环境中有一个路径名构成无效文本,那么您需要LC_ALL=C,才能-iname '*.mp3'按预期工作。我不确定sed。如果您想使用此类路径名,请export LC_ALL=C事先考虑。

答案2

抱歉,我没有足够的声誉来发表评论,所以这里是我的评论:我在一个小的目录中测试了它,我看到find .它返回了˙。˙,但其中没有/,所以我认为你的 sed 搞砸了。这是我的测试:

find . -printf "%T@ %p\n" |sort -n
1650024944.2971079410 ./.nvmrc
1650108929.2884298980 ./package.json
1650108929.2884298980 ./package-lock.json
1657746711.8129748630 .

find . -printf "%T@ %p\n" |sort -n |sed 's@^.*/@@'
.nvmrc
package.json
package-lock.json
1657746711.8129748630 .

可能不是最有效的解决方案:

find . -printf "%T@ %p\n" |sort -n |grep -v ' \.$' |sed 's@^.*/@@'
.nvmrc
package.json
package-lock.json

更新:我认为这会起作用。你非常接近了,只是错过了-printf你使用的位置-print0。而且grep -v解决方案不适用于-print0,所以我添加了! -path .find 以排除.

find . ! -path . -print0 -printf "%T@ %p\n" | sort -n | sed 's@^.*/@@' | xargs -I{} cp "{}" '/run/user/1000/gvfs/mtp:host=SAMSUNG_SAMSUNG_Android_RF8NA3063PJ/Internal storage/Music/music'

答案3

find . -type f -printf "%T@ %p\n" \
    | sort -n \
    | sed "s:^[^ ]* ::;s:\":\\\\\":g;s:^:cp -t target-directory \":;s:$:\":" \
    | bash

解释:

  1. 寻找命令:
  • -类型 f- 仅搜索文件,不搜索目录
  1. sed命令:
  • s:^[^ ]* ::- 删除主要内容
  • s:":\\\\\":g- 将双引号改为 \"
  • s:^:cp -t 目标目录 \":-- 添加前缀
  • s:$:\":- 并添加后缀

我必须使用管道bash而不是使用,xargs因为我无法处理 中的引号/双引号xargs。很抱歉。请阅读此内容:https://unix.stackexchange.com/questions/385466/properly-escaping-output-from-pipe-in​​-xargs

(抱歉我的英语不好)

相关内容