Impara awk programmando un gioco "indovina il numero".
I linguaggi di programmazione tendono a condividere molti tratti comuni. Un ottimo modo per imparare una nuova lingua è creare un programma familiare. In questo articolo creerò un gioco "indovina il numero" utilizzando awk per dimostrare concetti familiari.
Quando impari un nuovo linguaggio di programmazione, è bene concentrarti sulle cose che la maggior parte dei linguaggi di programmazione hanno in comune:
- Variabili: luoghi in cui vengono archiviate le informazioni
- Espressioni: modi per calcolare le cose
- Dichiarazioni: il mezzo con cui i cambiamenti di stato sono espressi in un programma
Questi concetti sono alla base della maggior parte dei linguaggi di programmazione.
Una volta compresi questi concetti, puoi iniziare a capire il resto. Ad esempio, la maggior parte dei linguaggi ha un "modo di fare le cose" supportato dalla sua progettazione, e questi modi possono essere molto diversi da un programma all'altro. Questi modi includono la modularità (raggruppamento di funzionalità correlate), dichiarativo vs. imperativo, orientamento agli oggetti, caratteristiche sintattiche di basso vs. alto livello e così via. Un esempio familiare a molti programmatori è la "cerimonia", ovvero la quantità di lavoro richiesta per impostare la scena prima di affrontare il problema. Si dice che il linguaggio di programmazione Java abbia importanti requisiti cerimoniali, derivanti dalla sua progettazione, che richiede che tutto il codice sia definito all'interno di una classe.
Ma torniamo alle basi. I linguaggi di programmazione di solito condividono somiglianze. Una volta che conosci un linguaggio di programmazione, inizia imparando le basi di un altro per apprezzare le differenze in quel nuovo linguaggio.
Un buon modo di procedere è creare una serie di programmi di test di base. Con questi in mano, l’apprendimento inizia con queste somiglianze.
Un programma di test che puoi utilizzare è un programma "indovina il numero". Il computer sceglie un numero compreso tra uno e cento e ti chiede di indovinarlo. Il programma va in loop finché non fai un'ipotesi corretta.
Il programma "indovina il numero" esercita diversi concetti nei linguaggi di programmazione:
- Variabili
- Ingresso
- Produzione
- Valutazione condizionale
- Loop
È un ottimo esperimento pratico per imparare un nuovo linguaggio di programmazione.
Nota: questo articolo è adattato dall'articolo di Moshe Zadka sull'utilizzo di questo approccio in Julia e dall'articolo di Jim Hall sull'utilizzo di questo approccio in Bash.
Indovina il numero in awk
Scriviamo un gioco "indovina il numero" come un programma Awk.
Awk è tipizzato dinamicamente, è un linguaggio di scripting orientato alla trasformazione dei dati e ha un supporto sorprendentemente buono per l'uso interattivo. Awk esiste dagli anni '70, originariamente come parte del sistema operativo Unix. Se non conosci Awk ma ami i fogli di calcolo, questo è un segno... impara Awk!
Puoi iniziare la tua esplorazione di Awk scrivendo una versione del gioco "indovina il numero".
Ecco la mia implementazione (con i numeri di riga in modo da poter rivedere alcune delle funzionalità specifiche):
1 BEGIN {
2 srand(42)
3 randomNumber = int(rand() * 100) + 1
4 print "random number is",randomNumber
5 printf "guess a number between 1 and 100\n"
6 }
7 {
8 guess = int($0)
9 if (guess < randomNumber) {
10 printf "too low, try again:"
11 } else if (guess > randomNumber) {
12 printf "too high, try again:"
13 } else {
14 printf "that's right\n"
15 exit
16 }
17 }
Possiamo immediatamente vedere somiglianze tra le strutture di controllo di Awk e quelle di C o Java, ma a differenza di Python. In istruzioni come if-then-else o mentre, then, else e mentre prendono una frase o un gruppo di frasi racchiuse tra { e }. Tuttavia, c'è una grande differenza in AWk che deve essere compresa fin dall'inizio:
In base alla progettazione, Awk è costruito attorno a una pipeline di dati.
Che cosa significa? La maggior parte dei programmi Awk sono frammenti di codice che ricevono una riga di input, fanno qualcosa con i dati e li scrivono nell'output. Riconoscendo la necessità di tale pipeline di trasformazione, Awk fornisce per impostazione predefinita tutto il necessario per la trasformazione. Esploriamolo attraverso il programma sopra ponendo una domanda di base: dov'è la struttura di "lettura dei dati dalla console"?
La risposta è: è integrato. In particolare, le righe 7 – 17 dicono ad Awk cosa fare con ogni riga letta. Dato questo contesto, è abbastanza facile vedere che le righe 1 – 6 vengono eseguite prima che venga letto qualsiasi cosa.
Più specificamente, la parola chiave BEGIN alla riga 1 è una sorta di "pattern", in questo caso indica ad Awk che, prima di leggere qualsiasi dato, dovrebbe eseguire ciò che segue BEGIN > nel {…}. Una parola chiave END simile, non utilizzata in questo programma, indica ad Awk cosa fare quando tutto è stato letto.
Tornando alle righe 7 – 17, vediamo che creano un blocco di codice { … } che è simile, ma non c'è alcuna parola chiave davanti. Poiché non c'è nulla prima di { affinché Awk corrisponda, applicherà questa riga a ogni riga di input ricevuta. Ogni riga di input verrà inserita come ipotesi da parte dell'utente.
Diamo un'occhiata al codice in esecuzione. Innanzitutto, il preambolo che avviene prima della lettura di qualsiasi input.
Nella riga 2 inizializziamo il generatore di numeri casuali con il numero 42 (se non forniamo un argomento, viene utilizzato l'orologio di sistema). 42? Ovviamente 42. La riga 3 calcola un numero casuale compreso tra 1 e 100 e la riga 4 stampa quel numero a scopo di debug. La riga 5 invita l'utente a indovinare un numero. Tieni presente che questa riga utilizza printf
, non print
. Come il C, il primo argomento di printf'
è un modello utilizzato per formattare l'output.
Ora che l'utente è consapevole che il programma si aspetta un input, può digitare un'ipotesi sulla console. Awk fornisce questa ipotesi al codice nelle righe 7 – 17, come menzionato in precedenza. La riga 18 converte il record di input in un numero intero; $0
indica l'intero record di input, mentre $1
indica il primo campo del record di input, $2
il secondo e così via. Sì, Awk divide una riga di input in campi costituenti, utilizzando il separatore predefinito, che per impostazione predefinita è uno spazio bianco. Le righe 9 – 15 confrontano l'ipotesi con il numero casuale, stampando le risposte appropriate. Se l'ipotesi è corretta, la riga 15 esce prematuramente dalla pipeline di elaborazione della riga di input.
Semplice!
Data la struttura insolita dei programmi Awk come frammenti di codice che reagiscono a specifiche configurazioni della linea di input e fanno cose con i dati, diamo un'occhiata a una struttura alternativa solo per vedere come funziona la parte di filtraggio:
1 BEGIN {
2 srand(42)
3 randomNumber = int(rand() * 100) + 1
4 print "random number is",randomNumber
5 printf "guess a number between 1 and 100\n"
6 }
7 int($0) < randomNumber {
8 printf "too low, try again: "
9 }
10 int($0) > randomNumber {
11 printf "too high, try again: "
12 }
13 int($0) == randomNumber {
14 printf "that's right\n"
15 exit
16 }
Le righe 1 – 6 non sono cambiate. Ma ora vediamo che le righe 7 – 9 sono un codice che viene eseguito quando il valore intero della riga è inferiore al numero casuale, le righe 10 – 12 sono un codice che viene eseguito quando il valore intero della riga è maggiore del numero casuale e le righe 13 – 16 sono il codice che viene eseguito quando i due corrispondono.
Questo dovrebbe sembrare "interessante ma strano": perché dovremmo calcolare ripetutamente int($0)
, ad esempio? E di sicuro, sarebbe un modo strano per risolvere il problema. Ma questi modelli possono essere dei modi davvero meravigliosi per separare l'elaborazione condizionale poiché possono utilizzare espressioni regolari o qualsiasi altra struttura supportata da Awk.
Per completezza, possiamo utilizzare questi modelli per separare i calcoli comuni da quelli che si applicano solo a circostanze specifiche. Ecco una terza versione da illustrare:
1 BEGIN {
2 srand(42)
3 randomNumber = int(rand() * 100) + 1
4 print "random number is",randomNumber
5 printf "guess a number between 1 and 100\n"
6 }
7 {
8 guess = int($0)
9 }
10 guess < randomNumber {
11 printf "too low, try again: "
12 }
13 guess > randomNumber {
14 printf "too high, try again: "
15 }
16 guess == randomNumber {
17 printf "that's right\n"
18 exit
19 }
Riconoscendo che, indipendentemente dal valore dell'input, deve essere convertito in un numero intero, abbiamo creato le righe 7 – 9 proprio per questo. Ora i tre gruppi di linee, 10 – 12, 13 – 15 e 16 – 19, si riferiscono alla variabile già definita ipotesi invece di convertire ogni volta la linea di input.
Torniamo all'elenco delle cose che volevamo imparare:
- variabili – sì, Awk le ha; possiamo dedurre che i dati di input arrivano come stringhe ma possono essere convertiti in un valore numerico quando richiesto
- input - Awk invia semplicemente input attraverso il suo approccio "pipeline di trasformazione dei dati" per leggere materiale
- output: abbiamo utilizzato le procedure
print
eprintf
di Awk per scrivere materiale sull'output - valutazione condizionale – abbiamo imparato a conoscere if-then-else di Awk e i filtri di input che rispondono a specifiche configurazioni della linea di input
- loop – eh, immaginatelo! Non abbiamo avuto bisogno di un loop qui, ancora una volta, grazie all'approccio "pipeline di trasformazione dei dati" adottato da Awk; il ciclo "succede e basta". Tieni presente che l'utente può uscire prematuramente dalla pipeline inviando un segnale di fine file ad Awk (un CTRL-D quando si utilizza una finestra di terminale Linux)
Vale la pena considerare l'importanza di non aver bisogno di un ciclo per gestire l'input. Uno dei motivi per cui Awk è rimasto valido per così tanto tempo è che i programmi Awk sono compatti, e uno dei motivi per cui sono compatti è che non è necessario alcun boilerplate per leggere dalla console o da un file.
Eseguiamo il programma:
$ awk -f guess.awk
random number is 25
guess a number between 1 and 100: 50
too high, try again: 30
too high, try again: 10
too low, try again: 25
that's right
$
Una cosa di cui non abbiamo parlato erano i commenti. Un commento Awk inizia con un #
e termina con la fine della riga.
Incartare
Awk è incredibilmente potente e questo gioco "indovina il numero" è un ottimo modo per iniziare. Non dovrebbe essere la fine del tuo viaggio, però. Puoi leggere la storia di Awk e Gawk (GNU Awk), una versione ampliata di Awk e probabilmente quella che hai sul tuo computer se utilizzi Linux, o leggere tutto sull'originale dai suoi sviluppatori iniziali.
Puoi anche scaricare il nostro cheatsheet per aiutarti a tenere traccia di tutto ciò che impari.