我有大约 8 个子目录,其中包含图像文件。我需要将大约 6000 张照片中的 300 张从这些子目录移动到一个公共目录。我有一个文件,其中包含我需要移动的文件的名称(而不是路径)。我该怎么做?
我见过类似的方法,但没有一种可以读取文件中的名称,在子目录中搜索该名称以获取路径,然后将该文件移动到公共目录,然后转到下一个目录。我找不到让 find 读取文件的方法。这将在 MacOS 计算机上进行,但任何使用 Python 或 bash 的程序都应该可以正常工作。我不擅长编码,尤其是多变量编码。我以为我可以 cat 文件,然后以某种方式将其发送给 find,然后 find 可以将该目录路径发送给 copy 命令,但 find 命令对我来说是致命的。
谢谢任何有效的帮助。
答案1
在这种情况下,find
这不是最好的工具。经过一番努力,可以编写一个命令,从文件中读取并构建一个find
命令,用于检查多个目录中的多个名称;或多个find
命令:每个名称一个命令,甚至每个名称和目录的笛卡尔积成员一个命令。
仍然测试类似-name
,-path
或-regex
(如果支持)模式。我明白您想提供准确的名称。这些名称可能很安全,但也可能不安全。例如,Awesome.Picture.*OMG*.jpg
如果将名称解释为文件名模式或正则表达式,则名称可以匹配不止这个精确的字符串。通常,您需要清理每个名称,以便在解释后它仅与原始字符串匹配。
在 shell 中使用精确名称很容易。如果有 300 个名称和大约 8 个目录需要检查,它们的笛卡尔积包含大约 2400 对。即使是速度较慢的 shell 也可以在合理的时间内检查它们。与实际复制所需的时间相比,这可能微不足道。
以下是我的假设:
- 带有名称的文件每行指定一个文件名。
- 文件中的每一行都以不属于相应文件名的换行符结尾。请注意,此假设也适用于最后一行(比较这个答案)。
- 文件中指定的每一行都应按字面意思理解。空格(如果有)属于文件名;引号、反斜杠等也一样。
- 您想要检查几个精确的目录,而不是它们的子目录(即不进行递归搜索)。
这应该可以做到:
#!/bin/sh -
target="$1//."
shift
while IFS= read -r name; do
for dir do
source="$dir/$name"
test -f "$source" && </dev/tty cp -i -- "$source" "$target" && break
done
done
将其另存为contraption
并使其可执行(chmod +x contraption
)。使用方式如下:
</file/with/names ./contraption /target/dir /source/dir1 /source/dir2 /another/source
如果需要,请指定更多源目录。
笔记:
- 我本意是编写可移植的代码。我认为代码是可移植的,尽管通常可能需要调整shebang,因为POSIX不强制
sh
在/bin/sh
;也不强制env
在/usr/bin/env
(所以#!/usr/bin/env sh
理论上不是更好;在实践中参见这个答案)。 IFS= read -r name
解释如下:理解IFS= read -r line
。- 由于
test -f
代码不会(尝试)复制匹配文件这不是常规文件。 </dev/tty cp -i
是为了保护目标目录中已经存在的文件。标准cp -i
只会破坏一些东西(因为它会从文件的标准输入中读取)。注意/dev/tty
由 POSIX 指定。--
解释如下:--
(双破折号)是什么意思?.sh -
解释如下:为什么会-
发生#! /bin/sh -
这种事?- 最后一个斜杠
$target
(添加在target="$1//."
)确保它是一个目录。这里之所以有点,只是因为我希望我的代码非常安全(请参阅这个答案,它解释了两者)。如果您已经用尾部斜杠指定了第一个位置参数,那应该没关系。如果您指定了,我会注入两个斜杠(而不是一个)以获得///.
(不是) 。重点是//.
/
//.
可能会很麻烦。请注意,//.
如果您指定一个空字符串作为目标,您将得到(解决方案不是指定空字符串,它不是有效的路径名)。 break
如果复制成功,则脚本将转到下一个名称,而不检查尚未检查的目录中的当前名称。如果您希望目录包含具有相同名称的文件,请在命令行中先指定首选源目录,然后再指定次要源目录。