Programación del Intérprete de Órdenes (Shell Scripts)

Sistemas Operativos
Práctica 1: (Sesiones 1 y 2)
Programación del Intérprete
de Órdenes
(Shell Scripts)
Equipo “Sistemas Operativos DISCA/DSIC”
Universidad Politécnica de Valencia
1. NOMENCLATURA...................................................................................................................... 3
2. PREPARACIÓN ........................................................................................................................... 3
3. EL INTÉRPRETE DE ÓRDENES COMO LENGUAJE DE PROGRAMACIÓN ............... 4
3.1. INTRODUCCION ........................................................................................................................ 4
3.2. SHELL SCRIPTS ......................................................................................................................... 4
3.2.1. Cómo ejecutar un shell script ....................................................................................................4
3.2.2. Argumentos en shell scripts .......................................................................................................5
3.2.3. Estructura de un shell script ......................................................................................................6
3.3. REDIRECCIONES....................................................................................................................... 6
3.3.1. Redirección de la salida estándar..............................................................................................6
3.3.2. Redirección de la salida de error ..............................................................................................8
3.3.3. Redirección de la entrada estándar ...........................................................................................9
3.3.4. Tuberías ...................................................................................................................................10
EJERCICIOS .................................................................................................................................. 11
3.4. VARIABLES.............................................................................................................................. 11
3.4.1. Variables y comillas.................................................................................................................11
3.4.2. Caracteres comodines..............................................................................................................12
EJERCICIOS .................................................................................................................................. 13
3.5. CONTROL DEL FLUJO ............................................................................................................ 13
3.5.1. Bucles con la sentencia for ......................................................................................................13
EJERCICIOS .................................................................................................................................. 14
3.5.2. Ejecución condicional con la sentencia if................................................................................15
3.5.3. La orden test ............................................................................................................................16
EJERCICIOS .................................................................................................................................. 18
4. FILTROS A UTILIZAR............................................................................................................. 19
4.1. GREP .......................................................................................................................................... 19
4.2. AWK........................................................................................................................................... 19
4.2.1. Sintaxis.....................................................................................................................................19
4.2.2. Dividiendo la entrada en registros y campos ..........................................................................20
4.2.3. Los patrones especiales BEGIN y END ...................................................................................21
4.2.4. La sentencia IF ........................................................................................................................21
4.3. TR ............................................................................................................................................... 22
EJERCICIOS .................................................................................................................................. 22
ii
1. Nomenclatura
A fin de mejorar la comprensión del texto, se utilizan distintos tipos de caracteres y
convenciones, tal y como se resume a continuación:
•
Las órdenes que se introducen desde teclado aparecen tras una línea vertical, y utilizan un tipo
de letra específico. La posible respuesta a la orden aparece a continuación, y se escribe en
negrita. Por ejemplo:
$ orden introducida
respuesta de la orden anterior
•
El texto correspondiente a un fichero (shell script) se escribe utilizando el mismo tipo de letra
que en el caso de las órdenes, pero encerrado entre dos líneas horizontales. Por ejemplo:
Texto que corresponde
al contenido de un determinado fichero
al que se hace referencia en el texto
•
En aquellos lugares en los que se plantea una cuestión a responder por parte del alumno,
aparece un recuadro en blanco delimitado mediante una línea doble. El objeto de estas
respuestas es que el alumno las utilice como material de estudio. De forma alternativa el
alumno puede copiar el resultado que aparece en pantalla en un editor de textos y guardar
dichos resultados en un fichero.
1
2. Preparación
Antes de comenzar esta práctica se recomienda crear un directorio nuevo que se utilizará como
espacio de trabajo y descargar en él los ficheros que contienen los shell scripts referidos en este
documento. Para ello, realice las siguientes acciones:
1. Cree un nuevo directorio (escoja el nombre que prefiera, no es necesario que se llame
soprac1) y sitúese en él
$ mkdir soprac1
$ cd soprac1
2. Acceda con un navegador a la página de la asignatura Sistemas Operativos del portal
poliformat.upv.es.
3. Descarge el archivo ficherosPr1.tgz que encontrará en la documentación asociada a la
primera práctica.
4. Desarchive ficherosPr1.tgz con la siguiente orden:
$ tar xzvf ficherosPr1.tgz
5. Compruebe que se han generado los siguientes ficheros: lsbin, copia1, copia2, copia3,
argumentos y append.
3
3. El intérprete de órdenes como lenguaje de programación
3.1. INTRODUCCION
El shell o intérprete de órdenes de LINUX es un programa ejecutable (como cualquier otro
programa de usuario) que se encuentra disponible en cualquier entorno LINUX y cuyo cometido
es leer órdenes para el sistema operativo, analizarlas y realizar las llamadas al sistema que sean
necesarias para ejecutarlas.
Este intérprete define, al igual que ocurre con cualquier otro intérprete o traductor, un lenguaje
de programación que posee características como:
• Procedimientos, más conocidos como shell scripts
• Palabras y caracteres reservados (también conocidos como metacaracteres)
• Variables
• Estructuras para el control del flujo tipo if, while, etc.
• Manejo de interrupciones
Realmente en un sistema LINUX existen varios intérpretes de órdenes, pudiendo elegir cada
usuario el que prefiera. El intérprete de órdenes más básico es el Bourne shell o sh, que está
disponible en cualquier sistema. Otros intérpretes de órdenes usuales son el Korn shell o ksh, el
shell con sintaxis del tipo de lenguaje C o csh y el Bourne-Again SHell o bash que es totalmente
compatible con el sh al tiempo que incorpora características del ksh y csh.
Esta práctica introducirá las características y la sintaxis del sh, las cuales resultan también
aplicables al ksh y al bash.
3.2. SHELL SCRIPTS
Las órdenes que el shell debe ejecutar pueden ser proporcionadas a través del teclado o como un
programa contenido en un fichero. Se denomina shell script a un fichero que contiene órdenes
para ser ejecutadas por el shell. Por ejemplo, se puede crear un fichero denominado contenido
en el directorio soprac1 con el siguiente contenido:
#!/bin/bash
# contenido
echo El contenido del directorio actual es:
ls
Las líneas que comienzan por el carácter # son comentarios. El resto son sentencias que
ejecutará el shell. En particular, la orden echo sirve para mostrar el mensaje que se le indica a
continuación. La orden ls sirve para obtener un listado de ficheros, que por defecto serán los
ficheros del directorio actual.
Cree el fichero anterior usando un editor de textos. A continuación veremos cómo podemos
ejecutarlo.
3.2.1. Cómo ejecutar un shell script
La ejecución de un fichero puede realizarse de varias formas distintas, veamos aquí las más
habituales y cómo se aplicarían para nuestro ejemplo del fichero ‘contenido’.
• Creando un shell y pasándole el fichero como argumento:
$ sh contenido
• Dando permiso de ejecución al fichero y ejecutándolo como si fuese otra orden:
$ chmod +x contenido
$ ./contenido
4
En este último caso, el shell detecta que se trata de un shell script y al igual que en el caso
anterior crea otro shell para que lo ejecute. La primera línea (#!/bin/bash) indica el tipo de
shell que debe crearse para ejecutar la orden.
Observe que el nombre del fichero shell script que se ha utilizado en la orden anterior es
./contenido y no simplemente contenido. Es decir se le ha indicado al shell que el fichero a
ejecutar se encuentra en el directorio actual de trabajo (directorio “.”). Para no tener que indicar
cada vez dónde se encuentra el fichero a ejecutar (indicando la ruta completa), el shell utiliza la
variable de entorno denominada PATH. Esta variable contiene la lista de nombres de directorios
(separados por el carácter “:”) donde el shell buscará los ficheros ejecutables.
Podemos consultar el valor de esta variable ejecutando la orden:
$ echo $PATH
Compruebe si el valor de la variable de entorno PATH que está utilizando el shell contiene el
directorio actual de trabajo. En caso afirmativo compruebe que el resultado de ejecutar el
fichero contenido es el mismo utilizando el nombre ./contenido que sólo contenido, y vaya al
apartado 3.2.2.
En caso negativo, debe incluirlo ejecutando la orden:
$ PATH=$PATH:.
Este cambio sólo durará hasta que finalice la ejecución del shell (o sea, se cierre la terminal). Si
desea que el cambio sea permanente edite el fichero de configuración $HOME/.bashrc y añada
al final la orden anterior.
Una vez añadido, abra un nuevo terminal y compruebe que el cambio se ha aplicado
$ echo $PATH
3.2.2. Argumentos en shell scripts
Un shell script puede invocarse con argumentos, los cuales pueden ser referenciados como $0,
$1, $2, $3, etc... El argumento $0 referencia el propio nombre del programa, en tanto que "[email protected]"
referencia todos los argumentos y $# indica el número de argumentos. Esto se ilustra con el
programa argumentos que se proporciona con los ficheros de ésta práctica:
#!/bin/bash
# Programa argumentos.
echo Orden tecleada: $0 "[email protected]"
echo Número de argumentos: $#
echo Nombre de la orden: $0
echo Argumento 1: $1
echo Argumento 2: $2
echo Argumento 3: $3
echo Argumento 4: $4
echo Argumento 5: $5
echo Argumento 6: $6
que, después de darle permisos de ejecución, puede invocarse como:
$ argumentos pera uva limon
Anote el resultado a continuación:
1
5
3.2.3. Estructura de un shell script
Tal y como hemos visto hasta el momento un shell script se compone de sentencias, de órdenes
que nos facilita el shell. Sin embargo, estas órdenes pueden utilizarse junto con otros elementos
para ir realizando poco a poco sentencias más complicadas. Esto será lo que veamos a partir de
este momento en esta sección.
3.3. REDIRECCIONES
Cuando desde el shell ejecutamos un programa, éste dispone de tres canales de comunicación
que permiten que el programa lea o escriba datos a través de ellos. Estos canales son:
-
Salida estándar: canal por el cual el programa escribirá su salida. Por defecto, lo que se
escribe en este canal se vuelca sobre la ventana del shell, de manera que cuando
ejecutamos una orden como ls, su salida aparece en la misma ventana.
-
Salida de error o de diagnóstico: canal que el programa utilizará para escribir
mensajes de error. Por defecto, este canal también se vuelca sobre la ventana del shell.
-
Entrada estándar: canal por el cual el programa puede leer datos de entrada. Por
defecto, este canal recoge lo que se teclee sobre la ventana del shell, permitiendo que el
programa lo pueda leer.
Entrada estándar
$ programa
programa
Salida estándar
Ventana shell
Salida de error
Cada uno de los tres canales puede ser redirigido para que actúe sobre un fichero en vez de
hacerlo sobre la ventana de shell. A continuación veremos cómo podemos hacerlo.
3.3.1. Redirección de la salida estándar
Para redirigir la salida estándar de un programa, la sintaxis es:
$ programa > fichero
Con ello haremos que la salida estándar del programa no se vuelque sobre la ventana, sino que
se guarde en el fichero que le indicamos, tal como vemos en la figura.
6
Entrada estándar
$ programa >fichero
programa
Salida
estándar
Ventana shell
Salida de error
fichero
Para comprobar el uso de este mecanismo, empezaremos ejecutando la orden:
$ ls -l
El argumento -l de la orden ls indica que queremos un listado detallado de los ficheros.
Ahora ejecutaremos la orden redirigiendo su salida :
$ ls -l > dir.dat
La salida de la orden no se ha mostrado en la ventana, sino que se ha almacenado en el fichero
dir.dat. Para comprobarlo, podemos mostrar el contenido del fichero con la orden cat:
$ cat dir.dat
...
Si el fichero usado para la redirección no existía antes de la orden, se crea, y si ya existía, se
reemplazará su contenido. Comprobar ...
$ ls -l > salida.dat
$ cat salida.dat
$ ps > salida.dat
$ cat salida.dat
...
Según esto, podemos usar este método para crear un fichero vacío. Así:
$ > vacio
Si queremos que la salida de un programa se añada al contenido actual de un fichero, usaremos
el símbolo >>. Por ejemplo...
$ ls -l > salida.dat
$ cat salida.dat
$ echo Ultima linea >> salida.dat
¿En qué ha cambiado el contenido del fichero salida.dat tras ejecutar la orden echo?
2
Si queremos que la salida de un programa se “pierda”, es decir, no aparezca ni en la pantalla ni
se almacene en ningún fichero, podemos usar el dispositivo /dev/null, así:
$ ls -l > /dev/null
7
3.3.2. Redirección de la salida de error
De la misma manera que podemos redirigir la salida estándar, es posible almacenar la salida de
error en un fichero, usando 2>.
$ programa 2> fichero
Entrada estándar
$ programa 2>fichero
programa
Salida
de error
Ventana shell
Salida estándar
fichero
Por ejemplo, consideremos la orden
$ cat noexisto.dat salida.dat
Esta orden debe mostrar el contenido de los ficheros noexisto.dat y salida.dat. Dado
que el primer fichero no existe, en su lugar aparecerá un mensaje de error.
Ejecute la orden y observe el resultado. En particular, observe que aparece el mensaje de error.
Compruebe ahora lo que ocurre al ejecutar
$ cat noexisto.dat salida.dat 2> err.dat
¿Qué se muestra por pantalla? ¿Qué ha ocurrido con el mensaje de error?
3
Vuelva ahora a ejecutar la orden anterior, pero redirija esta vez la salida estándar en vez de la de
error (por ejemplo, al fichero sal.dat). ¿Qué se muestra por pantalla?
4
Se puede redirigir simultáneamente las dos salidas (la estándar y la de error) de un programa, de
la forma:
$ programa > fichero1 2> fichero2
8
Entrada estándar
$ programa >fichero1 2>fichero2
programa
Salida
estándar
Ventana shell
Salida
de error
fichero1
fichero2
Para comprobarlo, vuelva a ejecutar la orden anterior redirigiendo tanto la salida estándar como
la de error (cada una a un fichero diferente). ¿Qué se muestra por pantalla?
5
En LINUX, la salida estándar y la de error están numeradas como 1 y 2 respectivamente. Por
esa razón, para redirigir la salida de error se utiliza 2>. Si no se especifica ningún número
delante de > se toma 1 por omisión. Así pues, las siguientes órdenes son equivalentes
$ cat noexisto.dat salida.dat > sal.dat 2> err.dat
$ cat noexisto.dat salida.dat 1> sal.dat 2> err.dat
Si se quiere lanzar la salida de error hacia el mismo fichero que se lanza la salida estándar, se
usa la nomenclatura 2>&1. También se puede usar 1>&2 para añadir la salida estándar a la
salida de error. Por ejemplo, si en la instrucción anterior queremos que tanto la salida estándar
como la salida de error se lancen hacia el mismo el fichero podremos utilizar:
$ cat noexisto.dat dir.dat 1> sal.dat 2>&1
...
3.3.3. Redirección de la entrada estándar
Al igual que se puede redirigir la salida estándar, se puede hacer lo propio con la entrada
estándar.
Para ello usaremos el símbolo <.
$ programa < fichero
9
fichero
Entrada estándar
$ programa <fichero
programa
Salida estándar
Ventana shell
Salida de error
Por ejemplo:
$ tail -3 < salida.dat
La orden tail filtra su entrada, dejando pasar únicamente las últimas líneas (en este ejemplo las
tres últimas líneas).
3.3.4. Tuberías
LINUX también permite usar la salida estándar de un proceso como la entrada estándar de otro,
tal y como si colocáramos una tubería que los uniera. A una secuencia de órdenes enlazadas de
este modo, se le llama tubería (pipeline). Para conectar dos procesos con una tubería, usaremos
el símbolo | de la siguiente forma:
$ programa1 | programa2
La idea se muestra en el siguiente esquema (por claridad se han omitido los canales que no se
ven afectados por la tubería).
Ejecute las siguientes órdenes:
$ ls –l
...
$ ls –l | tail -3
...
En la segunda orden, la salida estándar de ls se usa como entrada estándar de tail, que como
hemos mencionado es un filtro que deja pasar las última líneas (en un apartado posterior se
describen otros ejemplos más de filtros). Es intuitivo pensar que las tuberías nos permiten
construir procesos que operen en un “flujo” de datos. Con el estudio de los “filtros” de LINUX
esta posibilidad se convierte en un recurso muy apreciado. Se pueden conectar varios procesos
en una misma orden.
Ejecute las siguientes órdenes:
$ ls –l
$ ls –l | grep “rwx”
$ ls –l | grep “rwx”
10
| wc
En la segunda orden, la salida de ls -l (contenido detallado del directorio actual) se envía a la
entrada de grep rwx, que selecciona las líneas que contienen la cadena rwx (la función grep
se explica en la sección 3.3).
En la tercera orden se va un paso más allá, enviando la salida de grep a wc, encargado de
mostrar el número de líneas, palabras y caracteres.
Ejercicios
I. Escriba un shell script de nombre ultimasLineas que reciba 2 parámetros: el número de lineas
(N) a mostrar y el fichero del que mostrarlas. El resultado sería mostrar las N últimas líneas
del fichero, tal como se ve en el ejemplo:
$ ultimasLineas 2 salida.dat
Las 2 ultimas lineas del fichero salida.dat son:
Penultima linea
Ultima linea
II. ¿Cómo habría que lanzarlo para que el resultado no saliera por pantalla, sino que se
almacenara en un fichero? ¿y para que si se produjese un error, no se viera ni se almacenase?
3.4. VARIABLES
El shell, como cualquier otro intérprete, permite la utilización de variables que en el caso del
shell son de tipo alfanumérico.
Creación y modificación de variables Una variable se crea asignándole una cadena de
caracteres mediante el operador =. Este operador también permite modificar su valor y debe
utilizarse sin dejar espacios alrededor del símbolo =
$ nombre=Pepe
$ apellido=García
Referencias a variables Las variables se pueden referenciar utilizando el operador $
$ echo $nombre $apellido
Anotar el resultado en la siguiente casilla:
6
3.4.1. Variables y comillas
Las comillas simples se utilizan para que los espacios y otros metacaracteres puedan formar
parte de una cadena:
$ saludo='Hola; que tal !'
$ echo $saludo
Las comillas dobles permiten que el operador $ sea interpretado al asignar una variable:
$ echo ' Alumno: - $nombre $apellido -'
$ echo " Alumno: - $nombre $apellido -"
Anotar el resultado de ambas órdenes en la siguiente casilla:
11
7
Las comillas, simples o dobles, son necesarias cuando se quiere pasar un argumento a un shell
script y este argumento contiene espacios en blanco. Compare los resultados de:
$ argumentos pera uva limon
$ argumentos "pera uva limon"
8
El símbolo $() permite asignar una variable con la salida de una orden. Comprobar la diferencia
entre las siguientes asignaciones.
$
$
$
$
hoy=date
echo $hoy
hoy=$(date)
echo $hoy
9
Si la salida de un mandato contiene espacios en blanco y quiere utilizarse como argumento a un
shell script, de nuevo es necesario el uso de las comillas: Compare los resultados de:
$ argumentos $(date)
$ argumentos "$(date)"
10
3.4.2. Caracteres comodines
A veces es interesante referenciar ficheros que tengan en su nombre características comunes.
“Todos los ficheros que empiezan por la letra c...”. En LINUX esto se consigue utilizando
caracteres especiales (llamados metacaracteres o comodines) que representan otras cosas:
• El carácter asterisco '*' representa a cualquier cadena de caracteres arbitraria incluyendo la
cadena vacía.
• La interrogación '?' representa a cualquier carácter simple.
• Los corchetes '[' ']' pueden contener un grupo o rango de caracteres y corresponden a un
carácter simple.
12
El shell, no los mandatos, interpreta este carácter antes de ejecutar un mandato. Lo sustituye por
los nombres de los ficheros existentes en el directorio actual, y estos nombres son pasados como
argumentos.
Ejemplos: Vamos a utilizar la orden ls, aunque en principio los comodines se pueden aplicar a
cualquier orden.
$
$
$
$
$
$
ls
ls
ls
ls
ls
ls
*.*
copia?
/proc
–d /proc/[1-9]*
–d /proc/[19]*
Todas estas opciones pueden ser combinadas entre sí. El alumno debe probar diferentes
combinaciones y observar los resultados.
Ejercicios
III. Escriba un shell script de nombre fechaSistema que muestre por pantalla el mensaje
siguiente:
$ fechaSistema
La fecha del sistema es XXXXXX
(donde XXXXX es la fecha y hora actuales que se puede obtener con el comando date)
3.5. CONTROL DEL FLUJO
El shell posee sentencias para el control del flujo similares a las existentes en otros lenguajes de
programación, tales como for, if...else, case, while. Los siguientes subapartados describen su
sintaxis e ilustran su utilización con diversos ejemplos.
3.5.1. Bucles con la sentencia for
La sentencia for itera sobre una lista de valores y asigna en cada iteración un valor a la variable
asociada. Su sintaxis es la siguiente1:
for variable in lista de valores
do
sentencias
done
Por ejemplo, se puede utilizar un bucle for para visualizar los ficheros de /usr/bin que empiezan
por a, b y c. En este caso la introducción de la sentencia for se realiza de forma interactiva,
pulsando INTRO al final de cada línea. Observe la aparición del segundo prompt (símbolo >) al
utilizar una sentencia de control que abarca varias líneas.
$
>
>
>
for i in /usr/bin/a* /usr/bin/b* /usr/bin/c*
do
ls $i
done
Los bucles for difieren, fundamentalmente, en la forma en que se proporciona la lista de valores.
A continuación se describen diversas formas de proporcionar esta lista. La lista de valores de un
bucle for puede ser la lista de argumentos de un shell script. Por ejemplo, podemos usar el
fichero ‘lsbin’ (que se adjunta en las prácticas), el cual visualiza los ficheros de /usr/bin que
1
Al igual que las restantes sentencias de control, pueden utilizarse en línea de órdenes o dentro de un shell-script
13
empiecen por cada una de las cadenas alfanuméricas que se proporcionan como argumento a
lsbin. En este caso, la utilización de la sentencia for se basa en el uso de un fichero formado por
líneas con la sintaxis de for. El contenido del mismo es el mostrado en la siguiente casilla y se
puede comprobar su funcionamiento con la orden lsbin ca cu:
for i in "[email protected]"
do
echo PROGRAMAS QUE EMPIEZAN POR /usr/bin/$i
echo --------------------------------------ls /usr/bin/"$i"*
done
Anotar el resultado en la siguiente casilla:
11
La lista de valores de un bucle for también pueden ser los ficheros del directorio actual. Por
ejemplo, el siguiente programa crea una copia de seguridad de cada uno de los ficheros del
directorio actual:
$
>
>
>
>
for k in *
do
cp $k $k.bak
echo Creada copia de $k
done
y el siguiente programa mueve estas copias de seguridad a un subdirectorio denominado bak:
$
$
>
>
>
>
mkdir bak
for j in *.bak
do
mv $j bak/$j
echo Movido $j a bak/$j
done
Finalmente la lista de valores de un bucle for también puede provenir de la ejecución de una
orden mediante el uso de $(). Por ejemplo:
$
>
>
>
for i in $(ls)
do
echo $i
done
Anotar el resultado:
12
Ejercicios
IV. El mandato argumentos siempre muestra los argumentos del 1 al 6, existan o no realmente.
Cree una versión mejorada en un fichero de nombre argumentos2 que muestre sólo los
argumentos suministrados. Por ejemplo:
14
$ argumentos2 pera
Orden tecleada: argumentos pera
Nombre de la orden: argumentos
Hay 1 argumentos y son
pera
$ argumentos2 pera uva limon "manzana golden"
Orden tecleada: argumentos pera uva limon
Nombre de la orden: argumentos
Hay 4 argumentos y son
pera
uva
limon
manzana golden
3.5.2. Ejecución condicional con la sentencia if
La sentencia if permite la ejecución condicional de órdenes. Su sintaxis es:
if orden se ejecuta con éxito
then
sentencias
else
sentencias alternativas
fi
Observe que la condición de la sentencia if no es una expresión sino una orden de LINUX. La
condición es cierta si la orden “termina correctamente”, (en cuyo caso se ejecutan las sentencias
que siguen el then) y falsa en caso contrario (en cuyo caso se ejecutan las sentencias que siguen
el else). La cláusula else es opcional.
Estado de terminación de una orden
Una orden se dice que “termina correctamente” (o con éxito) cuando la orden termina su
ejecución sin dar ningún mensaje de error. En dicho caso, se dice que su estado de terminación
vale 0. El estado de terminación de la última orden ejecutada por el shell se almacena en la
variable de entorno $? Observe su valor en la ejecución de una orden que no termina
correctamente y en una que termina correctamente:
$ cp noexiste fich
...
$ echo $?
$ cp lsbin fich
$ echo $?
El programa copia1 (que se adjunta con las prácticas) ejecuta la orden cp y comprueba su estado
de terminación. Compruébese su funcionamiento, tanto con ficheros existentes como aquellos
que no existan. Su código es el siguiente:
#!/bin/bash
# Programa copia1
if cp $1 $2 2>/dev/null
then
echo " $1 ha sido copiado a $2 "
else
echo " $1 no ha sido copiado a $2 "
fi
La sentencia exit
La sentencia exit fuerza la terminación inmediata de un fichero de órdenes (no se ejecutan las
sentencias que siguen a exit). El estado de terminación del fichero de órdenes puede ser
proporcionado como parámetro de exit. El programa copia2 (que se adjunta con la práctica)
15
completa el programa copia1 para que proporcione un estado de terminación distinto de cero en
el caso en que la orden no funcione correctamente:
#!/bin/bash
# Programa copia2
if cp $1 $2 2>/dev/null
then
echo " $1 ha sido copiado a $2 "
exit 0
else
echo " $1 no ha sido copiado a $2 "
exit 1
fi
Y se puede comprobar con un fichero que no exista, tal como ocurre en la siguiente orden:
$ copia2 noexiste fich
Anotar el resultado de ejecutar echo $? tras la anterior orden en la siguiente casilla:
13
Repetir la misma operación con la siguiente orden:
$ copia2 argumentos fich
Anotar el resultado de ejecutar echo $? tras la anterior orden en la siguiente casilla:
14
3.5.3. La orden test
La orden test permite evaluar condiciones y, por lo tanto, resulta de gran utilidad para utilizarla
conjuntamente con la sentencia if.
Por ejemplo, el programa copia3 (que se adjunta con las prácticas) completa el programa copia2
para que compruebe el número de argumentos:
#!/bin/bash
# Programa copia3
if test $# -ne 2
then
echo Uso de copia: copia f1 f2
echo Observe que copia necesita dos argumentos
exit 1
fi
Y se puede comprobar con la siguiente orden:
$ copia3 f1
Anotar el resultado de ejecutar echo $? tras la anterior orden en la siguiente casilla:
16
15
Los tipos de expresiones que puede evaluar la orden test son los siguientes:
Expresiones numéricas. La forma general de las expresiones es:
N <primitiva> M
donde N y M son interpretados como valores numéricos. Las primitivas que se pueden utilizar
son:
-eq
-ne
-gt
-lt
-ge
-le
N y M son iguales.
N y M son distintos.
N es mayor que M.
N es menor que M.
N es mayor o igual que M.
N es menor o igual que M.
Un ejemplo de utilización podría ser el siguiente:
$
$
>
>
ficheros=$(ls -l | grep $USER | wc -l)
if test $ficheros -gt 2
then echo "Más de 2 ficheros del usuario"
fi
Expresiones alfanuméricas.
Sean S y R cadenas alfanuméricas. Podemos tener dos tipos de expresiones:
<primitiva> S
S <primitiva> R
Las primitivas que se pueden utilizar son:
S=R
S != R
-z S
-n S
las cadenas S y R son iguales.
las cadenas S y R son distintas.
comprueba si la cadena S tiene longitud cero.
comprueba si la cadena S tiene una longitud distinta de cero.
Tipos de ficheros. La forma general de las expresiones es:
<primitiva> fichero
Las primitivas que se pueden utilizar son:
-s
-f
-d
-r
-w
-x
comprueba que el fichero existe y no está vacío.
comprueba que el fichero existe y es regular (no directorio).
comprueba si el fichero es un directorio.
comprueba si el fichero tiene permiso de lectura.
comprueba si el fichero tiene permiso de escritura.
comprueba si el fichero tiene permiso de ejecución.
Para comprobar su utilización, realizaremos el siguiente script llamado ejec:
LISTADO=$(ls $1* 2>/dev/null)
17
if test -z "$LISTADO"
then
echo "No existe ningún fichero que empiece por $1"
else
echo "Existe algún fichero que empieza por $1"
fi
Y cuyo resultado se puede comprobar con las siguientes órdenes:
$ ejec noexiste
...
$ ejec c
...
Anotar los resultados:
16
Operadores ’and’ y ’or’. Se pueden combinar varios operadores en una sola orden test
mediante los operadores -o y -a que representan el “o lógico” e “y lógico” respectivamente. A
continuación se ofrece un ejemplo de utilización, que llamaremos append (se adjunta con la
práctica) cuyo contenido se muestra en la casilla y cuya ejecución se puede probar con la orden
append numerado a1:
if test -w $2 -a -r $1
then cat $1 >> $2
else echo "No se puede añadir $1 a $2"
fi
Ejercicios
V. El mandato copia3 no funciona correctamente si los nombres de fichero suministrados
contienen espacios en blanco. Por ejemplo, copia3 argumentos "argumentos 2" falla.
Averigüe qué produce el fallo y cree una versión mejorada de copia3 que corrija el problema.
VI. Teclee la siguiente orden y anote el número de PID del proceso creado por el shell para
ejecutarla:
$ emacs mifichero&
Sin cerrar la ventana que habrá aparecido, pase al ejercicio siguiente.
VII. Explore el directorio /proc y observe que existen varios subdirectorios cuyo nombre es un
número. Ese número se corresponde con el PID de los procesos que existen en el sistema.
Explore uno de ellos, por ejemplo el directorio del proceso creado en el paso anterior,
prestando especial atención a los ficheros status y cmdline.
VIII. Escriba un shell script en un fichero de nombre vermandatos que muestre por pantalla los
mandatos que ejecutan todos los procesos del sistema. Concretamente, vermandatos debe
recorrer todos los subdirectorios del directorio /proc que comiencen por un número y para
cada uno mostrar el nombre absoluto del subdirectorio y el contenido del fichero de nombre
cmdline que contiene el mandato que ejecuta el proceso, por ejemplo:
/proc/10023
/proc/10025
18
ls-l
cpfich1fich2
IX. Escriba un shell script en un fichero de nombre vermandatos2. Este shell script realiza las
mismas funciones que el shell script vermandatos, excepto cuando el mandato de un proceso
sea la cadena vacía. En este caso debe mostrar en pantalla el texto "SIN MANDATO". Por
ejemplo:
/proc/10023
/proc/10025
/proc/10028
ls-l
cpfich1fich2
SIN MANDATO
4. Filtros a utilizar
A continuación se describen algunas órdenes que resultan útiles para el trabajo con ficheros y
que se combinan con el uso de los mecanismos de redirección y tuberías. Estos filtros utilizan
algunos ficheros de ejemplo para actuar sobre ellos. Inicialmente tales ficheros no se encuentran
en su directorio de trabajo.
4.1. GREP
Este mandato permite seleccionar las líneas procedentes de la entrada que contienen cierto
patrón. El formato general es:
$ grep [opciones] patrón fichero1 fichero2 ….
Por ejemplo, podemos extraer de un fichero las líneas que nos interesan:
$ grep $USER salida.dat
USER es una variable que contiene el usuario con el que te has conectado, con lo cual el
resultado de este grep, será el listado de los archivos de los que eres propietario.
Anote el resultado de la orden en la siguiente casilla.
17
4.2. AWK
Awk es una herramienta del sistema LINUX que es útil para modificar archivos, buscar y
transformar bases de datos, generar informes simples, etc., Puesto que es un lenguaje de
programación las posibilidades de awk son muy grandes, pero este manual pretende ilustrar su
uso más básico.
La función básica de awk es buscar líneas en ficheros u otras unidades de texto que contienen
ciertos patrones. Cuando en una línea se encuentra un patrón, awk realiza las acciones
especificadas para dicho patrón sobre dicha línea. Awk sigue realizando el procesamiento de las
líneas de entrada de esta forma hasta que llega al fin de fichero.
4.2.1. Sintaxis
Awk se puede invocar de diferentes formas, pero en cualquier caso hay que indicar qué patrones
buscamos, qué acciones queremos realizar sobre las líneas que contengan dichos patrones y de
dónde tomamos las líneas de entrada. Los patrones y acciones se le indican mediante un
programa.
19
Hay varias formas de invocar a awk. Si el programa es corto, es más fácil incluir el programa en
la misma orden que ejecuta awk, de la siguiente manera:
$ awk 'programa' fichero_de_entrada_1 fichero_de_entrada_2 …
Este formato indica al shell que ejecute awk y use programa para procesar las líneas de los
ficheros de entrada fichero_de_entrada_1 fichero_de_entrada_2 …
Cuando el programa es largo es más conveniente poner el programa en un fichero y ejecutarlo
de la siguiente manera:
$ awk –f prog-file fichero_de_entrada_1 fichero_de_entrada_2 …
en este caso prog-file es un fichero que contiene el programa.
Un programa en awk consiste en una serie de reglas. Cada regla especifica un patrón a buscar, y
una acción a realizar cuando se encuentre dicho patrón en el registro de entrada.
Sintácticamente, una regla consiste en un patrón seguido por una acción. La acción se encierra
entre llaves para separarla de los patrones
patrón { acción }
patrón { acción }
…
patrón { acción }
Patrones
En los patrones pueden utilizarse expresiones regulares encerradas entre barras diagonales /. La
expresión regular más simple es una secuencia de letras, números o ambos. Por ejemplo, la
expresión regular root casa con cualquier cadena que contenga root. Por lo tanto, el patrón /root/
casa con cualquier registro que contenga la cadena root.
Acciones
La acción da lugar a que algo suceda cuando un patrón concuerda. Si no se especifica una
acción, awk supone {print}. Esta acción copia el registro (suele ser una línea) del fichero de
entrada en la salida estándar. La instrucción print puede ir seguida de argumentos haciendo que
awk imprima sólo los argumentos.
A continuación se muestra un ejemplo básico de la utilización de awk.
$ awk
'/root/ { print $0 }' /etc/passwd
Esta línea realiza lo siguiente: Busca dentro del fichero /etc/passwd la cadena de caracteres root,
y mediante la instrucción print $0 imprime por pantalla la línea donde se ha encontrado dicha
cadena. Anotar el resultado en la siguiente casilla:
18
4.2.2. Dividiendo la entrada en registros y campos
La entrada en el programa awk típico se lee en unidades llamadas registros, y éstos son
procesados por las reglas uno a uno. Por defecto, cada registro es una línea del fichero de
entrada.
20
El lenguaje awk divide sus registros de entrada en campos. Por defecto los campos son
separados por espacios en blanco. El modo en que awk divide los registros de entrada en
campos es controlada por el separador de campos, el cual es un carácter simple o una expresión
regular. Awk recorre el registro de entrada en búsqueda de coincidencias del separador de
campos; los campos son el texto que se encuentra entre dichos separadores de campos
encontrados. Por ejemplo, si el separador de campo es ‘:’, entonces la siguiente línea:
ferbear:x:2598:2000:Fernando Berenguer Aranda:/home/alumnos/ferbear:/bin/bash
será particionada en 7 campos: 'ferbear', 'x', '2598', '2000', 'Fernando Berenguer Aranda',
'/home/alumnos/ferbear' y '/bin/bash'.
El separador de campos puede ser fijado en la línea de órdenes usando el argumento ‘-F’. Por
ejemplo:
$ awk –F: 'programa' ficheros_de_entrada
Fija como separador de campos el carácter ':'.
Para referirse a un campo en un programa awk, se utiliza el símbolo $ seguido por el número del
campo. Por lo tanto $1 se refiere al primer campo, $2 al segundo y así sucesivamente. $0 es un
caso especial, representa el registro de entrada completo.
Otro ejemplo, si escribiéramos:
$ awk –F: '/model name/ {print $2}' /proc/cpuinfo
Se imprimiría el segundo campo separado por ‘:’ de las líneas que contuvieran model name del
fichero /proc/cpuinfo.
4.2.3. Los patrones especiales BEGIN y END
BEGIN y END son patrones especiales. Se utilizan para suministrar al awk qué hacer antes de
empezar a procesar y después de haber procesado los registros de entrada. Una regla BEGIN se
ejecuta una vez, antes de leer el primer registro de entrada. Y la regla END una vez después de
que se hayan leído todos los registros de entrada. Por ejemplo:
$ awk 'BEGIN
{ print "Análisis de ´rwx´" ; rwxbar=0 ; }
> /rwx/ { ++rwxbar }
> END {print "´rwx´ aparece " rwxbar " veces " }' salida.dat
Este programa averigua cuantas veces aparece la cadena ´rwx´ en el fichero de entrada
salida.dat. La regla BEGIN imprime un título para el informe e inicializa el contador rwxbar a
0, aunque no habría necesidad ya que awk lo hace por nosotros automáticamente.
La segunda regla incrementa el valor de la variable rwxbar cada vez que se lee de la entrada un
registro que contiene el patrón ´rwx´. La regla END imprime el valor de la variable al final de la
ejecución.
4.2.4. La sentencia IF
La sentencia if-else es una sentencia para la toma de decisiones de awk. Presenta la siguiente
forma:
if (condición) cuerpo-then [else cuerpo-else]
donde condición es una expresión que controla qué es lo que realizará el resto de la sentencia.
Si la condición es verdad, entonces se ejecuta el cuerpo-then; sino se ejecuta cuerpo-else
21
(asumiendo que esté presente la cláusula else). La parte else de la sentencia es opcional. La
condición se considera falsa si su valor es 0 o una cadena nula, sino se considerará verdadera.
Aquí se presenta un ejemplo:
$
>
>
>
awk '{ if ($0 % 2 == 0)
print $0 "es par"
else
print $0 "es impar" }'
En este ejemplo, si la expresión $0%2==0 es cierta (es decir, el valor que se le pasa es
divisible entre 2), entonces se ejecuta la primera sentencia print, sino se ejecuta la segunda
sentencia print.
Si el else aparece en la misma línea que el cuerpo-then, y el cuerpo-then no es una
sentencia compuesta (no aparece entre llaves) entonces un punto y coma debe separar el cuerpothen del else. Para ilustrar esto veamos el siguiente ejemplo.
$ awk '{ if ($0 % 2 == 0) print $0 "es par"; else print $0 "x es
impar"}'
Si se olvida el ´;´ provocará un error de sintaxis. Se recomienda no escribir la sentencia else de
esta forma, ya que puede llevar al lector a confusión.
4.3. TR
Este mandato permite cambiar o traducir los caracteres procedentes de la entrada de acuerdo a
reglas que se especifican. El formato general es:
$ tr [opciones] cadena_1 cadena_2
Ejemplos de utilización de este mandato son:
•
Para cambiar un carácter por otro: por ejemplo, el utilizado como separador entre campos de
un archivo (‘:’) con otro (por ejemplo, el tabulador):
$ tr : '\t' < nom_fich_entrada
•
Para cambiar un conjunto de caracteres: para poner en mayúsculas todos los caracteres
que aparecen en un archivo:
$ tr '[a-z]' '[A-Z]' < nom_fich_entrada
•
Sustituir los caracteres nulos que contiene un fichero por blancos:
$ cat nom_fich_entrada | tr "\000" " " > nom_fich_salida
Ejercicios
X. Escriba un shell script en un fichero de nombre vermandatos3. Este shell script realiza las
mismas funciones que el shell script vermandatos2, pero muestra el mandato de forma más
legible. En el fichero cmdline las palabras están separadas por el carácter nulo, por lo que
aparecen juntas en pantalla. vermandatos3 debe usar la orden tr para reemplazar el carácter
nulo por el espacio en blanco. Ejemplo de salida:
/proc/10023
/proc/10025
/proc/10028
22
ls -l
cp fich1 fich2
SIN MANDATO
XI. Escriba un shell script en un fichero de nombre infoproc que muestre por pantalla información
relativa a un proceso del sistema. infoproc requiere un argumento en el que se indicará el
identificador del proceso del que se desea obtener información y muestra en pantalla los
identificadores del propietario y del grupo propietario de dicho proceso. Esta información
puede extraerla del fichero de nombre status que se encuentra en el directorio de cada
proceso, accediendo al primer valor numérico de las líneas que comienzan por Uid y Gid.
Ejemplo de uso:
$ infoproc 10023
8991 9112
XII. Escriba un shell script en un fichero de nombre sistema que muestre por pantalla información
relativa a todos los procesos del sistema. Concretamente, sistema debe recorrer todos los
subdirectorios del directorio /proc que comiencen por un número y para cada uno realizar lo
siguiente:
• Asignar a una variable llamada PID el identificador del proceso. Esta información puede
extraerla del fichero de nombre stat que se encuentra en el directorio de cada proceso,
accediendo al primer campo del fichero.
• Asignar a una variable llamada ESTADO el estado del proceso. Esta información puede
extraerla del fichero de nombre status que se encuentra en el directorio de cada proceso,
accediendo a la línea que comienza por State.
• Asignar a una variable llamada ORDEN la orden que está ejecutando el proceso. Esta
información deberá extraerla del fichero de nombre cmdline que se encuentra en el
directorio de cada proceso.
• Visualizar por la salida estándar una cadena que contenga el PID del proceso, su estado
y la orden (en formato legible) que ejecuta el proceso. Por ejemplo:
1585 (sleeping) emacs mifichero
23