Come elaborare un file riga per riga in uno script Bash di Linux

0
41

Fatmawati Achmad Zaenuri / Shutterstock

È abbastanza facile leggere il contenuto di un file di testo Linux riga per riga in uno script di shell, a patto che tu abbia a che fare con alcuni piccoli trucchi. Ecco come farlo in modo sicuro.

File, testo e modi di dire

Ogni linguaggio di programmazione ha una serie di idiomi. Questi sono i modi standard e senza fronzoli per eseguire una serie di attività comuni. Sono il modo elementare o predefinito per utilizzare una delle funzionalità del linguaggio con cui sta lavorando il programmatore. Diventano parte del kit di strumenti mentali di un programmatore.

Azioni come leggere dati da file, lavorare con i cicli e scambiare i valori di due variabili sono buoni esempi. Il programmatore conoscerà almeno un modo per raggiungere i propri fini in modo generico o vanigliato. Forse sarà sufficiente per il requisito in questione. O forse abbelliranno il codice per renderlo più efficiente o applicabile alla soluzione specifica che stanno sviluppando. Ma avere l'idioma degli elementi costitutivi a portata di mano è un ottimo punto di partenza.

Conoscere e comprendere gli idiomi in una lingua rende anche più facile apprendere un nuovo linguaggio di programmazione. Sapere come sono costruite le cose in una lingua e cercare l'equivalente – o la cosa più vicina – in un'altra lingua è un buon modo per apprezzare le somiglianze e le differenze tra i linguaggi di programmazione che già conosci e quello che stai imparando.

Leggere le righe da un file: One-Liner

In Bash, puoi usare un ciclo while sulla riga di comando per leggere ogni riga di testo da un file e fare qualcosa con esso. Il nostro file di testo si chiama "data.txt". Contiene un elenco dei mesi dell'anno.

gennaio
febbraio
marzo
.
.
ottobre
novembre
dicembre

Il nostro semplice one-liner è:

durante la lettura della riga; fare echo $ line; fatto <data.txt

Il ciclo while legge una riga dal file e il flusso di esecuzione del programmino passa al corpo del ciclo. Il comando echo scrive la riga di testo nella finestra del terminale. Il tentativo di lettura fallisce quando non ci sono più righe da leggere e il ciclo è fatto.

Un bel trucco è la capacità di reindirizzare un file in un ciclo. In altri linguaggi di programmazione, dovresti aprire il file, leggere da esso e chiuderlo di nuovo quando hai finito. Con Bash, puoi semplicemente usare il reindirizzamento dei file e lasciare che la shell gestisca tutte quelle cose di basso livello per te.

Ovviamente, questa battuta non è particolarmente utile. Linux fornisce già il comando cat, che fa esattamente questo per noi. Abbiamo creato un modo prolisso per sostituire un comando di tre lettere. Ma dimostra visibilmente i principi della lettura da un file.

Funziona abbastanza bene, fino a un certo punto. Supponiamo di avere un altro file di testo che contiene i nomi dei mesi. In questo file, la sequenza di escape per un carattere di nuova riga è stata aggiunta a ciascuna riga. Lo chiameremo "data2.txt".

Gennaio n
Febbraio n
Marzo n
.
.
Ottobre n
Novembre n
Dicembre n

Usiamo il nostro one-liner sul nostro nuovo file.

durante la lettura della riga; fare echo $ line; fatto <data2.txt

Il carattere di escape della barra rovesciata "" è stato eliminato. Il risultato è che a ciascuna riga è stata aggiunta una "n". Bash sta interpretando il backslash come l'inizio di una sequenza di escape. Spesso non vogliamo che Bash interpreti ciò che sta leggendo. Può essere più comodo leggere una riga nella sua interezza – sequenze di escape con barra rovesciata e tutto il resto – e scegliere cosa analizzare o sostituire da soli, all'interno del proprio codice.

Se vogliamo eseguire elaborazioni o analisi significative sulle righe di testo, avremo bisogno di utilizzare uno script.

Leggere le righe da un file con uno script

Ecco il nostro copione. Si chiama "script1.sh".

#! / bin / bash

Contatore = 0

mentre IFS = '' leggi -r LinefromFile || [[ -n “${LinefromFile}” ]]; fare

((Counter ++))
echo "Accesso alla riga $ Counter: $ {LinefromFile}"

fatto <"$ 1"

Impostiamo una variabile chiamata Counter a zero, quindi definiamo il nostro ciclo while.

La prima istruzione sulla riga while è IFS = ''. IFS sta per separatore di campo interno. Contiene valori che Bash utilizza per identificare i confini delle parole. Per impostazione predefinita, il comando di lettura rimuove gli spazi bianchi iniziali e finali. Se vogliamo leggere le righe dal file esattamente come sono, dobbiamo impostare IFS come una stringa vuota.

Potremmo impostarlo una volta fuori dal ciclo, proprio come stiamo impostando il valore di Counter. Ma con script più complessi, specialmente quelli con molte funzioni definite dall'utente in essi, è possibile che IFS possa essere impostato su valori diversi in altre parti dello script. Garantire che IFS sia impostato su una stringa vuota ogni volta che il ciclo while itera garantisce di sapere quale sarà il suo comportamento.

Leggeremo una riga di testo in una variabile chiamata LinefromFile. Stiamo usando l'opzione -r (leggi il backslash come un carattere normale) per ignorare i backslash. Verranno trattati come qualsiasi altro personaggio e non riceveranno alcun trattamento speciale.

