Як порівняти два файли


83

Отже, в основному я хочу зробити порівняння двох файлів за рядком за стовпцем 2. Як я можу це зробити?

Файл_1.txt:

User1 US
User2 US
User3 US

Файл_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

Крім того, ви можете помітити, що четверта команда - diff ... | tee Output_Fileце передача результатів diffв а 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 -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якщо все, що ви хочете, це повернення коду.
stevesliva

8

Meldце дійсно чудовий інструмент. Але ви також diffuseможете візуально порівняти два файли:

diffuse file1.txt file2.txt

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


7

Літерально дотримуючись питання (файл1, файл2, вихідний файл із повідомленням "змінився") працює сценарій нижче.

Скопіюйте скрипт у порожній файл, збережіть його як 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, насправді проста у використанні, як у відповіді 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

Порівняє пари імен / значень у двох файлах у форматі 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 = помилка

Я вирішив використовувати випадок case .. esac для оцінки $? тому що значення $? зміни після кожної команди, включаючи тест ([).

Як варіант, я міг би використовувати змінну, щоб утримувати значення $? :

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

Вище робиться те саме, що і заява справи. IDK, який мені більше подобається.

Очистіть вихід

        echo "" > Output_File

Вище очищає вихідний файл, тому якщо жоден користувач не змінився, вихідний файл буде порожнім.

Я роблю це всередині випадок, щоб Output_file залишався незмінним при помилці.

Скопіюйте файл користувача в сценарій оболонки

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

Вище копіюйте File_1.txt до домашнього режиму поточного користувача.

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

Втеча спеціальних персонажів

В основному я параноїк. Я знаю, що ці символи можуть мати особливе значення або виконувати зовнішню програму під час запуску в скрипті в рамках присвоєння змінної:

  • `- back-галочка - виконує програму, а результат виходить так, ніби вихід був частиною вашого сценарію
  • Знак долара - зазвичай префікс змінної
  • $ {} - дозволяє здійснити більш складну підстановку змінної
  • $ () - idk, що це робить, але я думаю, що він може виконати код

Чого я не знаю - це те, наскільки я не знаю про баш. Я не знаю, які інші символи можуть мати особливе значення, але я хочу уникнути їх усією зворотною косою рисою:

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

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

"s / (знайти) / (замінити) / (модифікатори)"

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

  • (замінити) = \\\\\\ 1

англійською мовою: приставка всіх спеціальних символів із зворотним нахилом

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

англійською: якщо на одній лінії знайдено більше одного матчу, замініть їх усіма

Прокоментуйте весь сценарій

        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 / (знайти) / (замінити) /"

англійською:

  • вимагати, але ігнорувати провідних символів коментарів (#)
  • ігноруйте провідні пробіли
  • зафіксувати перше слово як групу 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",
  • зробив його виконуваним (можливо) та
  • оголошено А1 асоціативним масивом ...

Вище ми джерело сценарію для запуску в поточній оболонці. Ми робимо це, щоб ми могли зберегти змінні значення, встановлені сценарієм. Якщо ви виконуєте сценарій безпосередньо, він породжує нову оболонку, і значення змінних втрачаються при виході нової оболонки, або, принаймні, це я розумію.

Це має бути функцією

        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.