Questo post è apparso originariamente sul sito di Stephen ed è ripubblicato qui (leggermente riscritto) con permesso
Introduzione a cal
Lo sapevi che esiste un calendario nella riga di comando di macOS e Linux?
$ cal
July 2021
Su Mo Tu We Th Fr Sa
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Un rapido controllo di tldr mostra quanto può essere utile questo strumento.
$ tldr cal
cal
Prints calendar information.
- Display a calendar for the current month:
cal
- Display previous, current and next month:
cal -3
- Display a calendar for a specific month (1-12 or name):
cal -m month
- Display a calendar for the current year:
cal -y
- Display a calendar for a specific year (4 digits):
cal year
- Display a calendar for a specific month and year:
cal month year
- Display date of Easter (Western Christian churches) in a given year:
ncal -e year
Ma c'è una cosa che non ha. Gli anni bisestili!
Naturalmente cal sa degli anni bisestili, altrimenti non sarebbe un granché come calendario. Ma non ha un modo per elencarli.
Riconoscere gli anni bisestili, in modo improvvisato
Costruiamolo noi! Certo, potremmo usare un vero linguaggio di programmazione con una libreria di calendari, ma costruire le cose assemblando componenti da riga di comando è divertente e non è che metteremo questo calcolo in produzione.
Per prima cosa, mettiamoci d'accordo che febbraio è l'indicatore di un anno bisestile.
$ cal 02 2021
February 2021
Su Mo Tu We Th Fr Sa
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28
$ cal 02 2020
February 2020
Su Mo Tu We Th Fr Sa
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
Potremmo cercare «29» e fargli riconoscere gli anni bisestili? Forse!
$ cal 02 2020 | grep -q 29 && echo "LEAP" || echo "normal"
LEAP
$ cal 02 2021 | grep -q 29 && echo "LEAP" || echo "normal"
normal
Fin qui tutto bene? Ma scommetto che vedi il problema.
$ for year in {2020..2030}; do
printf "$year ";
cal 02 "$year" | grep -q 29 && echo "LEAP" || echo "year";
done
2020 LEAP
2021 year
2022 year
2023 year
2024 LEAP
2025 year
2026 year
2027 year
2028 LEAP
2029 LEAP
2030 year
Il 2029 NON è un anno bisestile. Corrisponde solo per via del 29 nel 2029.
$ cal 02 2029
February 2029
Su Mo Tu We Th Fr Sa
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28
Potremmo eliminare quel risultato problematico rimuovendo dall'analisi l'intera riga con «February».
$ cal 02 2029 | grep -v Feb
Su Mo Tu We Th Fr Sa
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28
Ora sostengo che se troviamo un «29» nel risultato, allora è DAVVERO un anno bisestile.
$ for year in {2020..2030}; do
printf "$year ";
cal 02 "$year" | grep -v Feb | grep -q 29 && echo "LEAP" || echo "year";
done
2020 LEAP
2021 year
2022 year
2023 year
2024 LEAP
2025 year
2026 year
2027 year
2028 LEAP
2029 year
Corrisponde agli anni bisestili reali?
La pagina Leap Year di Wolfram Research contiene questo elenco come gli anni bisestili della prima metà del XXI secolo.
2000
2004
2008
2012
2016
2020
2024
2028
2032
2036
2040
2044
2048
Vediamo come ce la caviamo
$ for year in {2000..2051}; do cal 02 "$year" | grep -v Feb | grep -q 29 && echo $year; done
2000
2004
2008
2012
2016
2020
2024
2028
2032
2036
2040
2044
2048
Fatto!
Ma questo grep per «29» è troppo facile. Rendiamolo più bizzarro!
Riconoscere gli anni bisestili, in modo ancora più improvvisato
La differenza tra un anno bisestile e un anno normale è quel giorno in più del 29. Potremmo controllare il numero di caratteri nell'output di cal per riconoscere gli anni bisestili?
Sì, possiamo. Ma c'è un intoppo da superare prima. Ti faccio vedere cosa intendo.
Sappiamo che il 2020 è un anno bisestile e il 2021 no. Controlliamo la differenza tra il numero di caratteri prodotti per febbraio di ciascuno di quegli anni. Lo faremo con l'utile strumento wc, in particolare wc -c per contare i byte.
$ printf "hello" | wc -c
5
5 caratteri in «hello» (usiamo printf invece di echo perché altrimenti avremmo anche un carattere di nuova riga). Ottimo!
$ cal 02 2020 | wc -c
184
$ cal 02 2021 | wc -c
184
Ecco l'ostacolo. Ogni mese ha un output diverso (il 2020 ha quel giorno 29) ma entrambi hanno lo stesso numero di caratteri perché cal aggiunge spazi per garantire una formattazione coerente. Gli spazi sono caratteri contabili tanto quanto i numeri.
Cosa possiamo fare?
Possiamo rimuovere tutti gli spazi! Così rimarrebbero solo i caratteri che vogliamo contare.
Un ottimo strumento per rimuovere caratteri è il comando tr. Il comando tr traduce dati di tipo stringa. Per esempio, può trasformare tutti i caratteri a in caratteri A.
$ echo "aardvark" | tr a A
AArdvArk
Può anche eliminare tutti i caratteri specificati, che è esattamente ciò che vogliamo.
Nel nostro caso vogliamo eliminare tutti i caratteri di spaziatura, non importa quali siano (spazi, tabulazioni, nuove righe, ecc.). Esiste un raggruppamento utile chiamato classe di caratteri che ci permette di fare esattamente questo. La classe di caratteri [:space:] prenderà di mira tutti quei diversi tipi di caratteri di spaziatura.
$ cal 02 2020 | tr -d '[:space:]'
February2020SuMoTuWeThFrSa1234567891011121314151617181920212223242526272829
$ cal 02 2021 | tr -d '[:space:]'
February2021SuMoTuWeThFrSa12345678910111213141516171819202122232425262728
Ottimo! Possiamo contare il numero di caratteri per trovare gli anni bisestili ora? Certo che possiamo!
$ cal 02 2020 | tr -d '[:space:]' | wc -c
75
$ cal 02 2021 | tr -d '[:space:]' | wc -c
73
Ora stampiamo alcuni anni bisestili!
$ for year in {2000..2051}; do
cal 02 "$year" |
tr -d '[:space:]' |
wc -c |
grep -q 75 && echo "$year";
done
2000
2004
2008
2012
2016
2020
2024
2028
2032
2036
2040
2044
2048
Trucchi su trucchi e funziona!
Quale pensi sia più veloce? Sì, non ne ho idea neanch'io. Il percorso che elimina «February» è forse più veloce? Ma sarei sorpreso se ci fosse una grande differenza.
Non lasciamoci sorprendere. Scopriamolo!
C'è un ottimo strumento chiamato hyperfine che permette di valutare le prestazioni di più chiamate da riga di comando.
Qual è più veloce? Ce lo dirà Hyperfine
Ecco le opzioni che passo a hyperfine insieme ai due comandi.
--style basic
Plain output styling
--export-markdown hyperfine.md
Export results as markdown
--warmup 5
Do five runs before benchmarking
--ignore-failure
Ignore non-zero exits
Ci interessa solo valutare le prestazioni del calcolo di un anno, non il tempo necessario a generare il ciclo di anni. Quindi valuterò solo il controllo dell'anno bisestile per un anno specifico con ciascun approccio.
$ hyperfine --style basic --export-markdown hyperfine.md --warmup 5 --ignore-failure "cal 02 2050 | grep -v Feb | grep -q 29" "cal 02 2050 | tr -d '[:space:]' | wc -c | grep -q 75"
Benchmark #1: cal 02 2050 | grep -v Feb | grep -q 29
Time (mean ± σ): 1.8 ms ± 0.3 ms [User: 0.7 ms, System: 1.9 ms]
Range (min … max): 1.0 ms … 3.6 ms 493 runs
Warning: Command took less than 5 ms to complete. Results might be inaccurate.
Warning: Ignoring non-zero exit code.
Benchmark #2: cal 02 2050 | tr -d '[:space:]' | wc -c | grep -q 75
Time (mean ± σ): 1.9 ms ± 0.4 ms [User: 0.9 ms, System: 2.7 ms]
Range (min … max): 0.7 ms … 5.9 ms 811 runs
Warning: Command took less than 5 ms to complete. Results might be inaccurate.
Warning: Ignoring non-zero exit code.
Summary
'cal 02 2050 | grep -v Feb | grep -q 29' ran
1.07 ± 0.28 times faster than 'cal 02 2050 | tr -d '[:space:]' | wc -c | grep -q 75'
| Comando | Media [ms] | Min [ms] | Max [ms] | Relativo |
|---|---|---|---|---|
cal 02 2050 | grep -v Feb | grep -q 29 |
1.8 ± 0.3 | 1.0 | 3.6 | 1.00 |
cal 02 2050 | tr -d '[:space:]' | wc -c | grep -q 75 |
1.9 ± 0.4 | 0.7 | 5.9 | 1.07 ± 0.28 |
Come previsto, praticamente nessuna differenza tra loro per quanto riguarda il tempo effettivo, MA il percorso che elimina «February» potrebbe essere leggermente più veloce.
Questo conclude il viaggio di assemblaggio di strumenti da riga di comando.
Riepilogo
- Puoi creare nuove funzionalità da riga di comando assemblando strumenti esistenti in una pipeline di dati
- Un po' di creatività può ottenere risultati sorprendentemente complessi da pezzi semplici
- Hyperfine è uno strumento utile per valutare le prestazioni dei comandi da riga di comando
Prossimi passi
- Esplora l'uso del comando
cal. - Prova a costruire una pipeline che conta tutti i caratteri diversi dagli spazi per un intero anno:
cal 2020 - Prova a eseguire quella pipeline di conteggio per un intero anno per ciascuno degli anni dal 1750 al 1759: nota qualcosa di strano?