Ci sono due condizioni che soddisferanno il ciclo while e consentiranno al testo di essere elaborato dal corpo del ciclo:

  • read -r LinefromFile: Quando una riga di testo viene letta correttamente dal file, il comando read invia un segnale di successo al while e il ciclo while passa il flusso di esecuzione al corpo del ciclo. Nota che il comando di lettura deve vedere un carattere di nuova riga alla fine della riga di testo per considerarlo una lettura riuscita. Se il file non è un file di testo conforme a POSIX, l'ultima riga potrebbe non includere un carattere di nuova riga. Se il comando di lettura vede il marcatore di fine file (EOF) prima che la riga venga terminata da una nuova riga, non la tratterà come una lettura riuscita. In tal caso, l'ultima riga di testo non verrà passata al corpo del ciclo e non verrà elaborata.
  • [ -n “${LinefromFile}” ] : Dobbiamo fare del lavoro extra per gestire file non compatibili con POSIX. Questo confronto verifica il testo letto dal file. Se non è terminato con un carattere di nuova riga, questo confronto restituirà comunque il successo al ciclo while. Ciò garantisce che tutti i frammenti di linea finale vengano elaborati dal corpo del ciclo.

Queste due clausole sono separate dall'operatore logico OR "|| "In modo che se una delle clausole restituisce successo, il testo recuperato viene elaborato dal corpo del ciclo, indipendentemente dal fatto che sia presente o meno un carattere di nuova riga.

Nel corpo del nostro ciclo, stiamo incrementando la variabile Counter di uno e usando echo per inviare un output alla finestra del terminale. Vengono visualizzati il ​​numero di riga e il testo di ciascuna riga.

Possiamo ancora usare il nostro trucco di reindirizzamento per reindirizzare un file in un ciclo. In questo caso, stiamo reindirizzando $ 1, una variabile che contiene il nome del primo parametro della riga di comando passato allo script. Usando questo trucco, possiamo facilmente passare il nome del file di dati su cui vogliamo che lo script funzioni.

Copia e incolla lo script in un editor e salvalo con il nome file "script1.sh". Usa il comando chmod per renderlo eseguibile.

chmod + x script1.sh

Vediamo cosa fa il nostro script del file di testo data2.txt e dei backslash contenuti al suo interno.

./script1.sh data2.txt

Ogni carattere nella riga viene visualizzato alla lettera. Le barre rovesciate non vengono interpretate come caratteri di escape. Sono stampati come caratteri normali.

Passaggio della linea a una funzione

Stiamo ancora facendo eco al testo sullo schermo. In uno scenario di programmazione del mondo reale, probabilmente stiamo per fare qualcosa di più interessante con la riga di testo. Nella maggior parte dei casi, è una buona pratica di programmazione gestire l'ulteriore elaborazione della linea in un'altra funzione.

Ecco come potremmo farlo. Questo è "script2.sh".

#! / bin / bash

Contatore = 0

funzione process_line () {

echo "Riga di elaborazione $ Contatore: $ 1"

}

mentre IFS = '' leggi -r LinefromFile || [[ -n “${LinefromFile}” ]]; fare

((Counter ++))
process_line "$ LinefromFile"

fatto <"$ 1"

Definiamo la nostra variabile Counter come prima, quindi definiamo una funzione chiamata process_line (). La definizione di una funzione deve apparire prima che la funzione venga chiamata per la prima volta nello script.

La nostra funzione passerà alla riga di testo appena letta in ogni iterazione del ciclo while. Possiamo accedere a quel valore all'interno della funzione utilizzando la variabile $ 1. Se ci fossero due variabili passate alla funzione, potremmo accedere a quei valori usando $ 1 e $ 2, e così via per più variabili.

Il ciclo while è principalmente lo stesso. C'è solo una modifica all'interno del corpo del ciclo. La riga echo è stata sostituita da una chiamata alla funzione process_line (). Nota che non è necessario utilizzare le parentesi "()" nel nome della funzione quando la chiami.

Il nome della variabile che contiene la riga di testo, LinefromFile, viene racchiuso tra virgolette quando viene passato alla funzione. Questo si rivolge a linee che hanno spazi in esse. Senza le virgolette, la prima parola viene considerata come $ 1 dalla funzione, la seconda parola viene considerata $ 2 e così via. L'uso delle virgolette garantisce che l'intera riga di testo venga gestita, complessivamente, come $ 1. Nota che questo non è lo stesso $ 1 che contiene lo stesso file di dati passato allo script.

Poiché Counter è stato dichiarato nel corpo principale dello script e non all'interno di una funzione, è possibile fare riferimento all'interno della funzione process_line ().

Copia o digita lo script sopra in un editor e salvalo con il nome file "script2.sh". Rendilo eseguibile con chmod:

chmod + x script2.sh

Ora possiamo eseguirlo e passare un nuovo file di dati, "data3.txt". Questo ha un elenco dei mesi e una riga con molte parole sopra.

gennaio
febbraio
marzo
.
.
ottobre
Novembre nAltro testo "alla fine della riga"
dicembre

Il nostro comando è:

./script2.sh data3.txt

Le righe vengono lette dal file e passate una alla volta alla funzione process_line (). Tutte le righe vengono visualizzate correttamente, inclusa quella dispari con il backspace, le virgolette e più parole.

I mattoni sono utili

C'è una linea di pensiero che dice che un idioma deve contenere qualcosa di unico per quella lingua. Non è una convinzione a cui sottoscrivo. L'importante è che faccia un buon uso del linguaggio, sia facile da ricordare e offra un modo affidabile e robusto per implementare alcune funzionalità nel codice.