所以基本上我必须编写一个 bash 脚本来检查工作目录中的一些特定文件(命名为 file.00.txt 到 file.24.txt)。问题是,其中 3 个是完全相同的,我的任务是创建一个脚本来告诉我哪 3 个是相同的。
这是我的代码
#!/bin/bash
f0=file.00.txt
f1=file.01.txt
f2=file.02.txt
f3=file.03.txt
f4=file.04.txt
f5=file.05.txt
f6=file.06.txt
f7=file.07.txt
f8=file.08.txt
f9=file.09.txt
f10=file.10.txt
f11=file.11.txt
f12=file.12.txt
f13=file.13.txt
f14=file.14.txt
f15=file.15.txt
f16=file.16.txt
f17=file.17.txt
f18=file.18.txt
f19=file.19.txt
f20=file.20.txt
f21=file.21.txt
f22=file.22.txt
f23=file.23.txt
f24=file.24.txt
array=($f0 $f1 $f2 $f3 $f4 $f5 $f6 $f7 $f8 $f9 $f10 $f11 $f12 $f13 $f14 $f15 $f16 $f17 $f18 $f19 $f20 $f21 $f22 $f23 $f24)
i=0
touch placeholder
while [ $i -lt ${#array} ]
do
DIFF=$(diff ${array[i]} ${array[i+1]})
if [ "$DIFF" = "" ]
then
echo "${array[i]} y ${array[i+1]}" >> placeholder
fi
i=$((i+1))
done
cat placeholder
这段代码的想法是将每个文件与数组中的下一个文件进行比较,然后将相同的文件存储在名为 placeholder 的文件中,最后使用 cat 命令显示文件的内容。
然而,每次我运行脚本时我都会收到消息
file.00.txt y file.00.txt
file.01.txt y file.01.txt
file.02.txt y file.02.txt
每个文件依此类推。这不应该发生,因为我显然正在使用
echo "${array[i]} y ${array[i+1]}" >> placeholder
回应这两个位置。为什么会发生这种情况?我该如何解决这个问题?
我该如何解决这个问题?
答案1
您的代码看似奇怪的输出可能是因为您的脚本只曾经添加到输出文件。这意味着您之前的代码中可能存在一些错误(现已更正),但您仍然可以在输出文件中看到该运行的输出,因为输出文件永远不会被脚本删除或清空。
您可以将脚本缩短为
#!/bin/bash
array=( file.*.txt )
for name in "${array[@]}"; do
if [ -n "$prev_name" ] && cmp -s "$prev_name" "$name"
then
printf '%s y %s\n' "$prev_name" "$name"
fi
prev_name=$name
done
这使用通配模式用与该模式匹配的文件名填充数组。
然后它循环遍历名称,使用 . 比较数组中彼此相邻的文件名cmp -s
。该cmp
实用程序将退出并显示真的如果比较的两个文件的内容相同,则退出状态。
该循环用于$prev_name
保存数组中前一个文件的名称。在循环的第一次迭代中,该变量为空,因此跳过文件的实际比较。
你可能是什么预期的写是一个双循环。就像是
for nameA in "${array[@]}"; do
for nameB in "${array[@]}"; do
if [ "$nameA" != "$nameB" ] && cmp -s "$nameA" "$nameB"
then
printf '%s y %s\n' "$nameA" "$nameB"
fi
done
done
但这将A
与B
和 B
,并且A
调用的数量cmp
将随着涉及的文件数量呈二次方增长,这将是资源密集型的(在磁盘上;它将读取每个文件与数组中的文件名一样多)并且速度慢。
查找具有相同内容的文件集的常见方法是fdupes
:
$ fdupes --sameline .
./file.1.txt ./file.2.txt ./file.7.txt
如果您想要在没有 的情况下执行类似的操作fdupes
,您可以通过使用例如计算和比较每个文件的校验和来实现md5sum
:
#!/bin/bash
declare -A names count
while read -r cksum name; do
names[$cksum]+=${names[$cksum]:+,}$name
count[$cksum]=$(( count[$cksum] + 1 ))
done < <( md5sum file.*.txt )
for cksum in "${!count[@]}"; do
if [ "${count[$cksum]}" -gt 1 ]; then
printf '%s\n' "${names[$cksum]}"
fi
done
md5sum
第一个循环读取在所有相关文件中执行的输出。的输出md5sum
可能类似于
897316929176464ebc9ad085f31e7284 file.1.txt
8c9eb686bf3eb5bd83d9373eadf6504b file.10.txt
897316929176464ebc9ad085f31e7284 file.2.txt
26ab0db90d72e28ad0ba1e22ee510510 file.3.txt
84bc3da1b3e33a18e8d5e1bdd7a18d7a file.4.txt
aa6ed9e0f26a6eba784aae8267df1951 file.5.txt
6d7fce9fee471194aa8b5b6e47267f03 file.6.txt
897316929176464ebc9ad085f31e7284 file.7.txt
c30f7472766d25af1dc80b3ffc9a58c7 file.8.txt
9ae0ea9e3c9c6e1b9b6252c8395efdc1 file.9.txt
第一列中的校验和被读入cksum
,文件名被读入name
。
在第一个循环中,我们将名称附加到由校验和索引的关联数组中的条目。在那里完成分配的方式names[$cksum]
确保我们在需要时在每个新名称之前添加一个逗号(如果条目已经包含一些其他名称)。然后,我们更新看到该特定校验和的次数(这将在第二个循环中使用)。
在第二个循环中,我们检查校验和("${!count[@]}"
扩展到关联数组中的键(校验和)列表count
),对于每个校验和,我们测试其计数是否大于 1,这意味着我们找到了重复文件(如果如果您正在寻找由三个完全相同的文件组成的组,您可能需要使用-eq 3
而不是-gt 1
此处)。如果是,我们将打印与该校验和相关的名称。
测试它:
$ bash script.sh
file.1.txt,file.2.txt,file.7.txt
答案2
这是一种更有效的方法来完成您想要做的事情。我使用较小的样本集以使事情更清晰:
#!/bin/bash
# clear placeholder
printf "Files with no diff:\n" > placeholder
# set up sample data
echo "one" > file.00.txt
echo "one" > file.01.txt
echo "foo" > file.02.txt
echo "bar" > file.03.txt
echo "two" > file.04.txt
echo "two" > file.05.txt
# generate array
i=0
while [ $i -lt 6 ]; do
array+=( file.`printf %02d $i`.txt )
((i++))
done
i=0
while [ $i -lt 5 ]; do
diff --brief ${array[i]} ${array[i+1]} && \
echo "${array[i]} ${array[i+1]}" >> placeholder
((i++))
done
结果:
$ sh ./test.sh
Files file.01.txt and file.02.txt differ
Files file.02.txt and file.03.txt differ
Files file.03.txt and file.04.txt differ
$ cat placeholder
Files with no diff:
file.00.txt file.01.txt
file.04.txt file.05.txt
如果您实际上已经拥有数据,则无需生成示例数据。
代码解释:
在循环中(在 Bash 中)构建数组可以通过迭代您显然已经知道的方式来完成,但符号array+=
会附加一个元素。
这((++))
显然会增加你的计数器。
执行 diff 时,我使用该--brief
选项。如果您阅读diff
手册页,它会告诉您--brief
仅在发现差异时才打印输出。因此,diff 命令成功如果没有发现差异。
使用(AND) 表示法,此代码将与您的文件&&
进行比较的文件名称相呼应placeholder
当且仅当该diff
命令不生成任何输出。
如果有是文件之间的差异,diff
将差异输出到终端。这会导致&&
(AND) 失败,因此不会向占位符文件输出任何内容。
如果您对语法还有任何疑问,请随时提问。