cc31a Desarrollo de Software de Sistemas

Patricio Poblete (ppoblete@dcc.uchile.cl)

Shell

El shell es el programa que interpreta los comandos que el usuario tipea interactivamente. También puede procesar comandos pre-almacenados en un archivo y tiene instrucciones que lo transforaman en un lenguaje de programación.

En primer lugar analizaremos diversos ejemplos de uso interactivo del shell:


        $ who
        
        $ date; who
        
        $ (date; who) | wc
        
        $ date; who | wc
        
        $ (sleep 10; echo Ya pasaron 10 segundos) &
        
        $ echo hola >x
        
        $ >x echo hola
        
        $ echo *   # * no calza con nombres que empiezan por "."
        
        $ echo \*
        
        $ echo '*'
        
        $ echo x*y  # nombres de la forma x...y

Resumen de metacaracteres

clase de caracteres, también 0-9, a-z
>redirecciones
<
>>
|pipe
<"here doc"
...
FIN
*secuencia de 0 o más caracteres cualquiera (en nombres de archivos)
?un caracter cualquiera
[ccc]
;terminador
&como ";" pero no espera
`cmd`ejecuta cmd y reemplaza el output en este lugar (sin newlines)
(...)ejecuta en un sub-shell
$0, ..., $9argumentos del Shell ($0 es el nombre del comando)
$var, ${var}valor de una variable
\ctomar caracter c literalmente
'...'tomar literalmente
"..."similar, pero interpreta $, `` y \
#comentario
var=valorasignación
cmd1 && cmd2ejecuta cmd2 sólo si cmd1 terminó exitosamente
cmd1 || cmd2ejecuta cmd2 sólo si cmd1 falló

Archivos de comandos (shellfiles)

nu.sh


        who | wc -l

Uso:


        $ sh <nu.sh
        
        $ sh nu.sh
        
        $ chmod +x nu.sh
        $ ./nu.sh

Para poder ejecutarlo como comando (sin ./) deberíamos ponerlo en ~/bin.

Argumentos:

cx1.sh


        chmod +x $1

Uso: ./cx1.sh archivo

cx2.sh


        chmod +x $*

Uso: ./cx2.sh archivo1 archivo2 ...

Uso de `...`


        $ mail `cat amigos`

Variables


        $ echo $PATH
        
        $ PATH=$PATH:/otro/directorio

Variables creadas por el usuario


        $ pwd
        
        $ dir=`pwd`
        
        $ cd /tmp
        
        $ more $dir/x
        
        $ set # muestra todas las variables
        
        $ export var # para que sea visible en los sub-shells

Existe un comando "." que lee y ejecuta un archivo de comandos como si hubiese sido tipeado (no como sub-shell).

ejemplo.sh


        cd /tmp
        echo hola

Usando ese archivo:


        $ cat ejemplo.sh
        
        $ . ejemplo.sh
        
        $ pwd

Redirecciones

>archivo
>>archivo
<archivo
cmd1 | cmd2
2>archivoredirige fd 2
2>>archivo
2>&1mezcla fd 2 con fd 1
4<&0
<"here doc"
...
FIN
<<'FIN'"here doc" sin sustituciones
...
FIN

Loops


        for var in lista de palabras
        do
            comandos
        done

Ejemplo:


        $ for i in *
        > do
        >    echo $i
        > done

Programación en shell

Ejemplo: comando cal

        $ cal october 2001 # error
        
        $ cal 10 2001 # OK

Hagamos nuestro propio comando cal más flexible:

