如何比较两个文件


Answers:


92

查看diff命令。这是一个很好的工具,您可以通过man diff在终端中键入内容来阅读所有内容 。

您要执行的命令diff File_1.txt File_2.txt将输出两者之间的差异,并且应如下所示:

在此处输入图片说明

关于读取第三条命令的输出的简要说明:“箭头”(<>)表示左文件(<)与右文件(>)中行的值,其中左文件是您输入的首先在命令行上,在这种情况下File_1.txt

另外,您可能会注意到第4条命令是diff ... | tee Output_File将结果从管道传送diff到中tee,然后将其输出到文件中,以便您不想稍后在控制台上查看全部内容时将其保存以供以后使用。


这可以做其他文件(例如图像)吗?还是只限于文件?
格雷戈里歌剧院

2
据我所知,它仅限于文本文件。代码可以正常工作,因为它本质上是文本,但是任何二进制文件(带有图片的文件)都只会被淘汰。您可以通过执行以下操作来比较它们是否相同diff file1 file2 -s。这是一个示例:imgur.com/ShrQx9x
Mitch

有没有办法使输出着色?我想将其保留为仅CLI,但还有更多...人性化的功能。
LazarLjubenović18年

36

或者您可以使用Meld Diff

Meld可帮助您比较文件,目录和版本控制的项目。它提供文件和目录的两向和三向比较,并支持许多流行的版本控制系统。

通过运行安装:

sudo apt-get install meld

你的例子:

在此处输入图片说明

比较目录:

在此处输入图片说明

全文示例:

在此处输入图片说明


18

您可以使用vimdiff

例:

vimdiff  file1  file2

1
这个有颜色
杰克·多伦多

这对我有所帮助,因为它表明我的第一个文件的行尾是in dos,第二个文件的行是in unix
LoMaPh

13

FWIW,我比较喜欢diff的并排输出

diff -y -W 120 File_1.txt File_2.txt

将给出类似的内容:

User1 US                            User1 US
User2 US                            User2 US
User3 US                          | User3 NG

10

您可以使用以下命令cmp

cmp -b "File_1.txt" "File_2.txt"

输出将是

a b differ: byte 25, line 3 is 125 U 116 N

cmpdiff返回码要快得多。
stevesliva

8

Meld是一个非常好的工具。但是您也可以diffuse用来直观地比较两个文件:

diffuse file1.txt file2.txt

在此处输入图片说明


7

下面的脚本可以正确解决问题(file1,file2,带有“已更改”消息的输出文件)。

将脚本复制到一个空文件中,另存为compare.py,使其可执行,然后通过以下命令运行该脚本:

/path/to/compare.py <file1> <file2> <outputfile>

剧本:

#!/usr/bin/env python

import sys
file1 = sys.argv[1]; file2 = sys.argv[2]; outfile = sys.argv[3]

def readfile(file):
    with open(file) as compare:
        return [item.replace("\n", "").split(" ") for item in compare.readlines()]

data1 = readfile(file1); data2 = readfile(file2)
mismatch = [item[0] for item in data1 if not item in data2]

with open(outfile, "wt") as out:
    for line in mismatch:
        out.write(line+" has changed"+"\n")

额外增加几行,您可以使其输出到输出文件或终端,具体取决于是否定义了输出文件:

要打印到文件:

/path/to/compare.py <file1> <file2> <outputfile>

要打印到终端窗口:

/path/to/compare.py <file1> <file2> 

剧本:

#!/usr/bin/env python

import sys

file1 = sys.argv[1]; file2 = sys.argv[2]
try:
    outfile = sys.argv[3]
except IndexError:
    outfile = None

def readfile(file):
    with open(file) as compare:
        return [item.replace("\n", "").split(" ") for item in compare.readlines()]

data1 = readfile(file1); data2 = readfile(file2)
mismatch = [item[0] for item in data1 if not item in data2]

if outfile != None:
        with open(outfile, "wt") as out:
            for line in mismatch:
                out.write(line+" has changed"+"\n")
else:
    for line in mismatch:
        print line+" has changed"

4

一种简单的方法是使用colordiff,其行为类似于diff但会使其输出变色。这对于阅读差异非常有帮助。用你的例子,

$ colordiff -u File_1.txt File_2.txt
--- File_1.txt  2016-12-24 17:59:17.409490554 -0500
+++ File_2.txt  2016-12-24 18:00:06.666719659 -0500
@@ -1,3 +1,3 @@
 User1 US
 User2 US
-User3 US
+User3 NG

u选项提供统一的差异。这就是彩色差异的样子:

在此处输入图片说明

colordiff通过运行安装sudo apt-get install colordiff


