2. Comencem amb el Sha-Bang

Shell programming is a 1950s juke box . . .

—Larry Wall

En el cas més simple, un guió no és més que una llista de comandes del sistema emmagatzemades a un fitxer de text. I ja això és útil, si més no per evitar-nos teclejar una i altra vegada la mateixa seqüència de comandes.

Exemple 1. *cleanup*: Un script per netejar els fitxers de log de /var/log

1
2
3
4
5
6
7
# Cleanup
# A executar com a root, per suposat

cd /var/log
cat /dev/null > messages
cat /dev/null > wtmp
echo "Fitxers de Log netejats."

No hi ha res inusual aquí. Es tracta d’una seqüència de comandes que podrien haver estat invocades una darrera de l’altre des de la línia de comandes en una consola o terminal. Els avantatges de col·locar aquestes comandes en un guió van més enllà de simplement no haver de tornar a reescriure-les. El guió es converteix en un programa, una eina, i com a tal, pot ser fàcilment modificada o adaptada a requeriments particulars.

Exemple 2. *cleanup*: versió millorada del guió de neteja.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash
# Una capçalera adequada per un guió Bash

# Cleanup, versió 2

# A executar com a root, per suposat

# Mostra un missatge d'error i sorti si no s'executa com a root.

LOG_DIR=/var/log
# És millor fer servir variables que els valors directament.
cd $LOG_DIR

cat /dev/null > messages
cat /dev/null > wtmp


echo "Fitxers de Log netejats."

exit #  La manera adequada de sortir d'un guió.
     #  Un "exit" sense paràmetres retorna el resultat de sortida de
     #  la comanda anterior.

Ara si que comença a semblar un guió real! Però encara podem anar-hi més lluny...

Exemple 3. *cleanup*: Una versió ampliada i generalitzada dels guions anteriors.

 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#!/bin/bash
# Cleanup, versió 3

#  Atenció:
#  -------
#  Aquest guió fa servir força característiques que seran explicades
#  més endavant.
#  Quan ja entenguis la primera meitat d'aquest llibre, no hauries de
#  trobar res misterios aquí.

LOG_DIR=/var/log
ROOT_UID=0     # Només l'usuari amb $UID 0 té privilegis de root
LINIES=50      # El nombre de línies guardades per defecte
E_XCD=86       # Error: no es pot canviar de directori
E_NOTROOT=87   # Error: No és root

# A executar com a root, per suposat
if [ "$UID" -ne "$ROOT_UID" ]
then
  echo "Cal ser executat per root"
  exit $E_NOTROOT
fi

if [ -n "$1" ]
# Comprova si se li ha passat al menys un argument per línia de comandes
then
  linies=$1
else
  linies=$LINIES # Si no ha estat especificat el nombre de línies, es
                # considera el valor per defecte
fi


#  Stephane Chazelas suggereix la següent manera de comprovar els
#  arguments de línia de comandes, encara que és una mica avançat pel
#  nivell actual d'aquest llibre.
#
#    E_WRONGARGS=85  # Error: argument no numèric (format d'argument
#    incorrecte)
#
#    case "$1" in
#    ""      ) linies=50;;
#    *[!0-9]*) echo "Ús: `basename $0` linies-a-netejar";
#     exit $E_WRONGARGS;;
#    *       ) linies=$1;;
#    esac
#
#  Salta a la secció sobre bucles per desxifrar-ho.

cd $LOG_DIR

if [ `pwd` != "$LOG_DIR" ]  # o bé   if [ "$PWD" != "$LOG_DIR" ]
                            # que vol dir si no es troba a /var/log?
then
  echo "No es pot canviar al directori $LOG_DIR."
  exit $E_XCD
fi  # Torna a comprovar si es troba en el directori adequat abans de
    # començar a trastejar amb el fitxer de log.

# Seria molt més eficient de la següent manera:
#
# cd /var/log || {
#   echo "No es pot canviar al directori corresponent." >&2
#   exit $E_XCD;
# }

tail -n $linies messages > mesg.temp # Guarda la darrera secció del
                                     # fitxer de missatges de log.
mv mesg.temp messages                # Reanomena el fitxer com a log
                                     # del sistema.


#  cat /dev/null > messages
#  Ja no cal, doncs el mètode anterior és més segur.

cat /dev/null > wtmp  #  ': > wtmp' i '> wtmp'  produeixen el mateix efecte.

echo "Fitxers de Log netejats."
#  Fixat que la resta de fitxers de log que es trobin a /var/log no
#  es toquen.

exit 0
#  El valor zero de retorn, indica a la shell que el guió ha
#  finalitzat sense errors.

Donat que podria ser que no ens interessés eliminar el log de sistema completament, aquesta versió del guió conserva la darrera secció dels missatges de log. Constantment descobrirem maneres d’ajustar els nostres guions per millorar l’efectivitat.


