Shell: Parte 2

Redirezione

I comandi standard:

  • prendono l’input da stdin, standard input; di default stdin e’ l’input utente dato via terminale.
  • scrivono l’output in stdout, standard output; di default stdout e’ il terminale.
  • scrivono eventuali messaggi di errore in stderr, standard error; di default stderr e’ il terminale.

Possiamo modificare questi nomi simbolici usando gli operatori di redirezione:

  • cmd < file costringe cmd ad usare i contenuti di file come stdin.
  • cmd > file costringe cmd a scrivere l’output nel file file. Se file non esiste viene creato, se esiste viene sovrascirtto.
  • cmd >> file costringe cmd a scrivere l’output nel file file. Se file non esiste viene crato, se esiste l’output viene aggiunto alla fine del file.

Warning

Non tutti i comandi permettono di redirigere lo stdin. Tra questi ci sono ls e cat.

Esempio. Lo stdout di ls puo’ comunque essere rediretto verso un file arbitrario:

ls > lista_dei_file.txt

Esempio. Prendiamo il comando echo: di default prende l’input (stdin) dal terminale (qui indicato come “keyboard”) e scrive (stdout) su terminale. Se lanciamo:

echo foo

quello che succede e’ che echo replica (fa l’eco) di tutto quello che scriviamo su terminale e lo stampa sul terminale. In immagini:

Redirezione.

Redirigendo lo stdout di echo a file invece lo costringiamo a redirigere il proprio output nel file:

echo foo > temp.txt

In immagini:

Redirezione.

Esempio. Redirigere l’input di un comando funziona allo stesso modo. Prendiamo il comando wc. Di default prende l’input da schermo:

$ wc -w
foo
bar
baz
Control-d

qui Control-d dice a wc che l’input finisce li’. L’output sara’ 3. Pero’ possiamo costringere wc a leggere l’input da file:

wc -w < temp.txt

In immagini:

Redirezione.

Pipelines

Per eseguire piu’ comandi in sequenza, possiamo usare ;:

cmd1 ; cmd2 ; ... ; cmdN

In questo modo la shell esegue cmd1, poi cmd2, ..., ed infine cmdN.

Si possono combinare piu’ comandi in sequenza con l’operatore di concatetenamento | (pipe). La sintassi e’:

cmd1 | cmd2 | ... | cmdN

L’operatore | collega lo stdout di un comando allo stdin del comando che lo segue.

Una pipeline.

Note

Il simbolo | rappresenta un tubo!

Esempio. Anticipando un po’ i tempi, se lancio:

ls | tac

ottengo che le righe stampata da ls vengano prese da tac, che le stampa sottosopra.

Esempio. Per stampare su stdout la lista dei file nella mia home con ls e poi contare le righe con wc, scrivo:

ls -l ~ | wc -l

Posso passare argomenti ai vari comandi esattamente come se la pipeline non ci fosse.

Esempio. Per navigare comodamente l’output di una pipeline complessa:

cmd1 | ... | cmdN | less

Il comando less permette di vedere l’output pagina per pagina. Potete spostarvi nell’output come fareste nel manuale: vi spostate con le frecce, potete cercare con /, etc.

Modificare Files

I comandi di base sono:

comando funzione opzioni principali
echo stampa l’eco -n
cat stampa il contenuto di un file -n
tac stampa il contenuto sottosopra  
head stampa l’inzio di un file -n
tail stampa la fine di un file -n

Esempio. Come gia’ visto sopra, il comando echo puo’ essere usato per creare file di testo, ad esempio:

echo 'I am the last line *ever*!' > output.txt
echo 'You are not, you /fool/!' >> output.txt

Qui le virgolette singole ' servono per evitare che la shell reagisca ad eventuali caratteri speciali, nel nostro caso gli asterischi * e gli slash /.

Esercizi

  1. Partendo da ~, controllare il risultato dopo ogni passaggio:
    1. Creare una directory informatica. E’ vuota?
    2. Creare una directory esercizi.
    3. Spostarsi in informatica.
    4. Sempre da dentro informatica, rinominare esercizi in esercizi-shell.
    5. Creare una copia di esercizi-shell in informatica.
    6. Rimuovere la copia originale di esercizi-shell.
    7. Creare in informatica/esercizi-shell un file README, il cui testo deve leggere: “esercitazioni di informatica”.
    8. Aggiungere a README una seconda riga: “Parte 1, introduzione alla shell”.
    9. Tornare alla propria home.
  2. Partendo da ~, controllare il risultato dopo ogni passaggio:
    1. Creare un file di testo a in una nuova directory original. Il file deve contenere la stringa “*”.

    2. Prima di ciascuno dei punti successivi, creare una copia di original chiamata temp.

      1. Creare in temp due copie di a, chiamate b e c.
      2. Che differenza c’e’ tra echo a, ls a e cat a?
      3. Che differenza c’e’ tra mv a b e cp a b; rm a?
      4. Che differenza c’e’ tra cp a b; cp a c e mv a b; mv b c?
      5. Che differenza c’e’ tra mv a z e mkdir z; mv a z?
      6. Che differenza c’e’ tra echo a z e mkdir z; echo a z?
      7. Creare dieci file a1, ..., a10, poi cancellarli con una sola invocazione di rm.
  3. Cosa fa il comando (alcuni dei seguenti comandi sono errati):
    1. ls -e | head -n + 25?
    2. cat | head | tail?
    3. cat .?
    4. echo cat?
    5. cat a > b?
    6. cat << a?
    7. head > a | tail > b?
    8. ls > a; rm < a?
    9. echo KrustyIlKlown > a?
    10. tac < FILE1 | tac > FILE2
  4. Che differenza c’e’ tra (alcuni dei seguenti comandi sono errati):
    1. head < a > b
    2. cat a | head > b
    3. tac a | tac | head > b
    4. tac < a | head | tac > b
  5. Che differenza c’e’ tra:
    1. tac a | head -n 25 > b
    2. cat a | tail -n 25 > b
  6. Che differenza c’e’ tra:
    1. head a | tail
    2. head a > temp; tail a
  7. Che differenza c’e’ tra:
    1. cat file | head
    2. cat | head file
  8. Come faccio a:
    1. stampare l’ennesima riga di un file?
    2. stampare le righe dalla n alla n+m di un file?
  9. Eseguire in ordine:
    1. Creare un file data/b che contenga le stesse righe di data/a, ordinate dalla 26 alla 50, dalla 1 alla 25, e dalla 51 alla 100, in quest’ordine.
    2. Creare un file data/c che contenga le stesse righe di data/a, ordinate dalla 26 alla 50, dalla 25 alla 1, e dalla 51 alla 100, in quest’ordine.

