Script Bash – Parte 4 – Input e Output

Mikhail Raevskiy
Mikhail Raevskiy

Follow

28 luglio, 2020 – 11 min read

L’ultima volta, nella parte 3 di questa serie di scripting bash, abbiamo parlato dei parametri della riga di comando e degli switch. Il nostro argomento di oggi è l’input, l’output e tutto ciò che è collegato a questo.

Foto di Ben Kolde su Unsplash

Hai già familiarità con due metodi per lavorare con l’output degli script a riga di comando.script da linea di comando:

  • Visualizza i dati in uscita sullo schermo.
  • Ridirigere l’output su un file.

A volte qualcosa deve essere mostrato sullo schermo, e qualcosa deve essere scritto su un file, quindi è necessario capire come vengono gestiti input e output in Linux, il che significa che è necessario imparare come inviare i risultati degli script dove è necessario. Iniziamo parlando dei descrittori di file standard.

Tutto in Linux è costituito da file, inclusi input e output. Il sistema operativo identifica i file usando descrittori.

Ogni processo può avere fino a nove descrittori di file aperti. La shell bash riserva i primi tre descrittori con gli ID 0, 1 e 2. Ecco cosa significano.

  • 0STDIN– standard input stream.
  • 1STDOUT– standard output stream.
  • 2STDERR– flusso di errore standard. Possono essere paragonati alle fondamenta su cui è costruita l’interazione degli script con il mondo esterno. Consideriamo i dettagli su di loro.

    STDIN

    STDIN– questo è lo standard input della shell. Per un terminale, l’input standard è la tastiera. Quando gli script utilizzano il carattere di reindirizzamento dell’input – <, Linux sostituisce il descrittore del file di input standard con quello specificato nel comando. Il sistema legge il file ed elabora i dati come se fossero inseriti dalla tastiera.

    Molti comandi bash prendono l’input da STDIN a meno che non sia specificato un file sulla linea di comando da cui recuperare i dati. Per esempio, questo è vero per una squadra cat.

    Quando si inserisce un comando cat sulla linea di comando senza specificare i parametri, accetta l’input da STDIN. Dopo aver inserito la linea successiva, essa cat la stampa semplicemente sullo schermo.

    STDOUT

    STDOUT– lo standard output della shell. Per impostazione predefinita, questo è lo schermo. La maggior parte dei comandi bash emette dati STDOUT alla console, che li fa apparire nella console. I dati possono essere reindirizzati ad un file allegandoli al suo contenuto utilizzando il comando >>.

    Abbiamo quindi un certo file di dati, al quale possiamo aggiungere altri dati usando questo comando:

    pwd >> myfile

    Quello che esce pwdverrà aggiunto al file myfile, mentre i dati già presenti non andranno da nessuna parte.

    raevskym@DESKTOP-JNF3L6H:~/bash_course$ pwd >> myfile
    raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./myfile
    this is old file content
    /home/raevskym/Desktop

    Fin qui tutto bene, ma cosa succede se si prova a fare qualcosa come quella mostrata qui sotto, accedendo ad un file inesistentexfile, pensando tutto questo in modo chemyfileil messaggio di errore entri nel file.

    ls –l xfile > myfile

    Dopo aver eseguito questo comando, vedremo dei messaggi di errore sullo schermo.

    raevskym@DESKTOP-JNF3L6H:~/bash_course$ ls -l xfile > myfile
    ls: cannot access '-l': No sudh file or directory
    ls: cannot access 'xfile': No such file or directory

    Un tentativo di accedere ad un file inesistente genera un errore, ma la shell non ha reindirizzato i messaggi di errore al file, visualizzandoli. Ma noi volevamo che i messaggi di errore andassero al file. Cosa fare? La risposta è semplice – usare il terzo descrittore standard.

    STDERR

    STDERR è il flusso di errore standard della shell. Per default, questo handle punta alla stessa cosa che punta a STDOUT, che è il motivo per cui quando si verifica un errore, vediamo un messaggio sullo schermo.

    Così, supponiamo di voler reindirizzare i messaggi di errore, ad esempio, ad un file di log, o da qualche altra parte, invece di stamparli sullo schermo.

    Ridurre il flusso degli errori

    Come già sapete, un descrittore di file perSTDERR è 2. Possiamo reindirizzare gli errori mettendo questo descrittore prima del comando di reindirizzamento:

    ls -l xfile 2>myfile
    cat ./myfile

    Il messaggio di errore andrà ora al file myfile.

    raevskym@DESKTOP-JNF3L6H:~/bash_course$ ls -l xfile 2> myfile
    raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./myfile
    ls: cannot access 'xfile': No such flie or directory

    Redirezione dei flussi di errore e di output

    Quando si scrivono script a riga di comando, può verificarsi una situazione in cui è necessario organizzare sia la redirezione dei messaggi di errore che quella dell’output standard. Per ottenere ciò, è necessario utilizzare i comandi di reindirizzamento per i descrittori corrispondenti, indicando i file dove gli errori e l’output standard dovrebbero andare:

    ls –l myfile xfile anotherfile 2> errorcontent 1> correctcontent

    Vediamo più da vicino:

    raevskym@DESKTOP-JNF3L6H:~/bash_course$ ls -l myfile xfile anotherfile 2> errorcontent 1> correctcontent
    raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./correctcontent
    myfile
    raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./errorcontent
    ls: cannot access '-l': No sudh file or directory
    ls: cannot access 'xfile': No such file or directory
    ls: cannot access 'anotherfile': No such file or directory

    La shell reindirizzerà ciò che il comandolsnormalmente invia aSTDOUTun filecorrectcontentdopo la sua costruzione1>. I messaggi di errore che sarebbero finitiSTDERRnel file sonoerrorcontentdovuti al comando di reindirizzamento2>.

    Se necessario, eSTDERR, eSTDOUT possono essere reindirizzati allo stesso file utilizzando il comando&>:

    raevskym@DESKTOP-JNF3L6H:~/bash_course$ ls -l myfile xfile anotherfile &> content
    raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./content
    ls: cannot access '-l': No sudh file or directory
    ls: cannot access 'xfile': No such file or directory
    ls: cannot access 'anotherfile': No such file or directory
    myfile

    Dopo l’esecuzione del comando, ciò che è destinato aSTDERReSTDOUTè nel filecontent.

    Ridirigere l’output negli script

    Ci sono due metodi per reindirizzare l’output negli script a riga di comando:

    • Ridirigere temporaneamente, o reindirizzare l’output di una linea.
    • Ridirigere in modo permanente, o reindirizzare tutto o parte dell’output nello script.

    Ridirigere temporaneamente l’output

    In uno script, è possibile reindirizzare l’output di una singola linea a STDERR. Per fare questo, è sufficiente utilizzare il comando di reindirizzamento, specificando un descrittore STDERR, e davanti al numero del descrittore, è necessario mettere una virgola (&) simbolo:

    #!/bin/bash
    echo "This is an error" >&2
    echo "This is normal output"

    Se si esegue lo script, entrambe le linee andranno sullo schermo, poiché, come già sapete, per default, gli errori vengono visualizzati nello stesso posto dei dati normali.

    raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
    This is an error
    This is normal output

    Eseguiamo lo script in modo che l’outputSTDERR vada in un file.

    ./myscript 2> myfile

    Come potete vedere, ora il solito output viene fatto sulla console, e i messaggi di errore vengono inviati al file.

    raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript 2> content
    This is normal output
    raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./content
    This is an error

    Reindirizzamento permanente dell’output

    Se uno script ha bisogno di reindirizzare molti dati visualizzati, è echoinconveniente aggiungere il comando appropriato ad ogni chiamata. Invece, è possibile impostare l’output per essere reindirizzato ad un descrittore specifico per la durata dello script utilizzando il comando exec:

    #!/bin/bash
    exec 1>outfile
    echo "This is a test of redirecting all output"
    echo "from a shell script to another file."
    echo "without having to redirect every line"

    Eseguiamo lo script:

    raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
    raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat outfile
    This is a test of redirecting all output
    from a shell script to another file.
    without having to redirect every line

    Se si guarda il file specificato nel comando di reindirizzamento dell’output, risulta che tutto ciò che l’output del comandoecho è andato in quel file.

    Il comandoexec può essere usato non solo all’inizio dello script ma anche in altri posti:

    #!/bin/bash
    exec 2>myerror
    echo "This is the start of the script"
    echo "now redirecting all output to another location"
    exec 1>myfile
    echo "This should go to the myfile file"
    echo "and this should go to the myerror file" >&2

    Questo è quello che succede dopo aver eseguito lo script e aver visualizzato i file a cui abbiamo reindirizzato l’output.

    raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
    This is the start of the script
    now redirecting all output to another location
    raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./myerror
    and this should go to the myerror file
    raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./myfile
    This should go to the myfile file

    Prima, il comandoexec imposta il reindirizzamento dell’output daSTDERRa un filemyerror. Poi l’output di diversi comandi èechoinviato aSTDOUTe visualizzato sullo schermo. Dopo di che, il comandoexec imposta l’invio di tutto ciò che va aSTDOUTun filemyfile, e infine, usiamo il comando di reindirizzamentoSTDERR nel comandoecho, che porta a scrivere la linea corrispondente al file.myerror.

    Avendo imparato questo, potete reindirizzare l’output dove volete. Ora parliamo del reindirizzamento dell’input.

    Ridirigere l’input negli script

    Per reindirizzare l’input, potete usare la stessa tecnica che abbiamo usato per reindirizzare l’output. Per esempio, il comando exec permette di fare una fonte di dati per STDIN un file:

    exec 0< myfile

    Questo comando dice alla shell di reindirizzare il suo input da un file myfile, non da uno normale STDIN. Vediamo il reindirizzamento dell’input in azione:

    #!/bin/bash
    exec 0< testfile
    count=1
    while read line
    do
    echo "Line #$count: $line"
    count=$(( $count + 1 ))
    done

    Questo è ciò che apparirà sullo schermo dopo l’esecuzione dello script.

    raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
    Line #1: this is the first line
    Line #2: this is the second line
    Line #3: this is the third line

    In uno degli articoli precedenti, avete imparato come usare un comandoreadper leggere l’input dell’utente dalla tastiera. Se si reindirizza l’input rendendo l’origine dei dati un file, allora il comandoread, quando cerca di leggere i dati daSTDIN, li leggerà dal file, e non dalla tastiera.

    Alcuni amministratori Linux usano questo approccio per leggere e poi elaborare i file di log.

    Creare il proprio reindirizzamento dell’output

    Quando si reindirizzano input e output negli script, non si è limitati ai tre descrittori di file standard. Come detto, puoi avere fino a nove descrittori aperti. Gli altri sei, numerati da 3 a 8, possono essere usati per reindirizzare l’input o l’output. Ognuno di essi può essere assegnato a un file e usato nel codice dello script.

    È possibile assegnare un descrittore per l’output dei dati usando il comando exec:

    #!/bin/bash
    exec 3>myfile
    echo "This should display on the screen"
    echo "and this should be stored in the file" >&3
    echo "And this should be back on the screen"

    Dopo aver eseguito lo script, parte dell’output andrà sullo schermo, e parte andrà al file con il descrittore 3.

    raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
    This should display on the screen
    And this should be back on the screen
    raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./myfile
    and this should be stored in the file

    Creare descrittori di file per l’input di dati

    È possibile reindirizzare l’input in uno script allo stesso modo dell’output. Memorizza STDIN in un altro descrittore prima di reindirizzare l’input.

    Dopo aver finito di leggere il file, potete ripristinare STDIN e usarlo come al solito:

    #!/bin/bash
    exec 6<&0
    exec 0< myfile
    count=1
    while read line
    do
    echo "Line #$count: $line"
    count=$(( $count + 1 ))
    done
    exec 0<&6
    read -p "Are you done now? " answer
    case $answer in
    y) echo "Goodbye";;
    n) echo "Sorry, this is the end.";;
    esac

    Testiamo lo script:

    raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
    Line #1: First line
    Line #2: Second line
    Line #3: Third line
    Are you done now? y
    Goodbye
    raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
    Line #1: First line
    Line #2: Second line
    Line #3: Third line
    Are you done now? n
    Sorry, this is the end.

    In questo esempio, il descrittore di file 6 è stato utilizzato per memorizzare un riferimento aSTDIN. Poi è stato fatto il reindirizzamento dell’input, la fonte di dati per ilSTDINfile è diventata. Dopo di che, l’input per il comandoreadveniva dalSTDIN reindirizzato, cioè dal file.

    Dopo aver letto il file, torniamoSTDINal suo stato originale reindirizzandolo a un descrittore6. Ora, per controllare che tutto funzioni correttamente, lo script pone una domanda all’utente, attende l’input dalla tastiera ed elabora ciò che viene inserito.

    Chiude i descrittori di file

    La shell chiude automaticamente i descrittori di file dopo che lo script termina. Tuttavia, in alcuni casi, è necessario chiudere manualmente i descrittori prima che lo script finisca di lavorare. Per chiudere l’handle, deve essere reindirizzato a &-. Si presenta così:

    #!/bin/bash
    exec 3> myfile
    echo "This is a test line of data" >&3
    exec 3>&-
    echo "This won't work" >&3

    Dopo aver eseguito lo script, riceveremo un messaggio di errore.

    raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
    ./myscript: line 5: 3: Bad file descriptor

    Il punto è che abbiamo cercato di accedere ad un descrittore di file inesistente.

    Fate attenzione quando chiudete i descrittori di file negli script. Se inviate dati ad un file, poi chiudete il descrittore e poi lo riaprite, la shell sostituirà il file esistente con quello nuovo. Cioè, tutto ciò che è stato scritto su questo file in precedenza sarà perso.

    Ricuperare informazioni sugli handle aperti

    Per ottenere un elenco di tutti i descrittori aperti in Linux, è possibile utilizzare il comando lsof. Molte distribuzioni, come Fedora, hanno l’utilità lsof in /usr/sbin. Questo comando è molto utile in quanto visualizza informazioni su ogni handle aperto sul sistema. Questo include ciò che è aperto dai processi in esecuzione in background e ciò che è aperto dagli utenti che sono loggati.

    Questo comando ha molti tasti, vediamo i più importanti.

    • -p Permette di specificare un IDprocesso.
    • -d Permette di specificare il numero di un descrittore sul quale si vogliono ottenere informazioni.

    Per conoscere il PID del processo corrente, è possibile utilizzare una speciale variabile d’ambiente $$ nella quale la shell scrive quello corrente PID.

    La chiave è -a utilizzata per eseguire un’operazione booleana AND sui risultati restituiti utilizzando le altre due chiavi:

    raevskym@DESKTOP-JNF3L6H:~/bash_course$ lsof -a -p $$ -d 0,1,2
    COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
    myscript 24 raevskym 0u CHR 4,1 25332 /dev/tty1
    myscript 24 raevskym 1u CHR 4,1 25332 /dev/tty1
    myscript 24 raevskym 2u CHR 4,1 25332 /dev/tty1

    Il tipo di file associato aSTDINSTDOUTeSTDERR — CHR (modalità carattere). Poiché tutti puntano a un terminale, il nome del file corrisponde al nome del dispositivo assegnato al terminale. Tutti e tre i file standard sono disponibili per la lettura e la scrittura.

    Guardiamo come chiamare un comandolsofda uno script in cui, oltre allo standard, sono aperti altri descrittori:

    #!/bin/bash
    exec 3> myfile1
    exec 6> myfile2
    exec 7< myfile3
    lsof -a -p $$ -d 0,1,2,3,6,7

    Questo è ciò che accade se si esegue questo script.

    raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
    COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
    myscript 24 raevskym 0u CHR 4,1 25332 /dev/tty1
    myscript 24 raevskym 1u CHR 4,1 25332 /dev/tty1
    myscript 24 raevskym 2u CHR 4,1 25332 /dev/tty1
    myscript 24 raevskym 3w REG 0,14 0 13510 ~/bash_course/myfile1
    myscript 24 raevskym 6w REG 0,14 0 13229 ~/bash_course/myfile2

    Lo script ha aperto due descrittori per l’output (3e6) e uno per l’input (7). Anche i percorsi dei file usati per configurare i descrittori sono mostrati qui.

    Suppressione dell’output

    A volte è necessario assicurarsi che i comandi nello script, che, per esempio, possono essere eseguiti come processo in background, non mostrino nulla sullo schermo. Per fare questo, è possibile reindirizzare l’output a /dev/null. Questo è qualcosa come un “buco nero”.

    Per esempio, ecco come sopprimere i messaggi di errore:

    ls -al badfile anotherfile 2> /dev/null

    Lo stesso approccio si usa se, per esempio, avete bisogno di pulire un file senza cancellarlo:

    cat /dev/null > myfile

    Outcome

    Oggi hai imparato come funzionano input e output nello scripting a riga di comando. Ora sai come gestire i descrittori di file, crearli, visualizzarli e chiuderli, sai come reindirizzare i flussi di input, output ed errore. Questi sono tutti molto importanti nello sviluppo di script bash.

    La prossima volta, parliamo dei segnali di Linux, come gestirli negli script, come eseguire lavori programmati e compiti in background.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *