我正在学习 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
如果它引用的文件的内容与第一个路径名相同,则这将调用两个给定路径名中的第二个。和命令--
中的命令对于避免将以破折号开头的文件名解释为一组选项是必要的。cmp
rm
这问题使用这个脚本,就像使用您自己的脚本一样,如果您给它相同的路径名两次,即您将一个文件与其自身进行比较,它会提出将其删除。
因此,我们还需要确保这两个路径名引用了两个不同的文件。
您可以通过比较两个路径名字符串来做到这一点:
#!/bin/sh
if [ "$1" != "$2" ] && cmp -s -- "$1" "$2"; then
rm -i -- "$2"
fi
这对于某些应用程序来说可能已经足够了,但不考虑使用不同路径指定的符号链接或文件(例如./file
vs file
vs /path/to/file
)。在大多数 shell 中,您还可以使用非标准 (然而) -ef
test ("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 -r
和IFS
设置为空字符串。这将允许您读取以空格或制表符开头或结尾并包含\
字符的路径名(但您仍然无法指定包含换行符的路径名):
#!/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