1
如果您想要颜色,我发现vim内置的差异实际上易于使用,就像S
先生

2

附加答案

如果不需要知道文件的哪些部分不同,则可以使用文件的校验和。使用md5sum或有很多方法可以做到这一点sha256sum。基本上,它们每个都输出一个字符串,文件内容将散列到该字符串。如果两个文件相同,则它们的哈希也将相同。当您下载软件(例如Ubuntu安装iso映像)时,通常会使用它。它们通常用于验证下载内容的完整性。

考虑下面的脚本,您可以在其中提供两个文件作为参数,该文件将告诉您它们是否相同。

#!/bin/bash

# Check if both files exist  
if ! [ -e "$1"  ];
then
    printf "%s doesn't exist\n" "$1"
    exit 2
elif ! [ -e "$2" ]
then
    printf "%s doesn't exist\n" "$2"
    exit 2
fi

# Get checksums of eithe file
file1_sha=$( sha256sum "$1" | awk '{print $1}')
file2_sha=$( sha256sum "$2" | awk '{print $1}')

# Compare the checksums
if [ "x$file1_sha" = "x$file2_sha" ]
then
    printf "Files %s and %s are the same\n" "$1" "$2"
    exit 0
else
    printf "Files %s and %s are different\n" "$1" "$2"
    exit 1
fi

样品运行:

$ ./compare_files.sh /etc/passwd ./passwd_copy.txt                                                                
Files /etc/passwd and ./passwd_copy.txt are the same
$ echo $?
0
$ ./compare_files.sh /etc/passwd /etc/default/grub                                                                
Files /etc/passwd and /etc/default/grub are different
$ echo $?
1

较旧的答案

此外,还有一个comm命令,该命令比较两个排序的文件,并在3列中提供输出:第1列表示文件#1唯一的项目,第2列表示文件#2唯一的项目,第3列表示两个文件中都存在的项目。

要禁止显示任一列,可以使用开关-1,-2和-3。使用-3将显示不同的行。

在下面,您可以看到正在使用的命令的屏幕截图。

在此处输入图片说明

仅有一项要求-必须对文件进行排序,以便正确比较它们。sort命令可以用于此目的。贝娄(Bellow)是另一个屏幕截图,其中对文件进行排序然后进行比较。仅从左贝隆开始到File_1的行,从第二列开始的行仅属于File_2

在此处输入图片说明


@DavidFoerster它有点难以移动:)现在完成了,虽然编辑
谢尔盖Kolodyazhnyy


2

colcmp.sh

比较格式为的2个文件中的名称/值对name value\n。写入nameOutput_file是否改变。需要bash v4 +用于关联数组

用法

$ ./colcmp.sh File_1.txt File_2.txt
User3 changed from 'US' to 'NG'
no change: User1,User2

输出文件

$ cat Output_File
User3 has changed

来源(colcmp.sh)

cmp -s "$1" "$2"
case "$?" in
    0)
        echo "" > Output_File
        echo "files are identical"
        ;;
    1)
        echo "" > Output_File
        cp "$1" ~/.colcmp.array1.tmp.sh
        sed -i -E "s/([^A-Za-z0-9 ])/\\\\\\1/g" ~/.colcmp.array1.tmp.sh
        sed -i -E "s/^(.*)$/#\\1/" ~/.colcmp.array1.tmp.sh
        sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A1\\[\\1\\]=\"\\2\"/" ~/.colcmp.array1.tmp.sh
        chmod 755 ~/.colcmp.array1.tmp.sh
        declare -A A1
        source ~/.colcmp.array1.tmp.sh

        cp "$2" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/([^A-Za-z0-9 ])/\\\\\\1/g" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/^(.*)$/#\\1/" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A2\\[\\1\\]=\"\\2\"/" ~/.colcmp.array2.tmp.sh
        chmod 755 ~/.colcmp.array2.tmp.sh
        declare -A A2
        source ~/.colcmp.array2.tmp.sh

        USERSWHODIDNOTCHANGE=
        for i in "${!A1[@]}"; do
            if [ "${A2[$i]+x}" = "" ]; then
                echo "$i was removed"
                echo "$i has changed" > Output_File
            fi
        done
        for i in "${!A2[@]}"; do
            if [ "${A1[$i]+x}" = "" ]; then
                echo "$i was added as '${A2[$i]}'"
                echo "$i has changed" > Output_File
            elif [ "${A1[$i]}" != "${A2[$i]}" ]; then
                echo "$i changed from '${A1[$i]}' to '${A2[$i]}'"
                echo "$i has changed" > Output_File
            else
                if [ x$USERSWHODIDNOTCHANGE != x ]; then
                    USERSWHODIDNOTCHANGE=",$USERSWHODIDNOTCHANGE"
                fi
                USERSWHODIDNOTCHANGE="$i$USERSWHODIDNOTCHANGE"
            fi
        done
        if [ x$USERSWHODIDNOTCHANGE != x ]; then
            echo "no change: $USERSWHODIDNOTCHANGE"
        fi
        ;;
    *)
        echo "error: file not found, access denied, etc..."
        echo "usage: ./colcmp.sh File_1.txt File_2.txt"
        ;;