cal


        # cal - interfaz más agradable para /usr/bin/cal
        
        case $# in   # $# es el número de argumentos
        # Thu Oct 11 09:59:36 CLT 2001
        0) set `date`; m=$2; y=$6;; # set define $1, $2, etc.
        1) m=$1; set `date`; y=$6;; # un argumento ==> año
        *) m=$1; y=$2;;             # dos args ==> mes y año
        esac
        
        case $m in
        jan*|Jan*) m=1;;
        feb*|Feb*) m=2;;
        mar*|Mar*) m=3;;
        apr*|Apr*) m=4;;
        may|May)   m=5;;
        jun*|Jun*) m=6;;
        jul*|Jul*) m=7;;
        aug*|Aug*) m=8;;
        sep*|Sep*) m=9;;
        oct*|Oct*) m=10;;
        nov*|Nov*) m=11;;
        dec*|Dec*) m=12;;
        [1-9]|10|11|12) ;;
        *) y=$m; m="";; # sólo año
        esac
        /usr/bin/cal $m $y # ejecutamos el verdadero cal

Variables built-in del shell

$#número de argumentos
$*todos los argumentos
$-opciones
$?valor retornado por el último comando
$$pid del shell
$!pid del último comando iniciado con &
$HOMEdefault para cd
$IFSseparador de argumentos
$PATHlista de directorios
$PS1prompt 1
$PS2prompt 2

Otros comandos


        if cmd
        the
            cmds
        else
            cmds
        fi

En el if es frecuente usar el comando test:

saluda


        if test "$1" = hola
        then
            echo "Hola!"
        else
            echo "???"
        fi

Ejemplo: Comando which

mywhich


        # which - busca comando en PATH
        
        case $# in
        0) echo "Use: which comando"; exit 2
        esac
        
        for i in `echo $PATH|sed 's/:/ /g'`
        do
            if test -f $i/$1
            then
                echo $i/$1
                exit 0    # lo encontramos
            fi
        done
        exit 1 # no se encontró

¡Ojo! Este comando puede ser saboteado instalando un falso comando test en el PATH. Esto se puede prevenir haciendo:

mywhich2


        # which - busca comando en PATH
         
        oldpath=$PATH
        PATH=/bin:/usr/bin
        case $# in
        0) echo "Use: which comando"; exit 2
        esac
         
        for i in `echo $oldpath|sed 's/:/ /g'`
        do
            if test -f $i/$1
            then
                echo $i/$1
                exit 0    # lo encontramos
            fi
        done
        exit 1 # no se encontró

Dos maneras de esperar a un usuario:


        while sleep 60 # siempre retorna OK (0)
        do
            who | grep juan
        done


        until who|grep juan # retorna 0 (verdadero) si encuentra algo
        do
            sleep 60
        done

Empaquetaremos la segunda versión como un comando watchfor:

watchfor


        # watchfor - vigila si un usuario se conecta
        
        PATH=/bin/:/usr/bin
        case $# in
        0) echo "Use: watchfor persona"; exit 1
        esac
        
        until who|grep "$1"
        do
            sleep 60
        done

Nota: Se puede decir "watchfor 'juan|maria'" y funciona.

watchwho


        # watchwho - vigila usuarios que llegan y se van
        
        PATH=/bin:/usr/bin
        new=/tmp/ww1.$$
        old=/tmp/ww2.$$
        
        >$old # crea archivo vacío
        
        while true
        do
            who >$new
            diff $old $new
            mv $new $old
            sleep 60
        done

La salida se puede mejorar un poco agregando un post-proceso:

watchwho2


        # watchwho - vigila usuarios que llegan y se van
         
        PATH=/bin:/usr/bin
        new=/tmp/ww1.$$
        old=/tmp/ww2.$$
         
        >$old # crea archivo vacío
         
        while true
        do
            who >$new
            diff $old $new
            mv $new $old
            sleep 60
        done | awk '/>/{$1="login:  "; print}
                    /</{$1="logout: "; print}'

Interrupciones


        trap 'comandos' señales
0shell exit
1hangup
2interrupt (^C)
3quit (^\)
9kill
15terminate

Ejemplo: limpiar archivos temporales de whatchwho


        trap 'rm -f $new $old; exit 1' 1 2 15

Para proteger a un programa del hangup:


        (trap '' 1; comando) &

Esto se puede empaquetar en un comando nohup:

nohup


        trap '' 1 15
        exec nice -5 $*