Как сравнить два файла


83

Поэтому я хочу сравнить два файла строка за столбцом 2. Как я могу это сделать?

File_1.txt:

User1 US
User2 US
User3 US

File_2.txt:

User1 US
User2 US
User3 NG

Выходной файл:

User3 has changed

11
Использованиеdiff "File_1.txt" "File_2.txt"
Pandya

Также посетите: askubuntu.com/q/12473
Pandya

Ответы:


92

Посмотри в diffкоманду. Это хороший инструмент, и вы можете прочитать все об этом, набрав man diffв своем терминале.

Команда, которую вы захотите сделать, diff File_1.txt File_2.txtвыведет разницу между ними и должна выглядеть примерно так:

введите описание изображения здесь

Краткое примечание о прочтении вывода третьей команды: «стрелки» ( <и >) относятся к значению строки в левом файле ( <) по сравнению с правым файлом ( >), причем левый файл - тот, который вы ввели сначала в командной строке, в этом случаеFile_1.txt

Кроме того, вы можете заметить, что 4-ая команда diff ... | tee Output_Fileпередает эти результаты diffв a tee, который затем помещает этот вывод в файл, так что вы можете сохранить его на потом, если не хотите просматривать все это на консоли прямо в эту секунду.


Это может сделать другие файлы (например, изображения)? Или это ограничено только документами?
Грегори Опера

2
Насколько я знаю, это ограничивается текстовыми файлами. Код будет работать, так как он по сути текстовый, но любые двоичные файлы (которые представляют собой картинки) будут просто бесполезны. Вы можете сравнить , чтобы увидеть , если они идентичны, выполнив: diff file1 file2 -s. Вот пример: imgur.com/ShrQx9x
Митч

Есть ли способ раскрасить вывод? Я хотел бы сохранить это только для CLI, но с еще ... человеческим контактом.
Лазарь Любенович

36

Или вы можете использовать Meld Diff

Meld поможет вам сравнить файлы, каталоги и проекты с управлением версиями. Он обеспечивает двух- и трехстороннее сравнение файлов и каталогов, а также поддерживает многие популярные системы контроля версий.

Установить, запустив:

sudo apt-get install meld

Ваш пример:

введите описание изображения здесь

Сравнить каталог:

введите описание изображения здесь

Пример с полным текстом:

введите описание изображения здесь


18

Вы можете использовать vimdiff .

Пример:

vimdiff  file1  file2

1
у этого есть цвета
Джейк Торонто

Это помогло мне, так как показало, что конец строки моего первого файла был, dosа второй - 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

cmpгораздо быстрее, чем diffесли бы все, что вам нужно, это код возврата.
стевеслива

8

Meldэто действительно отличный инструмент. Но вы также можете использовать diffuseдля визуального сравнения двух файлов:

diffuse file1.txt file2.txt

введите описание изображения здесь


7

Придерживаясь вопроса (file1, file2, outputfile с сообщением «изменилось»), скрипт ниже работает.

Скопируйте скрипт в пустой файл, сохраните его как 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 diff действительно прост в использовании, как в ответе Mr.S
thomasrutter

2

Дополнительный ответ

Если нет необходимости знать, какие части файлов различаются, вы можете использовать контрольную сумму файла. Есть много способов сделать это, используя md5sumили sha256sum. По сути, каждый из них выводит строку, к которой относится хэш содержимого файла. Если два файла одинаковы, их хэш будет одинаковым. Это часто используется при загрузке программного обеспечения, такого как установочные образы Ubuntu. Они часто используются для проверки целостности загруженного контента.

Рассмотрим скрипт ниже, где вы можете указать два файла в качестве аргументов, и файл сообщит вам, являются ли они одинаковыми или нет.

#!/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Команда может быть использована для этой цели. Ниже приведен скриншот, где файлы сортируются, а затем сравниваются. Строки, начинающиеся с левого столбца только для File_1, строки, начинающиеся со столбца 2, принадлежат только File_2

введите описание изображения здесь


@DavidFoerster довольно сложно редактировать на мобильном телефоне :) Готово, правда
Сергей Колодяжный

2

Установите git и используйте

$ git diff filename1 filename2

И вы получите вывод в хорошем цветном формате

Git установка

$ apt-get update
$ apt-get install git-core

2

colcmp.sh

Сравнивает пары имя / значение в 2 файлах в формате name value\n. Записывает nameв Output_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 = ошибка