El sha-bang ( #!)[1] a l’inici d’un fitxer permet al sistema saber que el contingut d’aquest fitxer ha de ser processat per un determinat intèrpret. De fet, #! és un número màgic de dos bytes[2], una marca especial que designa el tipus de fitxer. En aquest cas indica que és un executable. Trobaràs més informació sobre aquest fascinant tema escrivint man magic a la teva consola. Tot just després del sha-bang apareix el programa que interpreta el contingut del fitxer, ja sigui comandes de la shell, d’un altre llenguatge de programació (ex. Python) o qualsevol altra utilitat. El que apareix a continuació del sha-bang és interpretat per a aquest programa[3].

Considera les següents capçaleres:

#!/bin/sh
#!/bin/bash
#!/usr/bin/perl
#!/usr/bin/tcl
#!/bin/sed -f
#!/bin/awk -f

Cadascuna d’aquestes línies defineixen un intèrpret diferent per processar el contingut del fitxer.[4]

/bin/sh és generalment la shell per defecte en la majoria de les variants comercials de UNIX (no així en sistemes Linux que fan servir bash). Si ens interessa molt la portabilitat en màquines no Linux, podem fer servir sh, tot i que sacrificarem les característiques específiques de Bash. Amb tot, un guió que s’executi amb sh respectarà l’estàndard POSIX[5]. Per més informació, mira XXX 36.9. Portability Issues i XXX Chapter 34. Gotchas.

Fixat que el camí que s’indica al sha-bang ha de ser correcte, altrament generarà un missatge d’error (normalment No es troba la comanda) com a única resposta d’executar el guió [#envline].

Si el nostre guió consisteix únicament en una llista de comandes i no inclou cap directiva interna de la shell (com ara una assignació a una variable de l’estil linies=50), podem ometre #!. De fet, si Bash és la teva shell per defecte, #! no és en realitat necessària a l’inici del guió. Amb tot, la necessitaràs si finalment et cal executar el guió des d’alguna altra shell com ara tcsh.

Nota

En aquest llibre, se’t proposa que construeixis els teus guions de manera modular. Guarda peces de codi que creguis que poden ser-te d’utilitat en el futur. Amb el temps, disposaràs d’una biblioteca extensa d’utilitats. Per exemple, considera el següent guió que comprova si ha estat cridat amb un cert nombre d’arguments:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
E_WRONG_ARGS=85     # resultat de retorn en cas d'error
parametres_valids="-a -h -m -z"
#                  -a = all, -h = help, etc.

if [ $# -ne $nr_args_esperats ]
then
  echo "Ús: `basename $0` $parametres_valids"
  # `basename $0` és el nom d'aquest fitxer
  exit $E_WRONG_ARGS
fi

Anotacions

[1]En la literatura es sol trobar com she-bang o sh-bang. El nom està format per les inicials dels mots amb els que sovint s’anomenen els símbols que el composen: sharp (#) i bang (!).
[2]Algunes versions de UNIX (aquelles basades en 4.2 BSD) suposadament prenen un nombre màgic de quatre bytes, en requerir un espai en blanc després de l’exclamació (bang). Per exemple #! /bin/sh. Probablement no sigui més que un mite segons Sven Mascheck.
[3]

El sha-bang en un guió de shell serà la primera línia que veurà l’intèrpret de comandes (sh o bash per exemple). Donat que la línia comença amb #, serà interpretat correctament com un comentari. La línia ja haurà fet la seva feina, permetent escollir l’intèrpret.

De fet, si el guió inclou una línia extra amb #!, bash la interpretarà com un comentari.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/bin/bash

echo "Part 1 del guió"
a=1

#!/bin/bash
# La línia anterior **no** llença un nou guió.

echo "Part 2 del guió"
echo $a  # Escriurà el valor de $a, és a dir, 1.
[4]

El sha-bang permet fer alguns trucs interessants, com per exemple:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#!/bin/rm
# Guió que s'auto elimina

# No sembla que passi gran cosa quan executes aquest guió... excepte
# que el fitxer desapareix.

VALOR=85

echo "Aquesta línia no es mostrarà mai (vols apostar?)."

exit $VALOR  # No importa. El guió no surt per aquí.
             # Si fas echo $? després de finalitzar el guió, rebràs un
             # 0 i no un 85.

També pots intentar afegir a un fitxer de text, com ara README, el sha-bang #!/bin/more i donar-li permissos d’execució. El resultat és un fitxer de documentació que s’auto llista.

[5](P)ortable (O)perating (S)ystem (I)nterface (POSIX) és un intent d’estandardització dels sistemes operatius “tipus” UNIX. Les especificacions POSIX apareixen llistades a la web del Open Group.
[6]Per evitar aquests problemes, els guions poden començar amb #!/bin/env bash. En el cas de les màquines UNIX, això pot ser especialment útil, donat que bash no es troba a /bin