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

Har*_*aha 2 shell-script files

我正在学习 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
Run Code Online (Sandbox Code Playgroud)

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

Kus*_*nda 5

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

if [` $x -eq 0 `]
Run Code Online (Sandbox Code Playgroud)

[]必须从内部参数用空格分开。此外,这一行的命令替换`$x -eq 0`, 是无意义的,因为它会尝试将 的值$x作为命令运行。

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

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


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

$ ./script.sh some/path/file1 some/other/path/file2
Run Code Online (Sandbox Code Playgroud)

如果以这种方式运行脚本,这两个路径名将在脚本中以"$1"和 形式提供"$2"

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

脚本看起来像

#!/bin/sh

if cmp -s -- "$1" "$2"; then
    rm -i -- "$2"
fi
Run Code Online (Sandbox Code Playgroud)

或者,更短,

#!/bin/sh

cmp -s -- "$1" "$2" && rm -i -- "$2"
Run Code Online (Sandbox Code Playgroud)

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

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

因此,我们还需要确保两个路径名指向两个不同的文件。

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

#!/bin/sh

if [ "$1" != "$2" ] && cmp -s -- "$1" "$2"; then
    rm -i -- "$2"
fi
Run Code Online (Sandbox Code Playgroud)

这对于大多数应用程序来说可能已经足够了,但没有考虑符号链接。在某些 shell 中,您还可以使用非标准-ef测试(“相等文件”)来测试两个路径名是否引用同一个文件(相同的 i 节点号和设备):

#!/bin/bash

if ! [ "$1" -ef "$2" ] && cmp -s -- "$1" "$2"; then
    rm -i -- "$2"
fi
Run Code Online (Sandbox Code Playgroud)

或者,

#!/bin/bash

! [ "$1" -ef "$2" ] && cmp -s -- "$1" "$2" && rm -i -- "$2"
Run Code Online (Sandbox Code Playgroud)

并进行一些健全性检查(还将-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"
Run Code Online (Sandbox Code Playgroud)

请注意,引用变量扩展很重要,因为路径名包含空格的情况并不少见(在 macOS 上,这很常见)。双引号变量扩展还可以阻止它们被解释为 shell globbing 模式(例如,您的代码不适用于名为 的文件*)。还要注意使用#!适合脚本的-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"
Run Code Online (Sandbox Code Playgroud)

有关的:


如果你在某个时候需要检查一个文件是否为空,就像在你自己的代码中一样,那么不要调用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
Run Code Online (Sandbox Code Playgroud)

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