Αποδιπλοποίηση των γραμμών ενός αρχείου διατηρώντας τη σειρά τους

Πως να αφαιρέσετε τις διπλές γραμμές ενός αρχείο σε Linux χωρίς να αλλάξει η σειρά τους - επεξήγηση awk one-liner κώδικα.
This article is also available in english.
Πρόσθεσα νέα σελίδα στο μπλογκ: "Αστεία" για προγραμματιστές

Υποθέστε ότι έχετε ένα αρχείο κειμένου και θέλετε να αφαιρέσετε όλες τις διπλές γραμμές.

TL;DR

Για να αφαιρεθούν οι γραμμές διατηρώντας τη σειρά τους στο αρχείο χρησιμοποιήστε:

awk '!visited[$0]++' your_file > deduplicated_file

Πως δουλεύει

Το script διατηρεί έναν πίνακα συσχετισμών (associative array) με κλειδιά την κάθε μοναδική γραμμή του αρχείου και τιμές τον αριθμό των εμφανίσεων τους. Για κάθε γραμμή του αρχείου, αν οι εμφανίσεις της γραμμής είναι 0 τότε αυξάνονται κατά 1 και εκτυπώνεται η γραμμή, διαφορετικά απλά αυξάνονται οι εμφανίσεις χωρίς να πραγματοποιείται εκτύπωση της γραμμής.

Δεν ήμουν εξοικειωμένος με την awk και ήθελα να καταλάβω πως επιτυγχάνεται αυτή η λειτουργικότητα με ένα τόσο μικρawk script. Έκανα την έρευνά μου και ιδού τι συμβαίνει:

  • το script της awk !visited[$0]++ εκτελείται για κάθε γραμμή του αρχείου εισόδου
  • η visited[] είναι μια μεταβλητή τύπου Πίνακα συσχετισμών (associative array) (δηλαδή Map). Δε χρειάζεται να την αρχικοποιήσουμε, η awk θα το κάνει αυτό για μας την πρώτη φορά που θα την προσπελάσουμε.
  • η μεταβλητή $0 κρατά τα περιεχόμενα της γραμμής που βρίσκεται κάθε φορά υπό επεξεργασία
  • ο κώδικας visited[$0] προσπελαύνει την τιμή που είναι αποθηκευμένη στον πίνακα συσχετισμών με κλειδί ίσο με $0 (που όπως είπαμε, είναι η υπό επεξεργασία γραμμή του αρχείου), δηλαδή τις εμφανίσεις (που εκχωρούμε παρακάτων)
  • ο τελεστής ! δημιουργεί την άρνηση της τιμής των εμφανίσεων:
  • ο τελεστής ++ αυξάνει την τιμή της μεταβλητής (visited[$0]) κατά ένα - 1.
    • Αν η τιμή είναι κενή, η awk τη μετατρέπει σε αριθμό άρα σε μηδέν - 0 αυτόματα και κατόπιν την αυξάνει.
    • Σημείωση: η πράξη εκτελείται αφού προσπελάσουμε πρώτα την τιμή της μεταβλητής.

Συνοψίζοντας, ολόκληρη η έκφραση εκτιμάται ως:

  • true αν οι εμφανίσεις είναι 0 ή το κενό λεκτικό
  • false αν οι εμφανίσεις είναι περισσότερες από μηδέν - 0

Οι δηλώσεις (statements) στην awk αποτελούνται από ένα μοτίβο (pattern) ή έκφραση (expression) και μια συσχετισμένη ενέργεια (associated action).

<pattern/expression> { <action> }

Αν το μοτίβο/έκφραση επιτύχει τότε εκτελείται η συσχετισμένη ενέργεια. Αν δεν δηλώσουμε την συσχετισμένη ενέργεια, η awk εξ ορισμού θα εκτυπώσει (print) τα δεδομένα εισόδου.

An omitted action is equivalent to { print $0 }

Το script μας αποτελείται από μια awk δήλωση με μία έκφραση, παραλείποντας την συσχετισμένη ενέργεια. Οπότε αυτό:

awk '!visited[$0]++' your_file > deduplicated_file

είναι ισοδύναμο με αυτό:

awk '!visited[$0]++ { print $0 }' your_file > deduplicated_file

Για κάθε γραμμή του αρχείου, αν η έκφραση επιτύχει τότε η γραμμή εκτυπώνεται στην έξοδο (output). Διαφορετικά, η συσχετισμένη ενέργεια δεν εκτελείται, τίποτα δεν εκτυπώνεται.

Γιατί να μη χρησιμοποιήσω την εντολή uniq;

Η εντολή uniq αφαιρεί μόνο τις γειτονικές διπλές γραμμές. Επίδειξη:

$ cat test.txt
A
A
A
B
B
B
A
A
C
C
C
B
B
A
$ uniq < test.txt
A
B
A
C
B
A

Άλλες προσεγγίσεις

Χρησιμοποιώντας την εντολή sort

Μπορούμε να χρησιμοποιήσουμε την παρακάτω sort εντολή για να αφαιρέσουμε τις διπλές γραμμές αλλά η σειρά των γραμμών δεν διατηρείται.

sort -u your_file > sorted_deduplicated_file

Χρησιμοποιώντας τις εντολές cat, sort και cut

Στην προηγούμενη προσέγγιση θα παραγόταν ένα αποδιπλοποιημένο αρχείο οι γραμμές του οποίου θα ήταν ταξινομημένες βάσει του περιεχομένου τους. Χρησιμοποιώντας σωληνώσεις με ένα σύνολο εντολών μπορούμε να διατηρήσουμε την αρχική σειρά των γραμμών:

cat -n your_file | sort -uk2 | sort -nk1 | cut -f2-

Πως λειτουργεί

Υποθέστε ότι έχουμε το ακόλουθο αρχείο κειμένου:

abc
ghi
abc
def
xyz
def
ghi
klm

Η εντολή cat -n test.txt τοποθετεί τον αριθμό της θέσης κάθε σειράς στην αρχή της.

1	abc
2	ghi
3	abc
4	def
5	xyz
6	def
7	ghi
8	klm

Η εντολή sort -uk2 ταξινομεί τις γραμμές βάσει της δεύτερης στήλης (επιλογή k2) και κρατά μόνο την πρώτη εμφάνιση των γραμμών που έχουν την ίδια τιμή στη δεύτερη στήλη (επιλογή u)

1	abc
4	def
2	ghi
8	klm
5	xyz

Η εντολή sort -nk1 ταξινομεί τις γραμμές βάσει της πρώτης στήλης (επιλογή k1) αντιμετωπίζοντας την τιμή της στήλης ως αριθμό (επιλογή n)

1	abc
2	ghi
4	def
5	xyz
8	klm

Τέλος, η εντολή cut -f2- εκτυπώνει κάθε γραμμή ξεκινώντας από τη δεύτερη στήλη και φτάνοντας ως το τέλος της (επιλογή -f2-: προσέξτε την παύλα - στο τέλος που δηλώνει να συμπεριληφθεί το υπόλοιπο της γραμμής)

abc
ghi
def
xyz
klm

References

Αυτά. Να μια γάτα.

duplicate cat

Σχόλια
Για σχόλια, feedback, λάθη κ.λπ. παρακαλώ χρησιμοποιήστε αυτό το GitHub issue.
Ευχαριστώ!