Im Grunde möchte ich also zwei Dateien zeilenweise in Spalte 2 vergleichen. Wie könnte ich das erreichen?
Datei_1.txt:
User1 US
User2 US
User3 US
Datei_2.txt:
User1 US
User2 US
User3 NG
Ausgabedatei:
User3 has changed
Im Grunde möchte ich also zwei Dateien zeilenweise in Spalte 2 vergleichen. Wie könnte ich das erreichen?
Datei_1.txt:
User1 US
User2 US
User3 US
Datei_2.txt:
User1 US
User2 US
User3 NG
Ausgabedatei:
User3 has changed
Antworten:
Schau in den diff
Befehl. Es ist ein gutes Werkzeug und Sie können alles darüber lesen, indem Sie man diff
in Ihr Terminal tippen.
Der Befehl, den Sie ausführen möchten, gibt diff File_1.txt File_2.txt
den Unterschied zwischen den beiden aus und sollte ungefähr so aussehen:
Ein kurzer Hinweis zum Lesen der Ausgabe des dritten Befehls: Die Pfeile ( <
und >
) verweisen auf den Wert der Zeile in der linken Datei ( <
) gegenüber der rechten Datei ( >
), wobei die linke Datei die von Ihnen eingegebene ist in diesem Fall zuerst in der BefehlszeileFile_1.txt
Außerdem stellen Sie möglicherweise fest, dass der vierte Befehl darin besteht, diff ... | tee Output_File
die Ergebnisse von diff
in ein zu tee
leiten, wodurch diese Ausgabe in eine Datei verschoben wird , sodass Sie sie für einen späteren Zeitpunkt speichern können, wenn Sie nicht alles in dieser Sekunde auf der Konsole anzeigen möchten.
diff file1 file2 -s
. Hier ist ein Beispiel: imgur.com/ShrQx9x
Oder Sie können Meld Diff verwenden
Mit Meld können Sie Dateien, Verzeichnisse und versionskontrollierte Projekte vergleichen. Es bietet einen Zwei- und Drei-Wege-Vergleich von Dateien und Verzeichnissen und unterstützt viele gängige Versionskontrollsysteme.
Installieren Sie, indem Sie Folgendes ausführen:
sudo apt-get install meld
Ihr Beispiel:
Verzeichnis vergleichen:
Beispiel mit vollem Text:
dos
und die zweite in war unix
.
FWIW, ich mag eher, was ich mit Side-by-Side-Ausgabe von diff bekomme
diff -y -W 120 File_1.txt File_2.txt
würde etwas geben wie:
User1 US User1 US
User2 US User2 US
User3 US | User3 NG
Sie können den Befehl verwenden cmp
:
cmp -b "File_1.txt" "File_2.txt"
Ausgabe wäre
a b differ: byte 25, line 3 is 125 U 116 N
cmp
ist viel schneller als diff
wenn alles was Sie wollen, der Rückkehrcode ist.
Wenn Sie sich an die Frage halten (Datei1, Datei2, Ausgabedatei mit der Meldung "Hat sich geändert"), funktioniert das folgende Skript.
Kopieren Sie das Skript in eine leere Datei, speichern Sie es als compare.py
, machen Sie es ausführbar und führen Sie es mit dem folgenden Befehl aus:
/path/to/compare.py <file1> <file2> <outputfile>
Das Drehbuch:
#!/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")
Mit ein paar zusätzlichen Zeilen können Sie es entweder in eine Ausgabedatei oder auf das Terminal drucken lassen, je nachdem, ob die Ausgabedatei definiert ist:
So drucken Sie in eine Datei:
/path/to/compare.py <file1> <file2> <outputfile>
So drucken Sie in das Terminalfenster:
/path/to/compare.py <file1> <file2>
Das Drehbuch:
#!/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"
Eine einfache Möglichkeit ist die Verwendung colordiff
, die sich so verhält, diff
aber die Ausgabe einfärbt. Dies ist sehr hilfreich zum Lesen von Unterschieden. Verwenden Sie Ihr Beispiel,
$ 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
Wobei die u
Option ein einheitliches Diff ergibt. So sieht das eingefärbte Diff aus:
Installieren Sie colordiff
durch Ausführen sudo apt-get install colordiff
.
Wenn Sie nicht wissen müssen, welche Teile der Dateien unterschiedlich sind, können Sie die Prüfsumme der Datei verwenden. Es gibt viele Möglichkeiten, dies mit md5sum
oder zu tun sha256sum
. Grundsätzlich gibt jeder von ihnen eine Zeichenfolge aus, zu der ein Dateiinhalt-Hash gehört. Wenn die beiden Dateien identisch sind, ist auch der Hash identisch. Dies wird häufig verwendet, wenn Sie Software herunterladen, z. B. Ubuntu-Installations-ISO-Images. Sie werden häufig zur Überprüfung der Integrität eines heruntergeladenen Inhalts verwendet.
Betrachten Sie das folgende Skript, in dem Sie zwei Dateien als Argumente angeben können. In der Datei wird angegeben, ob sie identisch sind oder nicht.
#!/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
Probelauf:
$ ./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
Zusätzlich gibt es einen comm
Befehl, der zwei sortierte Dateien vergleicht und die Ausgabe in 3 Spalten liefert: Spalte 1 für Elemente, die nur in Datei 1 vorhanden sind, Spalte 2 für Elemente, die nur in Datei 2 vorhanden sind, und Spalte 3 für Elemente, die in beiden Dateien vorhanden sind.
Zum Unterdrücken einer Spalte können Sie die Schalter -1, -2 und -3 verwenden. Mit -3 werden die Zeilen angezeigt, die sich unterscheiden.
Unten sehen Sie den Screenshot des Befehls in Aktion.
Es gibt nur eine Anforderung: Die Dateien müssen sortiert sein, damit sie richtig verglichen werden können. sort
Befehl kann für diesen Zweck verwendet werden. Unten sehen Sie einen weiteren Screenshot, in dem Dateien sortiert und dann verglichen werden. Zeilen, die links beginnen, gehören nur zu File_1, Zeilen, die in Spalte 2 beginnen, gehören nur zu File_2
Installieren Sie Git und verwenden Sie
$ git diff filename1 filename2
Und Sie erhalten eine Ausgabe in schönen Farben
Git- Installation
$ apt-get update
$ apt-get install git-core
Vergleicht Name / Wert-Paare in 2 Dateien im Format name value\n
. Schreibt das name
zu, Output_file
wenn es geändert wird. Benötigt bash v4 + für assoziative Arrays .
$ ./colcmp.sh File_1.txt File_2.txt
User3 changed from 'US' to 'NG'
no change: User1,User2
$ cat Output_File
User3 has changed
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
Aufschlüsselung des Codes und was er bedeutet, nach bestem Wissen. Ich freue mich über Änderungen und Vorschläge.
cmp -s "$1" "$2"
case "$?" in
0)
# match
;;
1)
# compare
;;
*)
# error
;;
esac
cmp setzt den Wert von $? wie folgt :
Ich habe mich für eine case .. esac- Anweisung entschieden, um $ auszuwerten . weil der Wert von $? ändert sich nach jedem Befehl, einschließlich test ([).
Alternativ könnte ich eine Variable verwendet haben, um den Wert von $ zu halten ? :
cmp -s "$1" "$2"
CMPRESULT=$?
if [ $CMPRESULT -eq 0 ]; then
# match
elif [ $CMPRESULT -eq 1 ]; then
# compare
else
# error
fi
Oben wird dasselbe wie in der case-Anweisung gemacht. IDK was mir besser gefällt.
echo "" > Output_File
Oben wird die Ausgabedatei gelöscht. Wenn also keine Benutzer geändert wurden, ist die Ausgabedatei leer.
Ich mache dies in den case- Anweisungen, damit die Output_file im Fehlerfall unverändert bleibt.
cp "$1" ~/.colcmp.arrays.tmp.sh
Oben kopiert File_1.txt in das Ausgangsverzeichnis des aktuellen Benutzers.
Wenn der aktuelle Benutzer beispielsweise john ist, ist das oben Genannte dasselbe wie cp "File_1.txt" /home/john/.colcmp.arrays.tmp.sh
Grundsätzlich bin ich paranoid. Ich weiß, dass diese Zeichen eine besondere Bedeutung haben oder ein externes Programm ausführen können, wenn sie in einem Skript als Teil der Variablenzuweisung ausgeführt werden:
Was ich nicht weiß, ist, wie viel ich nicht über Bash weiß. Ich weiß nicht, welche anderen Zeichen eine besondere Bedeutung haben könnten, aber ich möchte sie alle mit einem Backslash umgehen:
sed -i -E "s/([^A-Za-z0-9 ])/\\\\\\1/g" ~/.colcmp.array1.tmp.sh
sed kann viel mehr als nur den Mustervergleich mit regulären Ausdrücken . Das Skriptmuster "s / (find) / (replace) /" führt speziell die Musterübereinstimmung durch.
"s / (find) / (replace) / (modifiers)"
in englischer Sprache: Zeichensetzung oder Sonderzeichen als Erfassungsgruppe 1 erfassen (\\ 1)
auf englisch: allen Sonderzeichen einen Backslash voranstellen
auf englisch: wenn mehr als eine Übereinstimmung in derselben Zeile gefunden wird, ersetzen Sie sie alle
sed -i -E "s/^(.*)$/#\\1/" ~/.colcmp.arrays.tmp.sh
Oben wird ein regulärer Ausdruck verwendet, um jeder Zeile von ~ / .colcmp.arrays.tmp.sh ein Bash-Kommentarzeichen ( # ) voranzustellen . Ich mache das, weil ich später vorhabe, ~ / .colcmp.arrays.tmp.sh mit dem Befehl source auszuführen, und weil ich das gesamte Format von nicht genau kenne File_1.txt kenne .
Ich möchte nicht versehentlich beliebigen Code ausführen. Ich glaube nicht, dass jemand das tut.
s / (find) / (replace) /
in Englisch: Erfassen Sie jede Zeile als Erfassungsgruppe 1 (\\ 1)
in englischer sprache: ersetzen sie jede zeile durch ein rautenzeichen, gefolgt von der zeile, die ersetzt wurde
sed -i -E "s/^#\\s*(\\S+)\\s+(\\S.*?)\\s*\$/A1\\[\\1\\]=\"\\2\"/" ~/.colcmp.arrays.tmp.sh
Oben ist der Kern dieses Skripts.
#User1 US
A1[User1]="US"
A2[User1]="US"
(für die 2. Datei)s / (find) / (replace) /
auf Englisch:
Erfassen Sie den Rest der Zeile als Erfassungsgruppe 2
(Ersetzen) = A1 \\ [\\ 1 \\] = \ "\\ 2 \"
A1[
zum Starten der Arrayzuweisung in einem aufgerufenen ArrayA1
]="
]
= nahe A1[
Arrayzuordnung zB User1 ]="
US"
=
= Zuweisungsoperator zB Variable = Wert"
= Anführungszeichen, um Leerzeichen zu erfassen ... obwohl es jetzt, wo ich darüber nachdenke, einfacher gewesen wäre, den Code über diesem Backslash alles in Backslash-Leerzeichen umzuwandeln.auf Englisch: Ersetzen Sie jede Zeile im Format #name value
durch einen Array-Zuweisungsoperator im FormatA1[name]="value"
chmod 755 ~/.colcmp.arrays.tmp.sh
Oben wird chmod verwendet , um die Array-Skriptdatei ausführbar zu machen.
Ich bin mir nicht sicher, ob das notwendig ist.
declare -A A1
Das Großbuchstaben -A gibt an, dass die deklarierten Variablen assoziative Arrays sind .
Aus diesem Grund benötigt das Skript bash v4 oder höher.
source ~/.colcmp.arrays.tmp.sh
Wir haben schon:
User value
zu Zeilen von A1[User]="value"
,Oben haben wir beziehen das Skript es in dem aktuell Shell ausgeführt werden . Wir tun dies, um die vom Skript gesetzten Variablenwerte beizubehalten. Wenn Sie das Skript direkt ausführen, wird eine neue Shell erstellt, und die Variablenwerte gehen verloren, wenn die neue Shell beendet wird.
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
Wir machen dasselbe für $ 1 und A1 wie für $ 2 und A2 . Es sollte wirklich eine Funktion sein. Ich denke, an diesem Punkt ist dieses Skript verwirrend genug und es funktioniert, also werde ich es nicht reparieren.
for i in "${!A1[@]}"; do
# check for users removed
done
Oben werden assoziative Array-Schlüssel durchlaufen
if [ "${A2[$i]+x}" = "" ]; then
Oben wird die Variablensubstitution verwendet, um den Unterschied zwischen einem nicht festgelegten Wert und einer Variablen zu ermitteln, die explizit auf eine Zeichenfolge der Länge Null festgelegt wurde.
Anscheinend gibt es viele Möglichkeiten, um festzustellen, ob eine Variable festgelegt wurde . Ich habe den mit den meisten Stimmen gewählt.
echo "$i has changed" > Output_File
Oben wird der Benutzer $ i zur Ausgabedatei hinzugefügt
USERSWHODIDNOTCHANGE=
Oben wird eine Variable gelöscht, damit wir die Benutzer verfolgen können, die sich nicht geändert haben.
for i in "${!A2[@]}"; do
# detect users added, changed and not changed
done
Oben werden assoziative Array-Schlüssel durchlaufen
if ! [ "${A1[$i]+x}" != "" ]; then
Oben wird die Variablensubstitution verwendet, um festzustellen, ob eine Variable festgelegt wurde .
echo "$i was added as '${A2[$i]}'"
Da $ i der Array-Schlüssel (Benutzername) ist, sollte $ A2 [$ i] den Wert zurückgeben, der dem aktuellen Benutzer aus File_2.txt zugeordnet ist .
Wenn zum Beispiel $ i ist User1 , die oben lautet wie $ {A2 [User1]}
echo "$i has changed" > Output_File
Oben wird der Benutzer $ i zur Ausgabedatei hinzugefügt
elif [ "${A1[$i]}" != "${A2[$i]}" ]; then
Da $ i der Array-Schlüssel (Benutzername) ist, sollte $ A1 [$ i] den dem aktuellen Benutzer zugeordneten Wert aus File_1.txt und $ A2 [$ i] den Wert aus File_2.txt zurückgeben .
Oben werden die zugehörigen Werte für Benutzer $ i aus beiden Dateien verglichen .
echo "$i has changed" > Output_File
Oben wird der Benutzer $ i zur Ausgabedatei hinzugefügt
if [ x$USERSWHODIDNOTCHANGE != x ]; then
USERSWHODIDNOTCHANGE=",$USERSWHODIDNOTCHANGE"
fi
USERSWHODIDNOTCHANGE="$i$USERSWHODIDNOTCHANGE"
Oben wird eine durch Kommas getrennte Liste von Benutzern erstellt, die sich nicht geändert haben. Beachten Sie, dass die Liste keine Leerzeichen enthält. Andernfalls muss der nächste Scheck in Anführungszeichen gesetzt werden.
if [ x$USERSWHODIDNOTCHANGE != x ]; then
echo "no change: $USERSWHODIDNOTCHANGE"
fi
Oben wird der Wert von $ USERSWHODIDNOTCHANGE gemeldet, jedoch nur, wenn $ USERSWHODIDNOTCHANGE einen Wert enthält . So wie dies geschrieben ist, darf $ USERSWHODIDNOTCHANGE keine Leerzeichen enthalten. Wenn Leerzeichen erforderlich sind, könnte dies wie folgt geändert werden:
if [ "$USERSWHODIDNOTCHANGE" != "" ]; then
echo "no change: $USERSWHODIDNOTCHANGE"
fi
diff "File_1.txt" "File_2.txt"