如何比较两个文件,如果发现相等,则要求用户使用 shell 脚本删除重复文件?

如何比较两个文件,如果发现相等,则要求用户使用 shell 脚本删除重复文件?

我正在学习 Linux,并把这个问题作为我的作业,但我无法解决如何在 shell 模式下比较两个文件内容的问题。 (在这里,我们可以假设两个文件都具有这样的文本内容,例如 $cat > f1 这是文件 1)

$ cat duplicate_file.sh
echo "Enter file 1:"
read file1
echo "Enter file 2:"
read file2
cmp $file1 $file2 > newfile
x=` wc newfile | cut -d" " -f2 `
if [` $x -eq 0 `]
then
rm -i $file2
fi

我编写了这个程序,但这不起作用!那么,有什么建议吗?

答案1

代码中的直接问题是读取行上的语法错误

if [` $x -eq 0 `]

必须用空格字符将[and]与其中的参数分隔开。此外,该行上的命令替换`$x -eq 0`是无意义的,因为它会尝试将 的值$x作为命令运行。

您还遇到了不引用变量扩展的问题,这使您的脚本无法处理包含空格字符和文件名通配模式的文件名。

该脚本还会无条件地不必要地破坏文件(如果是现有目录的名称,newfile则会失败)并且缺少- 行。newfile#!


以交互方式询问用户文件路径是没有意义的。用户最好能够在命令行上使用 shell 的文件名完成功能,并提供文件的路径名作为两个操作数:

$ ./script.sh some/path/file1 some/other/path/file2

如果以这种方式运行脚本,两个路径名将在脚本中可用,分别为"$1""$2"

实用性cmp可以在此脚本中使用,而无需创建临时文件。不要重定向其输出,而是使用其选项(“silent”)使其安静,-s并使用其退出状态来确定两个文件是否相同。

该脚本看起来像

#!/bin/sh

if cmp -s -- "$1" "$2"; then
    rm -i -- "$2"
fi

或者,更短一些,

#!/bin/sh

cmp -s -- "$1" "$2" && rm -i -- "$2"

rm -i如果它引用的文件的内容与第一个路径名相同,则这将调用两个给定路径名中的第二个。和命令--中的命令对于避免将以破折号开头的文件名解释为一组选项是必要的。cmprm

问题使用这个脚本,就像使用您自己的脚本一样,如果您给它相同的路径名两次,即您将一个文件与其自身进行比较,它会提出将其删除。

因此,我们还需要确保这两个路径名引用了两个不同的文件。

您可以通过比较两个路径名字符串来做到这一点:

#!/bin/sh

if [ "$1" != "$2" ] && cmp -s -- "$1" "$2"; then
    rm -i -- "$2"
fi

这对于某些应用程序来说可能已经足够了,但不考虑使用不同路径指定的符号链接或文件(例如./filevs filevs /path/to/file)。在大多数 shell 中,您还可以使用非标准 (然而) -eftest ("equal file"),测试两个路径名是否引用同一个文件(相同的 inode 号和设备,因此对于同一文件的两个硬链接也返回 true):

#!/bin/bash

if ! [ "$1" -ef "$2" ] && cmp -s -- "$1" "$2"; then
    rm -i -- "$2"
fi

或者,

#!/bin/bash

! [ "$1" -ef "$2" ] && cmp -s -- "$1" "$2" && rm -i -- "$2"

并进行一些健全性检查(还将-ef测试移至健全性检查部分):

#!/bin/bash

if [ "$#" -ne 2 ]; then
    # did not get exactly two arguments
    printf 'Usage:\n\t%s file1 file2\n' "$0" >&2
    exit 1
elif [ ! -f "$1" ] || [ ! -f "$2" ]; then
    echo 'One of the files does not exist (or is not a regular file)' >&2
    exit 1
elif [ "$1" -ef "$2" ]; then
    printf '%s and %s refer to the same file\n' "$1" "$2" >&2
    exit 1
fi

cmp -s -- "$1" "$2" && rm -i -- "$2"

请注意,引用变量扩展很重要,因为路径名包含空格的情况并不罕见(在 macOS 上,这很常见)。双引号变量扩展还会阻止它们被解释为 shell 通配模式(例如,您的代码将无法在名为 的文件上运行*)。另外,请注意#!脚本中适当的 -line 的使用。

如果你的家庭作业需要您以交互方式读取两个文件的路径名,然后将read -rIFS设置为空字符串。这将允许您读取以空格或制表符开头或结尾并包含\字符的路径名(但您仍然无法指定包含换行符的路径名):

#!/bin/bash

IFS= read -p '1st pathname: ' -r p1
IFS= read -p '2nd pathname: ' -r p2

if [ ! -f "$p1" ] || [ ! -f "$p2" ]; then
    echo 'One of the files does not exist (or is not a regular file)' >&2
    exit 1
elif [ "$p1" -ef "$p2" ]; then
    printf '%s and %s refer to the same file\n' "$p1" "$p2" >&2
    exit 1
fi

cmp -s -- "$p1" "$p2" && rm -i -- "$p2"

有关的:


如果您在某些时候需要检查文件是否为空,就像在您自己的代码中一样,那么不要调用wc它(它的效率很低,因为它必须读取整个文件)。相反,使用-s测试:

if [ -s "$pathname" ]; then
    printf '%s has non-zero size\n' "$pathname"
else
    printf '%s is empty (or does not exist)\n' "$pathname"
fi

man test参阅您的系统,或参考该实用程序的 POSIX 标准

答案2

首先将 shebang 添加#!到顶部,例如#!/bin/bash

你有两个错误:

代替

cmp $file1 $file2 > newfile,

它应该是

cmp -- "$file1" "$file2" > newfile

因为这些变量的值可能包含空格、制表符、换行符(字符$IFS)、、、*(通配符),或者可能以[开头。?-

第二个错误:

代替

if [` $x -eq 0 `]

它应该是

if [ "$x" -eq 0 ].

否则你会得到错误

bash: 0: 未找到命令。

另外,如果文件名中有空格或通配符,则应该是:

rm -i -- "$file2"否则它可以删除多个文件。

答案3

这是我的实现。

#! /bin/bash

echo -n "Enter file1: " 
read file1
echo -n "Enter file2: " 
read file2

if cmp -s -- "$file1" "$file2"
then
      echo same 
      rm -i -- "$file2"
else 
      echo different
fi

我有 3 个文件:var1.txt, var2.txt, var3.txt.

  • var1不同的是var2
  • var2是相同的var3

对这些文件运行上述脚本 ( com.sh) 会导致:

$ bash com.sh
Enter file1: var1.txt 
Enter file2: var2.txt 
different

$ bash com.sh
Enter file1: var2.txt 
Enter file2: var3.txt 
same
rm: remove regular file 'var3.txt'?

答案4

以下代码将检查输出是否为null空白。如果为空则文件相同,否则文件不同。

output=`echo | cmp -b $file1 $file2`

if [[ -z $output ]]
then
    echo "Same"
    rm $file2
else
    echo "Diff"    
fi

相关内容