aula:linux_para_novatos:programacion_en_shell
Diferencias
Muestra las diferencias entre dos versiones de la página.
aula:linux_para_novatos:programacion_en_shell [2021/02/26 13:29] – creado admin | aula:linux_para_novatos:programacion_en_shell [2025/01/22 02:02] (actual) – editor externo 127.0.0.1 | ||
---|---|---|---|
Línea 1: | Línea 1: | ||
+ | Fuente de informacion | ||
+ | http:// | ||
+ | |||
+ | En este artículo se explica cómo escribir pequeños scripts de shell utilizando muchos ejemplos. | ||
+ | ====== ¿Por qué la programación en shell? ====== | ||
+ | Incluso existiendo varios interfaces gráficos disponibles para Linux el shell sigue siendo una herramienta muy eficiente. El shell no es sólo una colección de comandos sino un buen lenguaje de programación. Se pueden automatizar muchas tareas con él, el shell es muy útil para tareas de administración, | ||
+ | Por lo tanto veamos como funciona: | ||
+ | ====== Creando un script ====== | ||
+ | |||
+ | Existen muchos shells disponibles para Linux pero habitualmente se utiliza el **bash** (bourne again shell) para la programación en shell ya que está disponible libremente y es fácil de usar. Por lo tanto todos los scripts que escribamos en este artículo usarán el **bash**. | ||
+ | Para escribir nuestros programas en shell usaremos cualquier clase de editor de texto, p.ej. gedit, kedit, emacs, vi. | ||
+ | |||
+ | Todos los programa deben empezar con la siguiente línea: | ||
+ | |||
+ | #!/bin/sh | ||
+ | |||
+ | Lo caracteres **#!** indican al sistema que el primer argumento que sigue en la linea es el programa a utilizar para ejecutar este fichero. En este caso usamos el shell **/ | ||
+ | Cuando hallas escrito y guardado tu script debes hacerlo ejecutable para poder usarlo. | ||
+ | |||
+ | Para hacer el script ejecutable escribe | ||
+ | chmod +x nombre_del_fichero | ||
+ | Después puedes ejecutar tu script escribiendo: | ||
+ | ./ | ||
+ | ===== Comentarios ===== | ||
+ | |||
+ | Los comentarios en la programación en shell comienzan con # se prolongan hasta el final de la línea. Te recomendamos que uses comentarios. Si tienes comentarios y no usas un cierto script durante algún tiempo inmediatamente sabrás qué hace y cómo funciona. | ||
+ | ===== Variables ===== | ||
+ | |||
+ | Como en otros lenguajes de programación no se puede vivir sin variables. En la programación en shell todas las variables son de tipo string y no es necesario declararlas. Para asignar un valor a una variable se escribe: | ||
+ | |||
+ | nombre_de_la_variable=valor | ||
+ | |||
+ | Para obtener el valor de la variable simplemente hay que poner el signo del dólar delante de la variable: | ||
+ | |||
+ | #!/bin/sh | ||
+ | # asignamos un valor: | ||
+ | a=" | ||
+ | # ahora mostramos el contenido de " | ||
+ | echo "A es:" | ||
+ | echo $a | ||
+ | |||
+ | Escribe estas líneas en tu editor de textos y guardalo p.ej. como **primero**. Después haz ejecutable el script escribiendo en el shell | ||
+ | chmod +x primero | ||
+ | y después ejecútalo escribiendo | ||
+ | ./primero | ||
+ | El script mostrará lo siguiente: | ||
+ | |||
+ | A es: | ||
+ | hola mundo | ||
+ | |||
+ | Si necesitas manejar expresiones matemáticas entonces necesitas usar programas como **expr** (ver tabla abajo). | ||
+ | Además de las variables de shell normales que son únicamente válidas dentro del programa en shell hay también **variables de entorno**. | ||
+ | |||
+ | Una variable precedida de la palabra clave **export** es una variable de entorno. | ||
+ | ====== Comandos de shell y estructuras de control ====== | ||
+ | |||
+ | |||
+ | Hay tres categorías de comandos que pueden ser usados en scripts de shell: | ||
+ | ===== Comandos Unix ===== | ||
+ | |||
+ | |||
+ | Aunque un script de shell puede hacer uso de cualquier comando unix aquí tienes varios comandos utilizados con más frecuencia que el resto. Estos comandos se decriben como comandos para la manipulación de ficheros y texto. | ||
+ | |||
+ | ^Comando^Sintaxis del comando^Explicación y funcionamiento| | ||
+ | ^echo|echo "texto cualquiera" | ||
+ | ^ls| ls|listar ficheros| | ||
+ | ^wc |wc -l \\ wc -w fichero \\ wc -c fichero| Contar las líneas de un fichero | ||
+ | ^cp|cp fichero_origen fichero_destino|copiar el fichero_origen al fichero_destino| | ||
+ | ^mv|mv nombre_antiguo nuevo_nombre|renombrar o mover un fichero| | ||
+ | ^rm|rm fichero |borrar un fichero| | ||
+ | ^grep|grep ' | ||
+ | ^cut|cut -b num_de_columna fichero|sacar datos de columnas de texto de ancho definido \\ Ejemplo: sacar los caracteres de la posición 5 a la 9 \\ cut -b5-9 fichero.txt| | ||
+ | ^cat|cat fichero.txt|mostrar un fichero.txt por stdout (tu pantalla)| | ||
+ | ^file|file fichero|describir qué tipo de fichero es un_fichero| | ||
+ | ^read|read var |esperar a que el usuario introduzca algo y guardarlo | | ||
+ | ^sort|sort fichero.txt |ordenar las líneas del fichero fichero.txt| | ||
+ | ^uniq|.|borrar las líneas duplicadas, usado en combinación | ||
+ | ^expr|.|hacer operaciones matemáticas en el shell \\ Ejemplo: sumar 2 y 3 \\ expr 2 " | ||
+ | ^find|find fichero|buscar ficheros \\ Ejemplo: buscar por nombre: \\ find . -name nombre_del_fichero -print| | ||
+ | ^ tee|// " | ||
+ | ^basename|basename fichero|devolver el nombre del fichero dado un nombre y quitarle la ruta al directorio \\ Ejemplo: basename /bin/tux \\ devuelve sólo tux| | ||
+ | ^dirname |dirname fichero |devolver el nombre del directorio dado un nombre y \\ quitándole el nombre del fichero \\ Ejemplo: dirname /bin/tux \\ devuelve /bin| | ||
+ | ^head |head fichero | mostrar algunas líneas del principio de un fichero| | ||
+ | ^tail| tail fichero |mostrar algunas líneas del final de un fichero| | ||
+ | ^sed | |sed es básicamente un programa para buscar y reemplazar caracteres. \\ Lee el texto de la entrada estándar \\ (p.ej. una tubería) y escribe el resultado por stdout (normalmente la pantalla). \\ \\ El patrón de búsqueda es una expresión regular. \\ Este patrón de búsqueda de se debe confundir con la sintaxis de comodines (wildcard) del shell. \\ \\ Ejemplo, para reemplazar la cadena hola con HOLA en un fichero de texto usaremos: \\ //cat texto.fichero | sed ' | ||
+ | ^awk | |La mayoría de las veces awk se utiliza para extraer campos de una línea de un texto. \\ El campo separador por defecto es un espacio. \\ Para especificar uno diferente se usa la opción -F. \\ \\ Ejemplo: \\ //cat |fichero.txt | awk -F, ' | ||
+ | |||
+ | ===== Conceptos: ===== | ||
+ | |||
+ | Tuberías (pipes), redirecciones y comillas simples invertidas | ||
+ | Realmente no son comando pero son conceptos muy importantes. | ||
+ | ==== tuberías (pipes) (|) ==== | ||
+ | |||
+ | envian la salida (stdout) de un programa a la entrada (stdin) de otro. | ||
+ | grep " | ||
+ | encuentra las líneas con la cadena hola en fichero.txt y después cuenta el número de líneas. | ||
+ | La salida del comando grep se usa como entrada del comando wc. De esta manera se pueden contatenar tantos comandos como se quiera (dentro de unos límites razonables). | ||
+ | ==== redirecciones ==== | ||
+ | |||
+ | escribe la salida de un comando a un fichero o añade datos a un fichero | ||
+ | * escribe la salida a un fichero y sobreescribe el fichero antiguo en caso de existir | ||
+ | * añade datos a un fichero (o crea uno nuevo si no existía previamente pero nunca sobreescribe nada). | ||
+ | ==== Comillas simples invertidas (o tilde invertida) ==== | ||
+ | |||
+ | La salida de un comando se puede usar como argumento de línea de comandos (no stdin como anteriormente, | ||
+ | |||
+ | find . -mtime -1 -type f -print | ||
+ | |||
+ | El comando encuetra todos los ficheros que han sido modificados en las últimas 24 horas (-mtime -2 serían 48 horas). | ||
+ | |||
+ | Si quisieramos empaquetar estos fichero en un archivo tar (fichero.tar) la sintaxis sería: | ||
+ | |||
+ | tar xvf fichero.tar fichero1 fichero2 ... | ||
+ | |||
+ | En vez de escribirlo todo se pueden combinar los dos comandos (find y tar) usando comillas simples invertidas. Entonces tar empaquetará todos los ficheros que muestre find: | ||
+ | |||
+ | #!/bin/sh | ||
+ | # Las comillas son comillas simples invertidas (`) no comillas normales ('): | ||
+ | tar -zcvf ultimodific.tar.gz `find . -mtime -1 -type f -print` | ||
+ | ===== 3) Estructuras de control ===== | ||
+ | |||
+ | |||
+ | La sentencia " | ||
+ | |||
+ | if ....; then | ||
+ | .... | ||
+ | elif ....; then | ||
+ | .... | ||
+ | else | ||
+ | .... | ||
+ | fi | ||
+ | |||
+ | La mayoría de las veces se utiliza un comando muy especial dentro de las sentencias **if**. Se puede usar para comparar cadena o comprobar si un fichero existe, tiene permiso de lectura etc... | ||
+ | El comando " | ||
+ | |||
+ | [ -f " | ||
+ | [ -x "/ | ||
+ | [ -n " | ||
+ | [ " | ||
+ | |||
+ | Ejecuta el comando "**man test**" | ||
+ | Usarlo en scripts de shell es bastante directo: | ||
+ | |||
+ | #!/bin/sh | ||
+ | if [ " | ||
+ | echo "tu shell es el bash (bourne again shell)" | ||
+ | else | ||
+ | echo "tu shell no es bash sino $SHELL" | ||
+ | fi | ||
+ | |||
+ | La variable $SHELL contiene el nombre de la shell donde estás y esto es lo que estamos comprobando aquí comparandola con la cadena "/ | ||
+ | |||
+ | ==== Operadores cortos ==== | ||
+ | |||
+ | A aquellos que conozcan C les gustará la siguiente expresión: | ||
+ | |||
+ | [ -f "/ | ||
+ | |||
+ | Los **&& | ||
+ | |||
+ | "El fichero /etc/shadow existe **Y** (**AND**) se ejecuta el comando **echo**" | ||
+ | |||
+ | También está disponible el operador **O** (**OR**) (**||**). Un ejemplo: | ||
+ | |||
+ | #!/bin/sh | ||
+ | mailfolder=/ | ||
+ | [ -r " | ||
+ | echo " | ||
+ | grep "^From " $mailfolder | ||
+ | |||
+ | El script comprueba primero si puede leer un buzón de correo. Si puede entonces imprime las líneas " | ||
+ | |||
+ | Sencillamente este código lo leeríamos como " | ||
+ | * -mostrar un mensaje de error | ||
+ | * -salir del programa | ||
+ | Para manejar ambos como un solo comando podemos agruparlos en una función anónima usando llaves. | ||
+ | Puedes hacerlo todo sin **O** e **Y** usando simplemente sentencias **if** pero algunas veces los operadores **O** e **Y** son mucho más convenientes. | ||
+ | |||
+ | La sentencia **case** se puede usar para comparar (usando comodines del shell como *** y ?)** una cadena dada con varias posibilidades. | ||
+ | |||
+ | case ... in | ||
+ | ...) hacer algo aquí;; | ||
+ | esac | ||
+ | |||
+ | Veamos un ejemplo. El comando file comprueba que tipo de fichero es el fichero que le pasamos: | ||
+ | |||
+ | file lf.gz | ||
+ | |||
+ | devuelve: | ||
+ | |||
+ | lf.gz: gzip compressed data, deflated, original filename, | ||
+ | last modified: Mon Aug 27 23:09:18 2001, os: Unix | ||
+ | |||
+ | Ahora vamos a usar esto para escribir un script llamado // | ||
+ | |||
+ | #!/bin/sh | ||
+ | tipofichero=`file " | ||
+ | case " | ||
+ | "$1: Zip archive" | ||
+ | unzip " | ||
+ | "$1: gzip compressed" | ||
+ | gunzip " | ||
+ | "$1: bzip2 compressed" | ||
+ | bunzip2 " | ||
+ | *) error "El fichero $1 no se puede descomprimir con smartzip";; | ||
+ | esac | ||
+ | |||
+ | |||
+ | Te habrás fijado que hemos usado una nueva variable especial llamada $1. Esta variable contiene el primer argumento pasado a un programa. Digamos que ejecutamos | ||
+ | smartzip articles.zip | ||
+ | entonces $1 contendría la cadena articles.zip | ||
+ | |||
+ | La sentencia **select** es una extensión específica del bash y es muy útil para usos interactivos. El usuario puede escoger una opción de una lista de difirentes valores: | ||
+ | |||
+ | select var in ... ; do | ||
+ | break | ||
+ | done | ||
+ | .... ahora podemos usar $var .... | ||
+ | |||
+ | Un ejemplo: | ||
+ | |||
+ | #!/bin/sh | ||
+ | echo " | ||
+ | select var in " | ||
+ | break | ||
+ | done | ||
+ | echo "Has seleccionado $var" | ||
+ | |||
+ | Aquí tienes lo que haría el programa: | ||
+ | |||
+ | ¿Cuál es tu sistema operativo favorito? | ||
+ | 1) Linux | ||
+ | 2) Gnu Hurd | ||
+ | 3) Free BSD | ||
+ | 4) Otros | ||
+ | #? 1 | ||
+ | Has seleccionado Linux | ||
+ | ==== sentencias de bucle ==== | ||
+ | while ...; do | ||
+ | .... | ||
+ | done | ||
+ | |||
+ | El bucle **while** se ejecutará mientras la expresión que comprobamos sea verdadera. Se puede usar la palabra clave " | ||
+ | |||
+ | El bucle **for** toma una lista de cadenas (separadas por espacios) y las asigna a una variable: | ||
+ | |||
+ | for var in ....; do | ||
+ | .... | ||
+ | done | ||
+ | |||
+ | El siguente ejemplo muestra por pantalla de la letra A a la C: | ||
+ | |||
+ | #!/bin/sh | ||
+ | for var in A B C ; do | ||
+ | echo "var es $var" | ||
+ | done | ||
+ | |||
+ | Un script de ejemplo más útil es este, llamado // | ||
+ | |||
+ | #!/bin/sh | ||
+ | # listar un resumen del contenido de una serie de paquetes RPM | ||
+ | # USO: showrpm ficherorpm1 ficherorpm2 ... | ||
+ | # EJEMPLO: showrpm / | ||
+ | for rpmpackage in $*; do | ||
+ | if [ -r " | ||
+ | echo " | ||
+ | rpm -qi -p $rpmpackage | ||
+ | else | ||
+ | echo " | ||
+ | fi | ||
+ | done | ||
+ | |||
+ | Como se puede ver hemos usado la siguente variable especial, $* que contiene todos los argumentos de la línea de comandos. Si ejecutas | ||
+ | showrpm openssh.rpm w3m.rpm webgrep.rpm | ||
+ | entonces $* contiene las tres cadenas openssh.rpm, | ||
+ | |||
+ | El bash GNU también entiende bucles until pero en general con los bucles while y for es suficiente. | ||
+ | ==== Comillas ==== | ||
+ | Antes de pasarle cualquier argumento a un programa el shell intenta expandir los comodines y variables. | ||
+ | |||
+ | Expandir significa que el comodín (p.ej. *****) se reemplaza por los nombres de fichero apropiados o que una variable se reemplaza por su valor. Para modificar este comportamiento se pueden usar las comillas: Pongamos que tenemos varios ficheros en el directorio actual. Dos de ellos son ficheros jpg, correo.jpg y tux.jpg. | ||
+ | |||
+ | #!/bin/sh | ||
+ | echo *.jpg | ||
+ | |||
+ | Imprimirá " | ||
+ | Usar comillas (simples y dobles) evitará esta expansión de comodines: | ||
+ | |||
+ | #!/bin/sh | ||
+ | echo " | ||
+ | echo ' | ||
+ | |||
+ | Esto mostrará " | ||
+ | Las comillas simples son las más estrictas. Evitan incluso la expansión de variables. Las comillas dobles evitarán la exprasión de comodines pero no así la de variables: | ||
+ | |||
+ | #!/bin/sh | ||
+ | echo $SHELL | ||
+ | echo " | ||
+ | echo ' | ||
+ | |||
+ | Esto mostrará: | ||
+ | |||
+ | /bin/bash | ||
+ | /bin/bash | ||
+ | $SHELL | ||
+ | |||
+ | Finalmente existe la posibilidad de eliminar el significado especial de un único caracter anteponiendole una barra invertida: | ||
+ | |||
+ | echo \*.jpg | ||
+ | echo \$SHELL | ||
+ | |||
+ | Esto mostrará: | ||
+ | *.jpg | ||
+ | $SHELL | ||
+ | ==== Documentación aquí ==== | ||
+ | Documentación aquí es una forma muy elegante de enviar varias líneas de texto a un comando. Es bastante útil para escribir un texto de ayuda en un script sin tener que poner echo delante de cada línea. | ||
+ | |||
+ | Un " | ||
+ | |||
+ | #!/bin/sh | ||
+ | # tenemos menos de 3 argumentos. Mostramos el texto de ayuda: | ||
+ | if [ $# -lt 3 ] ; then | ||
+ | cat << | ||
+ | ren -- renombra varios ficheros usando expresiones regulares de sed | ||
+ | | ||
+ | USO: ren ' | ||
+ | |||
+ | EJEMPLO: rename all *.HTM fles in *.html: | ||
+ | ren ' | ||
+ | | ||
+ | AYUDA | ||
+ | exit 0 | ||
+ | fi | ||
+ | VIEJO=" | ||
+ | NUEVO=" | ||
+ | # El comando shift elimina un argumento de la lista | ||
+ | # de argumentos de la linea de comandos. | ||
+ | shift | ||
+ | shift | ||
+ | # ahora $* contiene todos los ficheros: | ||
+ | for fichero in $*; do | ||
+ | if [ -f " | ||
+ | nuevofichero=`echo " | ||
+ | if [ -f " | ||
+ | echo " | ||
+ | else | ||
+ | echo " | ||
+ | mv " | ||
+ | fi | ||
+ | fi | ||
+ | done | ||
+ | |||
+ | Hasta ahora este es el script más complejo que hemos visto. Hablemos sobre él un poco. | ||
+ | |||
+ | La primera sentencia if comprueba si hemos introducido al menos 3 parámetros de línea de comandos. (La variable especial $# contiene el número de argumentos). Si no, se envia el texto de ayuda al comando cat que consecuentemente lo muestra por pantalla. Después de mostrar el texto de ayuda salimos del programa. | ||
+ | |||
+ | Si hay 3 o más argumentos asignamos el primer argumento a la variable VIEJO y el segundo a la variable NUEVO. | ||
+ | |||
+ | Lo siguiente es cambiar la posición de los parámetros de la lista de comandos dos veces para poner el tercero en la primera posición de $*. Con $* entramos en el bucle for. Cada uno de los argumentos de $* se asigna uno a uno a la variable $fichero. Aquí comprobamos primero que el fichero existe y después creamos un nuevo fichero usando buscar y reemplazar con sed. | ||
+ | |||
+ | Las comillas simples invertidas se usan para asignar el resultado a la variable nuevofichero. Ya tenemos todo lo que necesitamos: | ||
+ | ==== Funciones ==== | ||
+ | Tan pronto como tengamos un programa más complejo nos encontraremos con que estamos usando el mismo código en varias partes y que sería de más ayuda el darle cierta estructura. Una función es similar a esto: | ||
+ | |||
+ | nombredelafuncion() | ||
+ | { | ||
+ | # dentro del cuerpo $1 es el primer argumento dado a la función | ||
+ | # $2 el segundo ... | ||
+ | | ||
+ | } | ||
+ | |||
+ | Se necesita " | ||
+ | |||
+ | Aquí tenemos un script llamado // | ||
+ | |||
+ | #!/bin/sh | ||
+ | # vim: set sw=4 ts=4 et: | ||
+ | | ||
+ | ayuda() | ||
+ | { | ||
+ | cat << | ||
+ | xtitlebar -- cambiar el nombre de un xterm, gnome-terminal o konsole de kde | ||
+ | | ||
+ | USO: xtitlebar [-h] " | ||
+ | | ||
+ | OPCIONES: -h texto de ayuda | ||
+ | | ||
+ | EJEMPLO: xtitlebar " | ||
+ | | ||
+ | AYUDA | ||
+ | exit 0 | ||
+ | } | ||
+ | | ||
+ | # en caso de error o si le pasamos -h llamar a la función ayuda: | ||
+ | [ -z " | ||
+ | [ " | ||
+ | | ||
+ | # enviamos la secuencia de escape para cambiar el título de la ventana: | ||
+ | echo -e " | ||
+ | # | ||
+ | |||
+ | Se recomienda siempre documentar detalladamente el código de los scripts. Con esto posibilitamos que otras personas (y tú) puedan usar y entender el script. | ||
+ | ==== Argumentos de línea de comandos ==== | ||
+ | |||
+ | Hemos visto que $* y $1, $2 ... $9 contienen los argumentos que el usuario especificó en la línea de comandos (las cadenas escritas detrás del nombre del programa). Hasta ahora sólo hemos tratado con sintaxis de linea de comandos bastante sencilla y corta (un par de argumentos obligatorios y la opción -h para ayuda). Pero pronto descubrirás que necesitas algún tipo de analizador para programas más complejos donde defines tus propias opciones. | ||
+ | |||
+ | Por convención se dice que los parámetros opcionales deben estar precedidos por un signo menos y deben ir antes de otros argumentos (como p.ej. nombres de ficheros). | ||
+ | |||
+ | Hay muchas posibilidades para implementar un analizador (**parser**). El bucle **while** siguiente combiando con una sentencia case es una solución muy buena para un analizador genérico: | ||
+ | |||
+ | #!/bin/sh | ||
+ | ayuda() | ||
+ | { | ||
+ | cat << | ||
+ | Esta es la demostración de un analizador de línea de comandos genérico. | ||
+ | EJEMPLO DE USO: cmdparser -l hola -f -- -fichero1 fichero2 | ||
+ | AYUDA | ||
+ | exit 0 | ||
+ | } | ||
+ | while [ -n " | ||
+ | case $1 in | ||
+ | -h) ayuda;shift 1;; # llamamos a la función ayuda | ||
+ | -f) opt_f=1; | ||
+ | -l) opt_l=$2; | ||
+ | --) shift; | ||
+ | -*) echo " | ||
+ | *) break;; | ||
+ | esac | ||
+ | done | ||
+ | | ||
+ | echo "opt_f es $opt_f" | ||
+ | echo "opt_l es $opt_l" | ||
+ | echo "el primer argumento es $1" | ||
+ | echo "el 2º argumento es $2" | ||
+ | | ||
+ | ¡Pruébalo! Puedes ejecutarlo con p.ej.: | ||
+ | |||
+ | cmdparser -l hola -f -- -fichero1 fichero2 | ||
+ | |||
+ | Produce | ||
+ | |||
+ | opt_f es 1 | ||
+ | opt_l es hola | ||
+ | el primer argumento es -fichero1 | ||
+ | el 2º argumento es fichero2 | ||
+ | |||
+ | ¿Cómo funciona? Basicamente se realiza un bucle a través de todos los argumentos y los compara con la sentencia **case**. Si encuentra alguna coincidencia establece una variable y cambia la línea de comandos en uno. Por convención en unix las opciones (lo que tiene delante un signo menos) deben estar primero. Puedes indicar que han finalizado las opciones escribiendo dos signos menos (**--**). Lo necesitarás p.ej. con grep para buscar una cadena que comience con el signo menos: | ||
+ | |||
+ | Buscar la cadena -xx- en el fichero f.txt: | ||
+ | grep -- -xx- f.txt | ||
+ | |||
+ | Nuestro analizador de opciones también puede manejar los **--** como se puede comprobar en el listado superior. | ||
+ | |||
+ | --- // |