Soluzioni

  1. Soluzioni:
    1. mkdir informatica. Verifico che sia vuota con ls informatica.
    2. mkdir esercizi.
    3. cd informatica. Verifico con pwd.
    4. mv ../esercizi ../esercizi-shell. Verifico con ls ...
    5. cp -r ../esercizi-shell .. Verifico con ls.
    6. rm -r ../esercizi-shell.
    7. echo 'esercitazioni di informatica' > esercizi-shell/README. Verifico con cat esercizi-shell/README.
    8. echo 'Parte 1, introduzione alla shell' >> esercizi-shell/README. Idem.
    9. cd.
  2. Soluzioni:
    1. mkdir original, seguito da echo '*' > original/a.

    2. Per comodita’, entro in temp con cd temp. Dopo ogni passaggio, faccio:

      cd ..
      rm -r temp
      cp -r original temp
      cd temp
      

      Soluzioni:

      1. cp a b; cp a c. Verifico con ls; cat b; cat c.

      2. echo a stampa la stringa “a” a schermo; ls a stampa il nome del file a (che e’ a...); cat a stampa il contenuto del file a, cioe’ le due righe che abbiamo scritto prima.

      3. Nessuna. mv a b rinomina a in b. cp a b; rm aa prima fa prima una copia di a chiamata b, poi rimuove a.

      4. Nel primo caso mi ritrovo con tre copie di a, nel second con un solo file: c.

      5. mv a z rinomina a in z; quindi z e’ un file. mkdir z; mv a z prima crea una directory z, poi ci mette dentro a.

      6. echo a z stampa a schermo la stringa a z. mkdir z; echo a z fa la stessa cosa, ma crea anche una directory di nome z.

      7. Cosi’:

        echo qualcosa > a1
        ...
        echo qualcosa > a10
        rm a*
        
  3. Soluzioni:
    1. Da’ errore. -e non e’ un’opzione valida per ls.
    2. Si pianta. cat si aspetta un file di input: noi non glielo diamo, e lui resta in attesa. Dato che cat e’ fermo, il resto della pipeline resta in attesa. Siamo costretti ad uccidere la pipeline con Control-c.
    3. Da’ errore. . e’ una directory, cat si aspetta un file.
    4. Stampa la stringa “cat” a schermo. (Non esegue il comando cat...)
    5. Stampa il contenuto del file a (se esiste), ma visto che lo stdout e’ rediretto, mette l’output di cat nel file b. E’ equivalente a cp a b.
    6. Da’ errore: << non e’ un operatore di redirezione valido.
    7. Non ha senso: stiamo cercando di redirigere lo stdout di head sia verso il file a che, con la pipe |, verso lo stdin di tail.
    8. ls mette in a la lista dei file nella directory corrente; fin qui tutto ok. Mai rm non supporta la redirezione, si aspetta invece il nome di un file! Quindi finisce per dare errore.
    9. Scrive il testo “KrustyIlKlown” nel file a.
    10. Il primo tac legge FILE1 lo stampa a schermo rovesciato; pero’ la pipe ne redirige lo stdout al secondo tac: l’effetto complessivo e’ che FILE1 viene stampato a schermo nel verso giusto. Infine, l’ultimo > mette il contenuto di FILE1 in FILE2. Il tutto e’ equivalente a cp FILE1 FILE2.
  4. Soluzioni:
    1. Stampa le prime dieci righe di a in b.
    2. Idem.
    3. Idem.
    4. Stampa le ultime dieci righe di a in b.
  5. Soluzioni:
    1. Stampa a sottosopra, prende le prime venticinque righe, le mette in b. Quindi b contiene le ultime venticinque righe di a sottosopra.
    2. Stampa a, prende le ultime venticinque righe, le mette in b. Quindi b contiene ultime venticinque righe di a ma non sottosopra.
  6. Soluzioni:
    1. Stampa le prime dieci righe di a.
    2. Mette le prime dieci righe di a nel file temp, poi stampa le ultime dieci righe di a.
  7. Soluzioni:
    1. Stampa file e ne tiene solo le prime dieci righe.
    2. Si pianta. Come sopra, cat si aspetta un file in input; non non glielo diamo e lui resta in attesa.
  8. Soluzioni:
    1. Stampo le prime n del file, di queste tengo solo l’ultima: head -n n file | tail -n 1.
    2. Assumiamo che tot=n+m. Stampo le ultime tot righe del file, di queste tengo solo le ultime m: head -n tot file | tail -n m.
  9. Soluzioni:
    1. Eseguo:

      head -n 50 data/a | tail -n 25 > data/b
      head -n 25 data/a >> data/b
      tail -n 50 data/a >> data/b
      
    2. Come sopra, ma il secondo comando diventa:

      head -n 25 data/a | tac >> data/b