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 "$@" 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 "$@" 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 "$@" 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
© Copyright 2024