![从 bash 脚本执行 cmp - 对现有文件“没有这样的文件或目录”](https://linux22.com/image/160307/%E4%BB%8E%20bash%20%E8%84%9A%E6%9C%AC%E6%89%A7%E8%A1%8C%20cmp%20-%20%E5%AF%B9%E7%8E%B0%E6%9C%89%E6%96%87%E4%BB%B6%E2%80%9C%E6%B2%A1%E6%9C%89%E8%BF%99%E6%A0%B7%E7%9A%84%E6%96%87%E4%BB%B6%E6%88%96%E7%9B%AE%E5%BD%95%E2%80%9D.png)
我正在尝试读取目录并识别其内容可能重复的任何文件。
其简化版本:
#!/bin/bash
IFS=$'\n'
for FILEA in $(find "$1" -type f -print)
do
echo "$FILEA"
for FILEB in $(find "$1" -type f -print)
do
echo cmp \"${FILEA}\" \"${FILEB}\"
cmp \"${FILEA}\" \"${FILEB}\"
done
done
然而,每次cmp
抱怨的实例No such file or directory
。
如果我获取 cmp 命令的回显版本,它会完全按照预期执行。
为什么 cmp 在脚本上下文中执行时看不到文件?
答案1
您的主要问题是在使用时向文件名添加文字双引号cmp
(并且文件名本身实际上未加引号)。这就是找不到文件的原因(它们的名称中没有引号)。您还可以循环输出find
,这并不理想。
如果你真的不想使用fdupes
,您可以执行以下操作来尝试使您的方法更加高效(速度方面):
#!/bin/bash
# enable the "**" globbing pattern,
# remove non-matched patterns rather than keeping them unexpanded, and
# allow the matching of hidden names:
shopt -s globstar nullglob dotglob
pathnames=("${1:-.}"/**) # all pathnames beneath "$1" (or beneath "." if "$1" is empty)
# loop over the indexes of the list of pathnames
for i in "${!pathnames[@]}"; do
this=${pathnames[i]} # the current pathname
# skip this if it's not a regular file (or a symbolic link to one)
[[ ! -f "$this" ]] && continue
# loop over the remainder of the list
for that in "${pathnames[@]:i+1}"; do
# skip that if it's not a regular file (or a symbolic link to one)
[[ ! -f "$that" ]] && continue
# compare and print if equal
if [[ "$this" -ef "$that" ]] || cmp -s "$this" "$that"; then
printf '"%s" and "%s" contains the same thing\n' "$this" "$that"
fi
done
done
这可以避免为每个文件遍历整个目录结构一次(您在内部循环中执行此操作),并且还可以避免多次比较对。它还是非常速度很慢,因为它需要cmp
在整个目录层次结构中的每个文件组合上运行。
相反,您可能想尝试一种更简单的方法:
#!/bin/bash
tmpfile=$(mktemp)
find "${1:-.}" -type f -exec md5sum {} + | sort -o "$tmpfile"
awk 'FNR == NR && seen[$1]++ { next } seen[$1] > 1' "$tmpfile" "$tmpfile"
rm -f "$tmpfile"
这会计算所有文件的 MD5 校验和,对该列表进行排序并将其保存到临时文件中。 awk
然后用于提取md5sum
所有重复文件的输出。
输出看起来像
$ bash ~/script.sh
01b1688f97f94776baae85d77b06048b ./QA/StackExchange/.git/hooks/pre-commit.sample
01b1688f97f94776baae85d77b06048b ./Repositories/password-store.git/hooks/pre-commit.sample
036208b4a1ab4a235d75c181e685e5a3 ./QA/StackExchange/.git/info/exclude
036208b4a1ab4a235d75c181e685e5a3 ./Repositories/password-store.git/info/exclude
054f9ffb8bfe04a599751cc757226dda ./QA/StackExchange/.git/hooks/pre-applypatch.sample
054f9ffb8bfe04a599751cc757226dda ./Repositories/password-store.git/hooks/pre-applypatch.sample
2b7ea5cee3c49ff53d41e00785eb974c ./QA/StackExchange/.git/hooks/post-update.sample
2b7ea5cee3c49ff53d41e00785eb974c ./Repositories/password-store.git/hooks/post-update.sample
3c5989301dd4b949dfa1f43738a22819 ./QA/StackExchange/.git/hooks/pre-push.sample
3c5989301dd4b949dfa1f43738a22819 ./Repositories/password-store.git/hooks/pre-push.sample
在上面的输出中,恰好有一些重复的对文件数量。
如果您的文件名包含嵌入的换行符,md5sum
将输出以字符为前缀的行\
:
$ touch $'hello\nworld'
$ md5sum *
\d41d8cd98f00b204e9800998ecf8427e hello\nworld
为了能够正确处理这个问题(通过删除行开头的反斜杠),您可能需要将脚本中的第一个管道修改为
find "${1:-.}" -type f -exec md5sum {} + | sed 's/^\\//' | sort -o "$tmpfile"