3.3 Lección 2
Certificación: |
Linux Essentials |
---|---|
Versión: |
1.6 |
Tema: |
3 El poder de la línea de comandos |
Objetivo: |
3.3 Convertiendo comandos en un script |
Lección: |
2 de 2 |
Introducción
En la última sección, hemos utilizado este sencillo ejemplo para demostrar el Bash scripting:
#!/bin/bash # A simple script to greet a single user. if [ $# -eq 1 ] then username=$1 echo "Hello $username!" else echo "Please enter only one argument." fi echo "Number of arguments: $#."
-
Todos los scripts deben comenzar con un shebang, que define la ruta del intérprete.
-
Todos los scripts deben incluir comentarios para describir su uso.
-
Este script en particular funciona con un argumento, que se pasa al script cuando se llama.
-
Este script contiene una instrucción if que prueba las condiciones de una variable incorporada
$#
. Esta variable se establece en el número de argumentos. -
Si el número de argumentos pasados al script es igual a 1, entonces el valor del primer argumento se pasa a una nueva variable llamada
username
y el script imprime un saludo al usuario. De lo contrario, se muestra un mensaje de error. -
Finalmente, el script imprime de la cantidad de argumentos. Esto es útil para la depuración.
Este es un ejemplo útil para empezar a explicar algunas de las otras características de Bash scripting.
Códigos de salida
Notarás que nuestro script tiene dos estados posibles: imprime "Hello <user>!"
o bien imprime un mensaje de error; lo que es bastante normal para muchas de nuestras utilidades principales. Esto es bastante normal para muchas de nuestras utilidades principales. Considere cat
con el que sin duda se ha estado familiarizando mucho.
Comparemos un uso exitoso de cat
con una situación donde este falla.
Un recordatorio de que nuestro ejemplo anterior es un script llamado new_script.sh
.
$ cat -n new_script.sh 1 #!/bin/bash 2 3 # A simple script to greet a single user. 4 5 if [ $# -eq 1 ] 6 then 7 username=$1 8 9 echo "Hello $username!" 10 else 11 echo "Please enter only one argument." 12 fi 13 echo "Number of arguments: $#."
Este comando tiene éxito, y notará que la bandera -n
también tiene números de línea impresos. Estos son muy útiles para depurar scripts, pero por favor tenga en cuenta que no son parte del script.
Ahora vamos a comprobar el valor de una nueva variable incorporada $?
. Por ahora, sólo hay que revisar la salida:
$ echo $? 0
Ahora consideremos una situación en la que cat
fallará, primero veremos un mensaje de error, y luego comprobaremos el valor de $?
.
$ cat -n dummyfile.sh cat: dummyfile.sh: No such file or directory $ echo $? 1
La explicación de este comportamiento es el siguiente: cualquier ejecución de la utilidad cat
devolverá un código de salida (exit code). Un código de salida nos dirá si el comando tuvo éxito o presentó un error. Un código de salida de cero indica que el comando se completó con éxito; esto es cierto para casi todos los comandos de Linux con los que trabaje. Cualquier otro código de salida indicará un error de algún tipo. El código de salida del último comando para ejecutar se almacenará en la variable $?
.
Considere un script en el que podamos estar copiando archivos a una unidad de red remota, ya que hay muchas maneras en las que la tarea de copia puede haber fallado: por ejemplo, nuestra máquina local puede no estar conectada a la red, o la unidad remota puede estar llena, y al comprobar el código de salida de nuestra utilidad de copia, podemos alertar al usuario de problemas al ejecutar el script.
Es una buena práctica implementar códigos de salida, así que lo haremos ahora, tenemos dos caminos en nuestro guión, un éxito y un fracaso, usemos el cero para indicar el éxito y el otro para indicar el fracaso.
1 #!/bin/bash 2 3 # A simple script to greet a single user. 4 5 if [ $# -eq 1 ] 6 then 7 username=$1 8 9 echo "Hello $username!" 10 exit 0 11 else 12 echo "Please enter only one argument." 13 exit 1 14 fi 15 echo "Number of arguments: $#."
$ ./new_script.sh Carol Hello Carol! $ echo $? 0
Note que el comando echo
en la línea 15 fue ignorado por completo, usando exit
terminará el script inmediatamente, por lo que esta línea nunca se encontrará.
Manejando Muchos Argumentos
Hasta ahora nuestro script sólo puede manejar un único nombre de usuario a la vez, cualquier número de argumentos además de uno causará un error, vamos a explorar cómo podemos hacer que este script sea más versátil.
El primer instinto de un usuario puede ser usar más variables posicionales como $2
, $3
, etc. Desafortunadamente, no podemos anticipar el número de argumentos que un usuario puede elegir usar.
Modificaremos la lógica de nuestro script, ya que al no tener ningún argumento debería causar un error, pero cualquier otro número de argumentos debería tener éxito, este nuevo script se llamará friendly2.sh
.
1 #!/bin/bash 2 3 # a friendly script to greet users 4 5 if [ $# -eq 0 ] 6 then 7 echo "Please enter at least one user to greet." 8 exit 1 9 else 10 echo "Hello $@!" 11 exit 0 12 fi
$ ./friendly2.sh Carol Dave Henry Hello Carol Dave Henry!
Hay dos variables integradas que contienen todos los argumentos pasados al script: $@
y $*
. En su mayor parte, ambos se comportan igual. Bash analizará sintácticamente los argumentos y separará cada argumento cuando encuentre un espacio entre ellos.
En efecto, el contenido de $@
se ve así:
|
|
|
|
|
|
Si está familiarizado con otros lenguajes de programación, puede reconocer este tipo de variables como un arreglo (array). Las matrices en Bash pueden crearse simplemente poniendo espacio entre elementos como la variable FILES
en el script arraytest
como a continuación:
FILES="/usr/sbin/accept /usr/sbin/pwck/ usr/sbin/chroot"
Contiene una lista de muchos artículos, lo que hasta ahora no es muy útil, ya que todavía no hemos introducido ninguna forma de manejar estos artículos individualmente.
Bucles (for loop)
Veamos el ejemplo arraytest
que se muestra antes. Si recuerdas, en este ejemplo estamos especificando una matriz propia llamada ARCHIVOS
. Lo que necesitamos es una forma de “desempaquetar” esta variable y acceder a cada valor individual, uno tras otro. Para hacer esto, utilizaremos una estructura llamada bucles (for loop), que está presente en todos los lenguajes de programación. Hay dos variables a las que nos referiremos: una es el rango y la otra es para el valor individual en el que estamos trabajando actualmente. Este es el script en su totalidad:
#!/bin/bash FILES="/usr/sbin/accept /usr/sbin/pwck/ usr/sbin/chroot" for file in $FILES do ls -lh $file done
$ ./arraytest lrwxrwxrwx 1 root root 10 Apr 24 11:02 /usr/sbin/accept -> cupsaccept -rwxr-xr-x 1 root root 54K Mar 22 14:32 /usr/sbin/pwck -rwxr-xr-x 1 root root 43K Jan 14 07:17 /usr/sbin/chroot
Si te refieres de nuevo al ejemplo de friendly2.sh
, puedes ver que estamos trabajando con un rango de valores contenidos dentro de una sola variable $@
. Para mayor claridad, llamaremos a esta última variable username
:
1 #!/bin/bash 2 3 # a friendly script to greet users 4 5 if [ $# -eq 0 ] 6 then 7 echo "Please enter at least one user to greet." 8 exit 1 9 else 10 for username in $@ 11 do 12 echo "Hello $username!" 13 done 14 exit 0 15 fi
Recuerda que la variable que definas aquí puede llamarse como quieras, y que todas las líneas dentro de do…. done
se ejecutarán una vez para cada elemento del array:
$ ./friendly2.sh Carol Dave Henry Hello Carol! Hello Dave! Hello Henry!
Ahora supongamos que queremos hacer que nuestra producción parezca un poco más humana, queremos que nuestro saludo sea en una sola línea.
1 #!/bin/bash 2 3 # a friendly script to greet users 4 5 if [ $# -eq 0 ] 6 then 7 echo "Please enter at least one user to greet." 8 exit 1 9 else 10 echo -n "Hello $1" 11 shift 12 for username in $@ 13 do 14 echo -n ", and $username" 15 done 16 echo "!" 17 exit 0 18 fi
Un par de notas:
-
Usando
-n
conecho
suprimirá la nueva línea después de imprimir, lo que significa que todos los ecos se imprimirán en la misma línea, y la nueva línea se imprimirá sólo después de!
en la línea 16. -
El comando
shift
eliminará el primer elemento de nuestro array, por lo que esto es así:
|
|
|
|
|
|
Se convierte en esto:
|
|
|
|
Observemos la salida:
$ ./friendly2.sh Carol Hello Carol! $ ./friendly2.sh Carol Dave Henry Hello Carol, and Dave, and Henry!
Uso de expresiones regulares para realizar la comprobación de errores
Es posible que queramos verificar todos los argumentos que el usuario está introduciendo, por ejemplo, quizás queramos asegurarnos de que todos los nombres pasados a friendly2.sh
contienen sólo letras, y cualquier carácter o número especial causará un error, para ello usaremos grep
.
Recordemos que podemos usar expresiones regulares con grep
.
$ echo Animal | grep "^[A-Za-z]*$" Animal $ echo $? 0
$ echo 4n1ml | grep "^[A-Za-z]*$" $ echo $? 1
El ^
y el $
indican el principio y el final de la línea respectivamente, el [A-Za-z]
indica un rango de letras, mayúsculas o minúsculas, el *
es un cuantificador, y modifica nuestro rango de letras de manera que estamos haciendo coincidir el cero con muchas letras. En resumen, nuestro grep
tendrá éxito si la entrada es solo letras, y fallará si fuera lo contrario.
Lo siguiente que hay que tener en cuenta es que grep
es devolver los códigos de salida en función de si hubo una coincidencia o no, una coincidencia positiva devuelve 0
, y una no coincidencia devuelve 1
. Podemos usar esto para probar nuestros argumentos dentro de nuestro script.
1 #!/bin/bash 2 3 # a friendly script to greet users 4 5 if [ $# -eq 0 ] 6 then 7 echo "Please enter at least one user to greet." 8 exit 1 9 else 10 for username in $@ 11 do 12 echo $username | grep "^[A-Za-z]*$" > /dev/null 13 if [ $? -eq 1 ] 14 then 15 echo "ERROR: Names must only contains letters." 16 exit 2 17 else 18 echo "Hello $username!" 19 fi 20 done 21 exit 0 22 fi
En la línea 12, estamos redirigiendo la salida estándar a /dev/null
, la cual es una forma sencilla de suprimirla. No queremos ver ninguna salida del comando grep
, sólo queremos probar su código de salida en lo que ocurre en la línea 13. Note también que estamos usando un código de salida de 2
para indicar un argumento inválido, por lo general es una buena práctica usar diferentes códigos de salida para indicar diferentes errores; de esta manera, un usuario experto puede usar estos códigos de salida para resolver problemas.
$ ./friendly2.sh Carol Dave Henry Hello Carol! Hello Dave! Hello Henry! $ ./friendly2.sh 42 Carol Dave Henry ERROR: Names must only contains letters. $ echo $? 2
Ejercicios guiados
-
Lea el contenido de
script1.sh
a continuación:#!/bin/bash if [ $# -lt 1 ] then echo "This script requires at least 1 argument." exit 1 fi echo $1 | grep "^[A-Z]*$" > /dev/null if [ $? -ne 0 ] then echo "no cake for you!" exit 2 fi echo "here's your cake!" exit 0
¿Cuál es el resultado de estos comandos?
-
./script1.sh
-
echo $?
-
./script1.sh cake
-
echo $?
-
./script1.sh CAKE
-
echo $?
-
-
Lea el contenido del archivo
script2.sh
:for filename in $1/*.txt do cp $filename $filename.bak done
Describa el propósito de este script tal como lo entiende.
Ejercicios exploratorios
-
Cree una secuencia de comandos que tome cualquier número de argumentos del usuario e imprima solo aquellos argumentos que sean números mayores a 10.
Resumen
En esta lección usted aprendió:
-
¿Qué son los códigos de salida, que significan y como implementarlos?
-
¿Cómo verificar el código de salida de un comando?
-
¿Qué son los bucles
for
y como usarlos con matrices? -
¿Cómo usar
grep
, expresiones regulares y códigos de salida para verificar la entrada del usuario en los scripts?
Comandos utilizados en los ejercicios:
shift
-
Esto eliminará el primer elemento de una matriz..
Variables especiales:
$?
-
Contiene el código de salida del último comando ejecutado.
$@
,$*
-
Contiene todos los argumentos pasados al script, como una matriz.
Respuestas a los ejercicios guiados
-
Lea el contenido de
script1.sh
a continuación:#!/bin/bash if [ $# -lt 1 ] then echo "This script requires at least 1 argument." exit 1 fi echo $1 | grep "^[A-Z]*$" > /dev/null if [ $? -ne 0 ] then echo "no cake for you!" exit 2 fi echo "here's your cake!" exit 0
¿Cuál es el resultado de estos comandos?
-
comando:
./script1.sh
Salida:
This script requires at least 1 argument.
-
comando:
echo $?
Salida:
1
-
comando:
./script1.sh cake
Salida:
no cake for you!
-
comando:
echo $?
Salida:
2
-
comando:
./script1.sh CAKE
Salida:
here’s your cake!
-
comando:
echo $?
Salida:
0
-
-
Lea el contenido del archivo
script2.sh
:for filename in $1/*.txt do cp $filename $filename.bak done
Describa el propósito de este script tal como lo entiende.
Este script realizará copias de seguridad de todos los archivos que terminen con
.txt
en un subdirectorio definido en el primer argumento.
Respuestas a los ejercicios exploratorios
-
Cree una secuencia de comandos que tome cualquier número de argumentos del usuario e imprima solo aquellos argumentos que sean números mayores a 10.
#!/bin/bash for i in $@ do echo $i | grep "^[0-9]*$" > /dev/null if [ $? -eq 0 ] then if [ $i -gt 10 ] then echo -n "$i " fi fi done echo ""