Я решил использовать регистр . Esac для вычисления $? потому что значение $? изменяется после каждой команды, включая тест ([).

В качестве альтернативы я мог бы использовать переменную для хранения значения $? :

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

Выше делает то же самое, что и утверждение дела. ИДК, который мне нравится больше.

Очистить вывод

        echo "" > Output_File

Выше очищает выходной файл, поэтому, если ни один пользователь не изменился, выходной файл будет пустым.

Я делаю это внутри инструкций case, чтобы файл Output_file оставался неизменным при ошибке.

Скопируйте файл пользователя в сценарий оболочки

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

Выше копирует File_1.txt в домашнюю директорию текущего пользователя.

Например, если текущий пользователь является пользователем john, приведенное выше будет таким же, как cp "File_1.txt" /home/john/.colcmp.arrays.tmp.sh

Побег Специальные персонажи

По сути, я параноик. Я знаю, что эти символы могут иметь особое значение или выполнять внешнюю программу при запуске в сценарии как часть назначения переменной:

  • `- back-tick - выполняет программу и вывод, как если бы вывод был частью вашего скрипта
  • $ - знак доллара - обычно префикс переменной
  • $ {} - допускает более сложную подстановку переменных
  • $ () - IDK, что это делает, но я думаю, что он может выполнять код

Чего я не знаю, так это того, как много я не знаю о bash. Я не знаю, какие другие символы могут иметь особое значение, но я хочу избежать их всех с помощью обратной косой черты:

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

sed может сделать гораздо больше, чем сопоставление с шаблоном регулярного выражения . Шаблон сценария "s / (find) / (replace) /" специально выполняет сопоставление шаблона.

"S / (поиск) / (заменить) / (модификаторы)"

на английском языке: запишите любую пунктуацию или специальный символ в качестве группы будущего 1 (\\ 1)

по-английски: префикс всех специальных символов с обратной косой чертой

  • (модификаторы) = г
    • г = глобально заменить

на английском: если в одной строке найдено более одного совпадения, замените их все

Закомментируйте весь скрипт

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

Выше используется регулярное выражение для префикса каждой строки ~ / .colcmp.arrays.tmp.sh с символом комментария bash ( # ). Я делаю это потому, что позже я намереваюсь выполнить ~ / .colcmp.arrays.tmp.sh, используя команду source, и потому что я точно не знаю весь формат File_1.txt .

Я не хочу случайно выполнять произвольный код. Я не думаю, что кто-то делает.

"S / (поиск) / (заменить) /"

на английском: запишите каждую строку как группу будущего 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"(для 2-го файла)

"S / (поиск) / (заменить) /"

по-английски:

  • требовать, но игнорировать начальные символы комментария (#)
  • игнорировать ведущие пробелы
  • захватить первое слово как caputure group 1 (\\ 1)
  • требуется пробел (или табуляция, или пробел)
    • который будет заменен знаком равенства, потому что
    • это не является частью какой-либо группы захвата, и потому что
    • шаблон (замена) ставит знак равенства между группой захвата 1 и группой захвата 2
  • захватить остальную часть линии в качестве группы захвата 2

  • (заменить) = A1 \\ [\\ 1 \\] = \ "\\ 2 \"

    • A1 \\ [- буквенные символы A1[для начала назначения массива в массиве с именемA1
    • \\ 1 = группа захвата 1 - которая не включает начальный хеш (#) и не включает начальный пробел - в этом случае группа захвата 1 используется для установки имени пары имя / значение в ассоциативном массиве bash.
    • \\] = \ "= буквенные символы ]="
      • ]= закрыть присвоение массива, например, A1[User1 ]="US"
      • = = оператор присваивания, например, переменная = значение
      • " = значение кавычки для захвата пробелов ... хотя теперь, когда я думаю об этом, было бы проще позволить приведенному выше коду, который ставит обратную косую черту, также и символы обратной косой черты.
    • \\ 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 как ассоциативный массив ...

Выше мы поставили скрипт для запуска в текущей оболочке. Мы делаем это, чтобы сохранить значения переменных, которые устанавливаются сценарием. Если вы выполняете сценарий напрямую, он порождает новую оболочку, и значения переменных теряются при выходе из новой оболочки, или, по крайней мере, я так понимаю.

Это должно быть функцией

        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

Мы делаем то же самое для $ 1 и A1, что мы делаем для $ 2 и A2 . Это действительно должно быть функцией. Я думаю, что на данный момент этот сценарий достаточно запутан и работает, поэтому я не собираюсь его исправлять.

Определить удаленных пользователей

        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 .

Например, если $ i равен User1 , приведенное выше читается как $ {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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.