esac

说明

据我所知,代码的分解及其含义。我欢迎您提出修改和建议。

基本文件比较

cmp -s "$1" "$2"
case "$?" in
    0)
        # match
        ;;
    1)
        # compare
        ;;
    *)
        # error
        ;;
esac

cmp会设置 $的值吗?如下

  • 0 =文件匹配
  • 1 =文件不同
  • 2 =错误

我选择使用case .. esac语句评估$?因为$的价值每个命令(包括测试([))之后的变化

或者,我可以使用变量来保存$?的值

cmp -s "$1" "$2"
CMPRESULT=$?
if [ $CMPRESULT -eq 0 ]; then
    # match
elif [ $CMPRESULT -eq 1 ]; then
    # compare
else
    # error
fi

以上与case语句具有相同的作用。我更喜欢IDK。

清除输出

        echo "" > Output_File

上面清除了输出文件,因此,如果没有用户更改,则输出文件将为空。

我在case语句中执行此操作,以便Output_file在出错时保持不变。

将用户文件复制到Shell脚本

        cp "$1" ~/.colcmp.arrays.tmp.sh

上面将File_1.txt复制到当前用户的主目录。

例如,如果当前用户是john,则上述内容将与cp“ File_1.txt” /home/john/.colcmp.arrays.tmp.sh相同

转义特殊字符

基本上,我很偏执。我知道这些字符在脚本中作为变量赋值的一部分运行时可能具有特殊含义或执行外部程序:

  • `-反勾号-执行程序和输出,就像输出是脚本的一部分一样
  • $-美元符号-通常在变量前添加前缀
  • $ {}-允许更复杂的变量替换
  • $()-idk这是做什么的,但我认为它可以执行代码

不知道我对bash有多少了解。我不知道其他哪些字符可能有特殊含义,但是我想用反斜杠将它们全部转义:

        sed -i -E "s/([^A-Za-z0-9 ])/\\\\\\1/g" ~/.colcmp.array1.tmp.sh

sed可以比正则表达式模式匹配做更多的事情。脚本模式 “ s /(查找)/(替换)/”专门执行模式匹配。

“ s /(查找)/(替换)/(修饰符)”

英文:捕获所有标点符号或特殊字符作为捕获组1(\\ 1)

  • (取代)= \\\\\\\ 1
    • \\\\ =文字字符(\\),即反斜杠
    • \\ 1 = 捕获组 1

英文:在所有特殊字符前加反斜杠

  • (修饰语)= g
    • g =全局替换

用英语:如果在同一行上找到多个匹配项,请全部替换

注释掉整个脚本

        sed -i -E "s/^(.*)$/#\\1/" ~/.colcmp.arrays.tmp.sh

上面使用正则表达式在〜/ .colcmp.arrays.tmp.sh的每一行添加bash注释字符()。之所以这样做,是因为以后我打算使用source命令执行〜/ .colcmp.arrays.tmp.sh,并且因为我不确定File_1.txt的整体格式。

我不想意外地执行任意代码。我认为没有人这样做。

“ s /(查找)/(替换)/”

用英语:捕获每一行作为捕获组1(\\ 1)

  • (替换)=#\\ 1
    • #=文字字符(#),即井号或井号
    • \\ 1 = 捕获组 1

用英语:用磅符号替换每行,后跟替换的行

将用户值转换为A1 [User] =“ value”

        sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A1\\[\\1\\]=\"\\2\"/" ~/.colcmp.arrays.tmp.sh

以上是该脚本的核心。

  • 转换为: #User1 US
    • 对此: A1[User1]="US"
    • 或这样:(A2[User1]="US"对于第二个文件)

“ s /(查找)/(替换)/”

用英语:

  • 需要但忽略前导注释字符(#)
  • 忽略前导空格
  • 捕获第一个单词作为捕获组1(\\ 1)
  • 需要一个空格(或制表符或空白)
    • 将被等号代替,因为
    • 它不属于任何捕获组,并且因为
    • (替换)模式在捕获组1和捕获组2之间放置等号
  • 捕获其余的行作为捕获组2

  • (替换)= A1 \\ [\\ 1 \\] = \“ \\ 2 \”

    • A1 \\ [-文字字符A1[可在名为的数组中开始数组分配A1
    • \\ 1 = 捕获组 1-不包括前导哈希(#)并且不包括前导空格-在这种情况下,捕获组1用于设置bash关联数组中的名称/值对的名称。
    • \\] = \“ =文字字符 ]="
      • ]=关闭数组分配,例如A1[User1 ]="US"
      • = =赋值运算符,例如variable = value
      • " =引用值以捕获空格...尽管我现在考虑过,让上面的代码反斜杠所有内容也反斜杠空格字符会更容易。
    • \\ 1 = 捕获组 2-在这种情况下,名称/值对的值
    • “ =关闭引号以捕获空格

用英语:用格式#name value的数组赋值运算符替换格式中的每一行A1[name]="value"

使可执行

        chmod 755 ~/.colcmp.arrays.tmp.sh

上面使用chmod使阵列脚本文件可执行。

我不确定这是否有必要。

声明关联数组(bash v4 +)

        declare -A A1

大写字母-A表示声明的变量将是关联数组

这就是为什么脚本需要bash v4或更高版本的原因。

执行我们的数组变量赋值脚本

        source ~/.colcmp.arrays.tmp.sh

我们已经:

  • 将文件从的行转换User value为的行A1[User]="value"
  • 使它可执行(也许),并且
  • 声明A1为关联数组...

上面我们脚本在当前shell中运行它。我们这样做是为了保留脚本设置的变量值。如果直接执行该脚本,它将生成一个新的shell,并且当新的shell退出时变量值会丢失,或者至少是我的理解。

这应该是一个功能

        cp "$2" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/([^A-Za-z0-9 ])/\\\\\\1/g" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/^(.*)$/#\\1/" ~/.colcmp.array2.tmp.sh
        sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A2\\[\\1\\]=\"\\2\"/" ~/.colcmp.array2.tmp.sh
        chmod 755 ~/.colcmp.array2.tmp.sh
        declare -A A2
        source ~/.colcmp.array2.tmp.sh

我们为$ 1A1做的事情与为$ 2A2做的事情一样。它确实应该是一个功能。我认为这时该脚本已经很混乱了,并且可以正常工作,所以我不会解决它。

检测删除的用户

        for i in "${!A1[@]}"; do
            # check for users removed
        done

上面循环通过关联数组键

            if [ "${A2[$i]+x}" = "" ]; then

上面使用变量替换来检测未设置的值与已显式设置为零长度字符串的变量之间的差异。

显然,有很多方法可以查看是否已设置变量。我选择了得票最多的那个。

                echo "$i has changed" > Output_File

上面将用户$ i添加到Output_File

检测添加或更改的用户

        USERSWHODIDNOTCHANGE=

上面清除了一个变量,因此我们可以跟踪未更改的用户。

        for i in "${!A2[@]}"; do
            # detect users added, changed and not changed
        done

上面循环通过关联数组键

            if ! [ "${A1[$i]+x}" != "" ]; then

上面使用变量替换来查看是否已设置变量

                echo "$i was added as '${A2[$i]}'"

因为$ i是数组键(用户名),所以$ A2 [$ i]应该从File_2.txt返回与当前用户关联的值。

例如,如果$ iUser1,则上面的内容为$ {A2 [User1]}

                echo "$i has changed" > Output_File

上面将用户$ i添加到Output_File

            elif [ "${A1[$i]}" != "${A2[$i]}" ]; then

因为$ i是数组键(用户名),所以$ A1 [$ i]应该从File_1.txt返回与当前用户关联的值,而$ A2 [$ i]应该从File_2.txt返回该值。

上面比较了两个文件中用户$ i的关联值。

                echo "$i has changed" > Output_File

上面将用户$ i添加到Output_File

                if [ x$USERSWHODIDNOTCHANGE != x ]; then
                    USERSWHODIDNOTCHANGE=",$USERSWHODIDNOTCHANGE"
                fi
                USERSWHODIDNOTCHANGE="$i$USERSWHODIDNOTCHANGE"

上面创建了一个逗号分隔的未更改用户列表。请注意,列表中没有空格,否则将需要引用下一个检查。

        if [ x$USERSWHODIDNOTCHANGE != x ]; then
            echo "no change: $USERSWHODIDNOTCHANGE"
        fi

上述报告的价值$ USERSWHODIDNOTCHANGE但前提是在一个价值$ USERSWHODIDNOTCHANGE。编写方式中,$ USERSWHODIDNOTCHANGE不能包含任何空格。如果确实需要空格,可以将上面的内容重写如下:

        if [ "$USERSWHODIDNOTCHANGE" != "" ]; then
            echo "no change: $USERSWHODIDNOTCHANGE"
        fi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.