3.5. Comandos del shell¶
Un comando puede ser clasificado en las siguientes tipos de comandos (de menor a mayor nivel):
Comandos simples
Tuberías
Listas AND-OR
Listas
Listas compuestas
Comandos compuestos (o estructuras de control)
Definiciones de función
Cada uno de estos tipos se forma mediante la composición de elementos de los tipos inferior. Pero también se permite anidar distintos tipos de comandos (no todas las combinaciones son posibles como se verá más adelante), por lo que podríamos encontrarnos: tuberías de comandos compuestos, comandos compuestos formados por otros comandos compuestos, etc.
En general, el valor devuelto por un comando compuesto (tipo b y superiores) será el valor devuelto por el último comando simple ejecutado.
A continuación describiremos cada uno de los tipos. Puede ver más detalles en el apartado 2.9 del estándar.
3.5.1. Comandos simples¶
Un comando simple está formado por (todos los elementos son
opcionales) una secuencia de asignación de variables y
redirecciones (en cualquier orden) seguida de palabras
(elemento ejecutable y sus argumentos) y redirecciones. A
continuación se muestra la estructura genérica de un comando
simple (los corchetes [
]
indican qué elementos son opcionales,
que es la notación usada en el resto de este documento):
[VAR=v] [redir] [ejecutable args] [redir]
Pudiendo ser el ejecutable programas «ejecutables» (comandos internos y ejecutables externos) e «interpretables» (funciones).
Es decir, en un mismo comando simple se puede hacer simultáneamente la asignación de variables y la ejecución de un programa. Cuando un comando simple no incluye un programa a ejecutar, la asignación de variables afecta a todo el shell, de lo contrario la asignación sólo afecta al programa que se va a ejecutar.
Si en un mismo comando simple hubiera que hacer expansiones en las palabras y en la asignación de variables, primero se hace las expansiones de las palabras.
Ejemplos de comandos simples:
VAR=x
El anterior comando asigna el valor x
a la variable VAR
y
afecta a todo el proceso shell actual.:
VAR=x programa
Asigna el valor x
a la variable VAR
y afecta solo al programa
.:
VAR=y OTRA=z
VAR=x programa $VAR
echo $VAR
Se asigna el valor y
a la variable VAR
y el valor z
a la
variable OTRA
, que afectan a todo el shell. A continuación,
asigna el valor x
a la variable VAR
y afecta sólo al programa
,
al cual se le pasa como primer argumento y
. A continuación se
imprime y por pantalla.:
VAR=x > fichero programa
VAR=x programa > fichero #equivalente
Ambas líneas, asignan el valor x
a la variable VAR
que afecta
solo al programa
. Se ejecuta el programa
y la salida estándar
se redirige al archivo fichero. La redirección se realiza
independientemente de que aparezca antes o después del
programa
. Si hubiera varias redirecciones se realizan en el
orden de aparición en la línea, de izquierda a derecha.
3.5.2. Tuberías¶
Una tubería es una secuencia de uno o más comandos (simples o
compuestos, pero no ningún tipo de lista) separados por el
operador |
. La salida estándar de un comando se conecta a la
entrada estándar del siguiente comando (cada comando se ejecuta
en otro subshell simultáneamente). Esta conexión se hace
previamente a cualquier redirección. El formato es:
[ ! ] comando1 [ | comando2 … ]
Opcionalmente, se puede añadir delante el carácter !
que hace
la negación lógica del valor devuelto por el último comando, de
tal manera que el valor devuelto por la tubería sería 1
si el
último comando devuelve 0
, o 0
en caso contrario.
3.5.3. Listas AND-OR¶
Una lista AND es una secuencia de tuberías (tenga en cuenta que
una tubería puede ser sólo un comando simple) separadas por el
operador &&
. El formato es:
tuberia1 [ && tuberia2 … ]
Se van ejecutando las tuberías de izquierda a derecha hasta que una de ellas devuelva un valor distinto de cero. No se realiza ninguna expansión en una tubería hasta que el shell no determine que tiene que ejecutar dicha tubería (dependerá del resultado de la tubería anterior).
Una lista OR es una secuencia de tuberías separadas por el
operador ||
. El formato es:
tuberia1 [ || tuberia2 … ]
Se van ejecutando las tuberías de izquierda a derecha hasta que una de ellas devuelva un valor cero. No se realiza ninguna expansión en una tubería hasta que el shell no determine que tiene que ejecutar dicha tubería.
Una lista AND-OR es el resultado de combinar listas AND y/o OR
en una misma línea. Los operadores &&
y ||
se evalúan con la
misma prioridad de izquierda a derecha. Ejemplo:
tuberia1 || tuberia2 && tuberia3
3.5.4. Listas¶
Las listas son secuencias de una o más listas AND-OR separadas
por los operadores ;
o &
. Los operadores ;
y &
no pueden
aparecer seguidos (por ejemplo, daría error prog1 & ; prog2
)
Según el operador las listas pueden ser secuenciales, asíncronas o mixtas (combinación de ambas).
3.5.4.1. Listas secuenciales¶
Se utiliza como separador el operador ;
. Se van ejecutando los
distintos comandos secuencialmente (no se ejecuta un comando
hasta que haya terminado el anterior). Cada lista AND-OR debe
estar terminada por el operador ;
a excepción de la última
donde es opcional. El formato es:
listaAND-OR1 [ ; listaAND-OR2 … ] [ ; ]
3.5.4.2. Listas asíncronas¶
Se utiliza como separador el operador &
. Se van ejecutando los
distintos comandos sin esperar a que el comando anterior
termine (ejecución en segundo plano). El formato es:
listaAND-OR1 & [ listaAND-OR2 & ]
En este caso, a menos que se haga una redirección explícita de
la entrada estándar, si un programa en segundo plano lee de la
entrada estándar recibirá un error de fin de fichero (EOF
).
3.5.4.3. Listas mixtas¶
Son combinaciones de listas secuenciales y asíncronas. Por ejemplo:
#asíncrona y secuencial
lANDOR1 & lANDOR2 [ ; ]
#secuencial y asíncrona
lANDOR1 ; lANDOR2 &
#asíncrona y secuencial
lANDOR1 & lANDOR2 & lANDOR3 ; lANDOR4
#secuencial, asíncrona, secuencial
lANDOR1 ; lANDOR2 & lANDOR3
3.5.5. Listas compuestas¶
No es más que una secuencia de listas (apartado Listas),
separadas por el carácter de nueva línea (intro
), terminada
por el operador ;
, el operador &
, el carácter de nueva línea
(intro
) o un comando compuesto. La utilidad de este tipo de
listas se verá sobre todo cuando se expliquen los comandos
compuestos.
TAREAS
Mire el contenido del script script_operadores.sh, que deberá contener lo siguiente:
#!/bin/sh
head -1 /etc/passwd && echo "Sin error1A" || echo "Con error1B"
head -1 /nofile && echo "Sin error2A" || echo "Con error2B"
echo "Comando dividido \
en dos líneas"
echo "Sin escapado: $$"
echo "Con escapado: \$\$"
echo "N º de proceso del shell bash:" `pidof bash`
Compruebe que dispone del permiso de ejecución. Invóquelo y analice su funcionamiento.
Desde la línea de comandos, cree
listas y tuberías de todos los
tipos vistos usando combinaciones
de los comandos ls
, echo
, cat
y
ps
.
3.5.6. Comandos compuestos o estructuras de control¶
Los comandos compuestos son lo que en otros lenguajes de programación se conocen como estructuras de control. Cada uno de estos comandos compuestos (o estructuras de control) están delimitados por una palabra reservada u operador de control al principio y otro al final (terminador). Si se hace una redirección a continuación del terminador, en la misma línea, esa redirección se aplicará a todos los comandos que se encuentre en ese comando compuesto, a menos que se haga otra redirección explícita en un comando en concreto.
A continuación se hace un repaso por todas las estructuras de control disponibles en el estándar POSIX. Puede consultar el apartado 2.9.4 del estándar para más información.
3.5.6.1. Secuencial (agrupación de comandos)¶
La agrupación de comandos permite mejorar la legibilidad del código, aplicar una redirección a un conjunto de comandos y crear un subshell entre otras cosas.
Existen dos formas de agrupar comandos, con los siguientes formatos:
( lista-compuesta )
Se ejecuta la lista compuesta en un subshell. Los cambios que se produzcan en este subshell no afectarán al shell actual. Si la lista compuesta está terminada por el carácter de nueva línea, este carácter puede omitirse.
{ lista-compuesta }
Se ejecuta la lista compuesta en el shell actual. Recuerde que las listas compuestas están terminadas por los operadores
;
,&
o nueva línea (el último comando debe estar separado de la llave de cierre por esos operadores).
En ambos casos, se permite añadir una redirección al final
(detrás del )
o }
) que afectará a todos los comandos del
grupo.
3.5.6.2. Condicional: if-elif-else¶
Presenta la siguiente sintaxis:
if lista-compuestaA1 then
lista-compuestaB1
elif lista-compuestaA2 then
lista-compuestaB2
...
else
lista-compuestaN
fi
Las entradas elif
tienen el significado de un else
seguido
de un nuevo if
. Puede haber tantas entradas elif
como se
desee. Tanto las entradas elif
como la entrada else
son
opcionales.
En esta estructura, lo primero que se hace es ejecutar la
lista-compuestaA1
, si el valor devuelto es 0
(ADVERTENCIA: 0
significa verdadero aquí), se ejecutaría lista-compuestaB1
y
terminaría la estructura if
. Si el valor devuelto no es 0
se
comprueba el siguiente elif
. Si ninguna de las
listas compuestas A
devuelve 0
se ejecutaría el bloque del
else
. En otras palabras, las listas compuestas B
solo se
ejecutan si se ha comprobado su respectiva lista compuesta A
y ha devuelto 0
.
A veces, para mejorar la legibilidad, las listas compuestas
se encierran entre llaves (agrupación de comandos) pero es
opcional. Asimismo, then
estará en una línea u otra
dependiendo del operador (&
, ;
o nueva línea) utilizado para
terminar la lista compuesta. Si desea dejar una
lista compuesta vacía (no quiere realizar ninguna operación
en un determinado caso), puede utilizar el comando :
(comando nulo).
Por ejemplo, si tenemos un programa llamado condicion
que
devuelve 0
si algo es verdadero y 1
si es falso, los
siguientes ejemplos son equivalentes:
#Ejemplo 1
if condicion; then
{ cmd1; cmd2; }
fi
#Ejemplo 2
if condicion; then
cmd1; cmd2;
fi
# Ejemplo 3
if condicion
then
cmd1;
cmd2;
fi
# Ejemplo 4
if condicion
then
cmd1
cmd2
fi
# Ejemplo 5
if condicion; then cmd1; cmd2; fi
# Ejemplo 6
if condicion; then { cmd1; cmd2; } fi
Recuerde que si usa las llaves, debe separarlas del resto de elementos. Por ejemplo:
# Llave inicial no separada de cmd1
if condicion; then {cmd1; cmd2;} fi
# Llave inicial no separada de 'then'
if condicion; then{ cmd1; cmd2;} fi
# Llave final no separada de cmd2
if condicion; then { cmd1; cmd2} fi
darán error de sintaxis.
Respecto a la condición que puede usarse, basta cualquier lista
compuesta que devuelva un valor (por ejemplo, pueden usarse los comandos
internos true
o false
). El valor de una lista compuesta es el valor del
último comando simple ejecutado en la lista compuesta.
Un programa habitual que se utiliza como condición es el programa test
.
El comando test
se puede ejecutar de dos formas (ambas equivalentes):
test expresion
#los [] deben estar separados
[ expression ]
En la segunda forma los corchetes no son operadores ni indican que la expresión sea opcional, sino que realmente son el nombre del programa.
Puede ver la descripción del comando test según el estándar.
Como expresiones más habituales pueden usarse las siguientes:
Expresión |
Verdadera sí (devuelve 0) |
---|---|
|
n1 = n2 |
|
n1 ≠ n1 |
|
n1 > n2 |
|
n1 ≥ n2 |
|
n1 < n2 |
|
n1 ≤ n2 |
Expresión |
Verdadera sí (devuelve 0) |
---|---|
|
|
|
|
|
|
|
|
Expresión |
Verdadera sí (devuelve 0) |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Cualquiera de las condiciones anteriores puede ser precedida por el
operador negación !
, en cuyo caso la condición será cierta si no se
satisface la comparación indicada. Por ejemplo, ! -d $DIR
se cumplirá si
$DIR
NO es un directorio.
Asimismo, se permite crear condiciones múltiples mediante los operadores:
|
AND: Verdadero si ambas condiciones son verdaderas |
|
OR: Verdadero si se cumple alguna de las dos condiciones |
Recuerde las restricciones de sintaxis del shell en lo que respecta a
los espacios, especialmente importantes en la escritura de las
condiciones. Por ejemplo, la siguiente entrada dará error de sintaxis
(el espacio tras ;
sí puede omitirse):
# Falta espacio entre if y [
if[ condicion ]; then
Y la siguiente dará error porque buscaría el comando [comando]
(incluyendo los corchetes como parte del nombre del comando), que en
general no encontrará (mostrando un mensaje de orden no encontrada).:
if [comando]; then
TAREAS
El comando test se puede ejecutar sin necesidad de utilizarlo en una estructura de control. Escriba la siguiente instruccion y analice su comportamiento (pruebe asignando también
1
a la variableV
y una cadena de texto):V=0; [ $V -eq 0 ] && { echo ES; echo 0; } || echo ES 1
El siguiente comando imprime por pantalla si el usuario actual es
root
. Ejecútelo comoroot
y como un usuario normal:if [ "`id -u`" -eq 0 ]; then echo ROOT; fi
Mire el contenido del siguiente script en su sistema y compruebe que tiene el permiso de ejecución:
#!/bin/sh FILE=/tmp/archivo if [ -r $FILE -a ! -w $FILE ]; then echo $FILE existe no modificable else echo No encontrado o modificable fi VAR1=1; VAR2=1 if [ $(($VAR1)) -ne $(($VAR2)) ]; then echo Distintos elif ls /; then : fi
Ejecute los comandos siguientes y analice el resultado:
rm –f /tmp/archivo ./script_if.sh
Ejecute ahora los comandos siguientes y vuelva a analizar el resultado:
touch /tmp/archivo chmod –w /tmp/archivo ./script_if.sh
3.5.6.3. Condicional: case¶
Presenta la siguiente sintaxis:
case cadena_texto in
patron1) lista-compuesta1;;
patron2) lista-compuesta2;;
...
* ) lista-defecto [;;]
esac
cadena_texto
debe aparecer obligatoriamente en la misma línea que la
palabra reservada case
(la palabra reservada in
puede estar en la
siguiente línea). En esta estructura, primero se expande cadena_texto
(si es necesario) y busca el primer patrón que encaje con dicho
valor. Cuando lo encuentre, ejecuta la lista compuesta
correspondiente y finaliza la estructura. Los patronN
se interpretan
como cadenas de caracteres y si es necesario se expanden (por
ejemplo, pueden contener variables). Admite los mismos caracteres que
los usados para la expansión de ruta (*
, ?
y []
). El patron *
coincide con todo. Asimismo, pueden
usarse patrones múltiples mediante el operador |
y opcionalmente
pueden comenzar con el paréntesis:
patronA | patronB)
(patronA | patronB)
(patronC)
El doble punto y coma ;;
permite determinar el final de los elementos
a interpretar cuando se cumpla su patrón asociado. Por ese motivo, el
;;
del último patrón puede omitirse. Es posible añadir espacios entre
los patrones y los paréntesis abiertos )
que marcan el final del
patrón. Conforme a esto, serían sintaxis válidas alternativas las
siguientes:
# Alternativa 1
case cad_texto in
patr)
cmd1;
cmd2;;
esac
# Alternativa 2
case cad_texto in
patr )
cmd1
cmd2
esac
# Alternativa 3
case cad_texto in patr) cmd1; cmd2;; esac
# Alternativa 4
case cad_texto in (patr) cmd1; cmd2; esac
TAREAS
Mire el contenido del siguiente script en su sistema y compruebe que tiene el permiso de ejecución:
#!/bin/sh case $1 in archivo | file) echo Archivo ;; *.c) echo Fichero C ;; *) echo Error echo Pruebe otro ;; esac
Ejecute los comandos siguientes y analice el resultado:
./script_case.sh archivo ./script_case.sh file ./script_case.sh file.c ./script_case.sh file.c++
3.5.6.4. Bucles incondicionales: for¶
Presenta la siguiente sintaxis:
for V in valores; do
lista-compuesta
done
El nombre de la variable V
debe aparecer obligatoriamente junto con
la palabra reservada for
en la misma línea. valores
debe estar
obligatoriamente en la misma línea que la palabra reservada in
. El
punto y coma ;
puede sustituirse por un salto de línea, y viceversa.
Así, por ejemplo, serían sintaxis válidas las siguientes alternativas:
# Alternativa 2
for V in valores; do lista-compuesta done
# Alternativa 3
for V
in valores
do
lista-compuesta
done
# Alternativa 4
for V in valores
do
lista-compuesta
done
valores
se corresponde con un conjunto de valores (tomándose cada
valor como una cadena de caracteres que puede ser objeto de expansión y
como caracteres de separación los caracteres definidos en la variable
IFS
). La estructura for
define la variable V
(si no ha sido
previamente definida). Para cada uno de los valores del resultado de
expandir valores
, la estructura inicializa la variable V
con
dicho valor y realiza una iteración (ejecutando lista-compuesta
, en la
cual suele ser habitual acceder al valor de la variable V
).
Es posible omitir in valores
. Si se omite equivale a haber
escrito: in "$@"
TAREAS
Ejecute el siguiente comando y compruebe como se puede utilizar una expansión de ruta dentro de un bucle
for
:for i in ~/.*; do echo Fichero oculto: $i; done
Mire el contenido del siguiente script en su sistema, compruebe que tiene el permiso de ejecución, invóquelo y compruebe el contenido del fichero
ficherosalida
que crea:#!/bin/sh for i in 1 2 3; do echo "Iteracion: $i" done > ficherosalida
Mire el contenido del siguiente script en su sistema, compruebe que tiene el permiso de ejecución e invóquelo.
#!/bin/sh for file in `ls /`; do echo "Fichero: $file" done
Suele ser habitual el uso del comando externo seq
para generar una lista
de valores. Si bien este comando no está recogido en el estándar POSIX,
es habitual su presencia en la mayoría de los sistemas UNIX. El comando
seq
presenta la sintaxis:
seq valor_inicial valor_final
siendo ambos valores números enteros. La salida del comando es la secuencia de números enteros entre ambos valores extremos indicados.
TAREAS
Ejecute el comando siguiente y observe su salida:
seq 1 10
Mire el contenido del siguiente script en su sistema, compruebe que tiene el permiso de ejecución general e invóquelo:
#!/bin/sh for i in `seq 1 3`; do echo "Iteracion: $i" done
Compruebe cómo se obtiene el mismo resultado que al invocar el fichero
script_for1.sh
aunque mostrando el resultado a la salida estándar.
3.5.6.5. Bucles condicionales: while y until¶
Presentan la siguiente sintaxis:
# Bucle while
while lista-comp-condicion do
lista-compuesta done
# Bucle until
until lista-comp-condicion do
lista-compuesta
done
La lista-comp-condicion
es una lista compuesta que se rige por las
mismas directrices indicadas en la estructura if
. La estructura:
while
va iterando (interpretalista-compuesta
) mientras se cumpla la condición indicada (lista-comp-condicion
devuelve el valor0
)until
va iterando (interpretalista-compuesta
) mientras NO se cumpla la condición indicada (lista-comp-condicion
devuelve un valor distinto de0
).
Así, por ejemplo, serían válidas y equivalentes las sintaxis
siguientes, si la condición del until
es la condición del while
negada:
# Alternativa 1
while l-comp-condW do
cmd1
cmd2
done
# Alternativa 2
until l-comp-condU do
cmd1
cmd2
done
# Alternativa 3
while l-comp-condW ; do cmd1; cmd2; done
# Alternativa 4
while l-comp-condW ; do { cmd1;cmd2;} done
TAREAS
Mire el contenido del siguiente script en su sistema, compruebe que tiene el permiso de ejecución e invóquelo:
#!/bin/sh CONTADOR=0 while [ $CONTADOR – lt 3 ]; do echo El contador es $CONTADOR CONTADOR=$(( $CONTADOR+1 )) done
Mire el contenido del siguiente script en su sistema, compruebe que tiene el permiso de ejecución e invóquelo:
#!/bin/sh CONTADOR=0 until [ $CONTADOR – ge 3 ]; do echo El contador es $CONTADOR CONTADOR=$(( $CONTADOR+1 )) done
Podrá comprobar cómo ambos scripts devuelven la misma salida.
3.5.6.6. Ruptura de sentencias de control¶
Igual que en otros lenguajes de programación, como en el lenguaje C,
es posible romper el funcionamiento normal de las estructuras
repetitivas ( for
, while
y until
). Sin embargo, hacerlo supone hacer
código no estructurado. Por coherencia con lo visto en otras
asignaturas, no se aconseja su uso.
En shell script esto se realiza con dos comandos internos: continue
y
break
. Ambos son comandos internos de la shell con la siguiente sintaxis y
funcionalidad:
continue
: utilizado en estructuras de control repetitivas para detener la iteración actual y continuar con la siguiente. Su sintaxis es:continue [ n ]
El parámetro opcional
n
es un número entero positivo que permite especificar la estructura de control en la que se desea detener la iteración. Si se tienen varias estructuras de control anidadas, la estructura actual en la que se encuentra el continue corresponde a la estructura 1; la estructura superior que engloba a ésta sería la estructura 2, y así sucesivamente. Así, el valor den
referencia a la estructura de control en la que deseamos detener la iteración actual y continuar con la siguiente (por omisión,n
vale1
).break
: utilizado en estructuras de control repetitivas para detener todas las iteraciones restantes de la estructura de control actual. Su sintaxis es:break [ n ]
El parámetro opcional
n
es un número entero positivo que permite indicar si se desean cancelar varias estructuras de control anidadas (por omisión, vale1
, que referencia a la estructura actual en la que se encuentra elbreak
).
3.5.7. Funciones¶
Presentan la siguiente sintaxis:
- Definición
fnombre() comando-compuesto [redir]
- Invocación
fnombre [arg1 arg2 … ]
El paréntesis siempre debe estar vacío (sólo indica que se está definiendo una función). Pueden insertarse espacios antes, entre y después del paréntesis. El comando compuesto puede ser cualquier de los que se han visto (agrupación de comandos, estructuras condicionales, estructuras repetitivas). Opcionalmente pueden aplicarse redirecciones a la función (afecta a los comandos que contiene, salvo que contengan una redirección explícita). A continuación se muestran ejemplos básicos de definición de funciones:
# Alternativa 1
fnombre(){
comando1
comando2
}
# Alternativa 2
fnombre() {
comando1;
comando2;
}
# Alternativa 3
fnombre() { comando1; comando2; }
En lo que se refiere al nombrado de las funciones, se aplican los mismos criterios antes expuestos para el nombrado de las variables.
El estándar permite que dentro de una función se invoque a otra. Los
argumentos pasados a la función en su invocación son accesibles desde el
cuerpo de la función mediante los parámetros posicionales $1
, $2
,…,
$9
, ${10}
,… Por tanto, dentro de la función, los parámetros
posicionales no corresponden a los argumentos usados en la invocación
del script.
Al igual que las variables, las funciones son:
Locales: sólo existen en el proceso shell en que son definidas.
Sólo son accesibles desde el momento de su definición hacia abajo del script, esto es, siempre deben definirse primero e invocarse después (no puede invocarse a una función que es definida más adelante).
Dentro de la función son visibles todas las variables y funciones definidas antes de su invocación. Y las variables definidas dentro de la función son visibles fuera tras la invocación de la función.
Dentro del cuerpo de la función suele ser habitual el uso del comando return, el cual provoca la salida inmediata de la función con el valor de retorno (número) indicado:
return [ n ]
Si no se indica ningún valor de retorno, la función devuelve el valor
del último comando ejecutado. Como siempre, el valor devuelto por la
función puede obtenerse con la variable $?
. return
también puede
utilizarse para terminar un script invocado implícitamente con .
.
TAREAS
Mire el contenido del siguiente script en su sistema, compruebe que tiene el permiso de ejecución, invóquelo con 2 números enteros como argumentos y analice su funcionamiento:
#!/bin/sh
suma () {
C=$(($1+$2))
echo "Suma: $C"
return $C
echo "No llega"
}
suma 1 2
suma $1 $2 #suma los 2 argumentos
echo "Valor devuelto: " $?