Του Λάζαρου Λαζαρίδη*
Πρόσφατα είδα μια πολύ ωραία εντολή που βρίσκει τις παλίνδρομες ημερομηνίες μεταξύ του τώρα και x
ημερών στο παρελθόν.
Η εντολή κοινοποιήθηκε στο Twitter από το λογαριασμό @climagic (ένας πολύ ωραίος λογαριασμός σχετικά με tweets που σχετίζονται με την γραμμή εντολών).
Σε αυτό το άρθρο θα σπάσω την εντολή σε τμήματα και θα εξηγήσω πως λειτουργεί το καθένα.
Τι είναι το παλίνδρομο – Καρκινική γραφή
Καρκινικές επιγραφές ή καρκίνοι ονομάζονται συμμετρικές φράσεις οι οποίες μπορούν να διαβαστούν είτε από την αρχή είτε από το τέλος. – Καρκινική γραφή – Wikipedia
Πριν λίγες μέρες, η ημερομηνία ήταν παλίνδρομη: 02/02/2020
Η εντολή
printf "now - %d days\n" {1..332044} |date -f- +%Y%_m%_d$'\n'%Y%m%d |tr -d ' ' > alldates.txt; rev alldates.txt >revdates.txt; paste alldates.txt revdates.txt |awk '$1==$2{print $1}'
και παρακάτω μια περισσότερο αναγνώσιμη εκδοχή της, χρησιμοποιώντας νέα γραμμή μετά από κάθε σωλήνωση.
printf "now - %d days\n" {1..332044} |
date -f- +%Y%_m%_d$'\n'%Y%m%d |
tr -d ' ' > alldates.txt; rev alldates.txt >revdates.txt; paste alldates.txt revdates.txt |
awk '$1==$2{print $1}'
Θα εξηγήσω κάθε τμήμα της εντολής χωριστά.
Σημειώστε ότι το αποτέλεσμα (output – έξοδος) του κάθε τμήματος
αποτελεί είσοδο (input) για το επόμενο τμήμα (αυτό που ακολουθεί τη
σωλήνωση |
).
printf “now – %d days\n” {1..332044}
Το πρώτο τμήμα είναι αρκετά απλό αλλά έχει και ένα ενδιαφέρον κομμάτι, το μηχανισμό επέκτασης αγκυλών τον οποίο δε γνώριζα.
Ανοίξτε ένα τερματικό και πληκτρολογήστε τις ακόλουθες εντολές:
# Από το 1 έως το 10
$ echo {1..10}
1 2 3 4 5 6 7 8 9 10
# Από το a έως το z
$ echo {a..z}
a b c d e f g h i j k l m n o p q r s t u v w x y z
# Από το 10 έως το 20
$ echo Before-{10..20}-After
Before-10-After Before-11-After Before-12-After Before-13-After Before-14-After Before-15-After Before-16-After Before-17-After Before-18-After Before-19-After Before-20-After
Όπως μπορείτε να δείτε, ο μηχανισμός επέκτασης αγκυλών επιτρέπει την παραγωγή τιμών βασισμένων σε μια ακολουθία – sequence που του ορίζουμε.
Μάθετε περισσότερα για τον μηχανισμό επέκτασης αγκυλών εδώ.
Επεξήγηση εντολής
- τυπώνουμε τη φράση “now – x days” τόσες φορές όσες το πλήθος των τιμών που προκύπτουν από την ακολουθία – sequence
- η ακολουθία
{1..332044}
θα παράξει 332044 τιμές ξεκινώντας από το 1 αυξανόμενη κατά ένα κάθε φορά - προσθέτουμε μια νέα γραμμή στο τέλος του παραγόμενου λεκτικού
Αποτελέσματα – Output
now - 1 days
now - 2 days
now - 3 days
now - 4 days
now - 5 days
...
now - 332038 days
now - 332039 days
now - 332040 days
now - 332041 days
now - 332042 days
now - 332043 days
now - 332044 days
date -f- +%Y%\_m%\_d$'\n'%Y%m%d
Σε αυτό το τμήμα της εντολής χρησιμοποιούμε την εντολή date
για να μετατρέψουμε σε ημερομηνία την κάθε γραμμή του αποτελέσματος της
προηγούμενης εντολής (της εντολής πριν τη σωλήνωση) και να τυπώσουμε
δύο γραμμές για κάθε ημερομηνία, μια που θα γεμίσει τις μονοψήφιες
ημέρες και τους μονοψήφιους μήνες με ένα μηδενικό και μια που θα γεμίσει
τις μονοψήφιες ημέρες και τους μονοψήφιους μήνες με ένα κενό (space).
Εξοικειωθείτε με την εντολή στη γραμμή εντολών:
# Δείξε την τρέχουσα ημερομηνία = now
$ date
Thu Feb 6 07:30:10 EET 2020
# Δείξε τη χθεσινή ημερομηνία
$ date --date="now - 1 days"
Wed Feb 5 07:31:22 EET 2020
# Δείξε τη χθεσινή ημερομηνία ως ηη/μμ/χχχχ (γέμισμα με μηδενικά)
$ date --date="now - 1 days" +%d/%m/%Y
05/02/2020
# Δείξε τη χθεσινή ημερομηνία ως ηη/μμ/χχχχ (γέμισμα με κενά)
$ date --date="now - 1 days" +%_d/%_m/%Y
5/ 2/2020
# Δείξε τη χθεσινή ημερομηνία ως ηη/μμ/χχχχ (σωρίς γέμισμα)
$ date --date="now - 1 days" +%-d/%-m/%Y
5/2/2020
Επεξήγηση εντολής
- χρησιμοποιούμε την επιλογή
-f
για να ορίσουμε ότι η είσοδος της εντολήςdate
θα είναι οι γραμμές ενός αρχείου, ακολουθούμενη από μια παύλα (hyphen)-
για να ορίσουμε ότι το αρχείο αυτό είναι η τυπική είσοδος –stdin
- ορίζουμε τη μορφή των τυπωμένων τιμών (ό,τι ακολουθεί το σύμβολο
+
) ώστε να είναι η αριθμητική αναπαράσταση του έτους (%Y
), του μήνα (γεμισμένου με κενά:%_m
, γεμισμένου με μηδενικά:%m
) και της ημέρας του μήνα (γεμισμένης με κενά:%_d
, γεμισμένης με μηδενικά:%d
) - η μορφή με τα κενά είναι
%Y%\_m%\_d
και η μορφή με τα μηδενικά%Y%m%d
- συνδυάζουμε αυτές τις δύο μορφές για κάθε μία ημερομηνία ενώνοντάς τες με μια νέα γραμμή
$'\n'
οπότε, για κάθε ημερομηνία παράγουμε δύο γραμμές, μια με μηδενικά και μια με κενά για τους μονοψήφιους μήνες και τις μονοψήφιες ημέρες του μήνα
Σημείωση: αντί να γεμίσουμε με κενά (χρησιμοποιώντας την κάτω παύλα _
στη μορφή), θα μπορούσαμε να χρησιμοποιήσουμε την παύλα -
ώστε να μη γεμίζουν με τίποτα οι μονοψήφιες τιμές. Με αυτόν τον τρόπο δε θα χρειαζόταν να αφαιρέσουμε τα κενά όπως περιγράφεται στο επόμενο τμήμα.
Αποτέλεσμα – Output
2020 2 5
20200205
2020 2 4
20200204
2020 2 3
20200203
2020 2 2
20200202
2020 2 1
20200201
2020 131
20200131
2020 130
20200130
tr -d ‘ ‘ > alldates.txt; rev alldates.txt >revdates.txt; paste alldates.txt revdates.txt
Σε αυτό το τμήμα αφαιρούμε τα κενά που παρήχθησαν προηγουμένως και δημιουργούμε δύο αρχεία, ένα με όλες τις ημερομηνίες ως έχουν και ένα με όλες τις ημερομηνίες αντεστραμμένες.
Στη συνέχεια συγχωνεύουμε αυτά τα δύο αρχεία, ενώνοντας κάθε γραμμή του πρώτου αρχείου με την αντίστοιχη γραμμή του δεύτερου παρεμβάλλοντας ένα στηλοθέτη – TAB.
Εξοικειωθείτε με τις εντολές στη γραμμή εντολών σας:
# Αφαίρεσε τα κενά
$ echo "Good night " | tr -d " "
Goodnight
# Αφαίρεσε τις παύλες
$ echo "Good-night" | tr -d "-"
Goodnight
# Τύπωσε a-x-z όπου χ ένας αριθμός μεταξύ του 1 και του 9
$ printf "a-%d-z\n" {1..9}
a-1-z
a-2-z
a-3-z
a-4-z
a-5-z
a-6-z
a-7-z
a-8-z
a-9-z
# και αντέστρεψε τη σειρά των χαρακτήρων της κάθε γραμμής
$ printf "a-%d-z\n" {1..9} | rev
z-1-a
z-2-a
z-3-a
z-4-a
z-5-a
z-6-a
z-7-a
z-8-a
z-9-a
Επεξήγηση εντολής
tr -d ''
: αφαιρούμε τα κενά από την είσοδο – input (που είναι η έξοδος – output της προηγούμενης εντολής) και αποθηκεύουμε την έξοδο σε ένα αρχείο με όνομαalldates.txt
rev
: αντιστρέφουμε του χαρακτήρες κάθε γραμμής του αρχείουalldates.txt
και αποθηκεύουμε την έξοδο σε ένα νέο αρχείο με όνομαrevdates.txt
paste
: ενώνουμε τα δύο αρχεία; κάθε γραμμή του αρχείουalldates.txt
ενώνεται με την αντίστοιχη γραμμή του αρχείουrevdates.txt
παρεμβάλλοντας ένα στηλοθέτη – tab (τα δύο αρχεία έχουν τον ίδιο αριθμό γραμμών).
Αποτελέσματα – Output
202025 520202
20200205 50200202
202024 420202
20200204 40200202
202023 320202
20200203 30200202
202022 220202
20200202 20200202
202021 120202
20200201 10200202
awk ‘$1==$2{print $1}’
Σε αυτό το τελευταίο τμήμα της εντολής, χρησιμοποιούμε την awk
για να τυπώσουμε τα παλίνδρομα.
Εξοικειωθείτε με τη σχετική λειτουργικότητα της awk στη γραμμή εντολών σας:
# Τύπωσε ολόκληρη την είσοδο (η προεπιλεγμένη συμπεριφορά της awk) αν τα πεδία είναι ίδια
$ echo "a a" | awk '$1==$2'
a a
# Τύπωσε ολόκληρη την είσοδο (η προεπιλεγμένη συμπεριφορά της awk) αν τα πεδία είναι ίδια
$ echo "a a" | awk '$1!=$2'
# δεν τυπώνεται τίποτα εδώ
# Τύπωσε ολόκληρη την είσοδο (η προεπιλεγμένη συμπεριφορά της awk) αν τα πεδία είναι ίδια
$ echo "a b" | awk '$1!=$2'
a b
# Τύπωσε το πρώτο πεδίο αν τα πεδία δεν είναι ίδια
$ echo "a b" | awk '$1!=$2 {print $1}'
a
# Τύπωσε το δεύτερο πεδίο αν τα πεδία δεν είναι ίδια
$ echo "a b" | awk '$1!=$2 {print $2}'
b
Επεξήγηση εντολής
- τα αποτελέσματα του προηγούμενου τμήματος της εντολής που είναι η
είσοδος αυτού του τμήματος αποτελούνται από γραμμές και κάθε γραμμή έχει
δύο τιμές χωρισμένες με στηλοθέτη – TAB. Αυτού του τύπου την είσοδο η awk την διαβάζει ως δύο πεδία για κάθε γραμμή. Μπορούμε να προσπελάσουμε αυτά τα πεδία χρησιμοποιώντας της μεταβλητές
$1
και$2
αντίστοιχα. - όπως περιγράφεται σε προηγούμενο άρθρο μου, οι δηλώσεις (statements) στην
awk
αποτελούνται από ένα μοτίβο (pattern) ή έκφραση (expression) και μια συσχετισμένη ενέργεια (associated action).<pattern/expression> { <action> }
Αν το μοτίβο/έκφραση επιτύχει τότε εκτελείται η συσχετισμένη ενέργεια. Στην περίπτωσή μας, η έκφραση μεταφράζεται ως “αν τα δύο πεδία είναι ίδια” ($1==$2
) και η ενέργεια ως “τύπωσε το πρώτο πεδίο” (printf $1
). - αν τα πεδία είναι ίδια τότε έχουμε μια παλίνδρομη ημερομηνία αφού κάθε ζεύγος πεδίων είναι η ημερομηνία και η αντίστροφη τιμή της.
Αποτελέσματα – Output
20200202
2019102
2018102
2017102
2016102
2015102
2014102
2013102
2012102
...
11111111
11111111
1111111
1111111
111111