###### tags: `ASO-GISI`
# Programación de shell scripts para la Bash
---

[TOC]
---
## Introducción
La shell es 1)una potente interfaz con el sistema operativo basada en línea de comandos y 2)un interprete de scripts. Un shell script es un programa realizado en el lenguaje de la shell. Dicho de una forma sencilla y generalizando, un shell script es un fichero de texto que contiene órdenes que también podrían ser ejecutadas desde la línea de comandos.
Los shell script son muy usados en la administración de los sistemas ya que permiten, con una sola orden, ejecutar múltiples comandos. Los scripts se utilizan frecuentemente para automatizar tareas. Ejemplos de tareas automatizables son la realización de copias de seguridad, la comprobación del espacio libre, la rotación de ficheros de log, etc. Cuando estas tareas se realizan de forma periódica su ejecución además se suele programar utilizando cron (lo veremos en el próximo Laboratorio).
## La shell
Hay múltiples implementaciones de shell: Bourne shell (sh), Bourne-Again shell (bash), Debian Almquist shell (dash), C shell (csh), Z shell (zsh), etc. En Ubuntu, sh era la shell por defecto para el arranque del sistema, aunque desde hace años esa labor ha pasado a manos de dash (para minimizar los cambios, ahora en Ubuntu /bin/sh es un enlace simbólico a dash). Para los usuarios, la shell por defecto es bash (la shell de inicio de un usuario concreto se encuentra en /etc/passwd y se puede establecer en el momento en que se crea un usuario, con posibilidad de modificarla posteriormente).
Para saber qué shell se está usando (ya sea en un terminal, ya sea en un script) se recomienda utilizar la orden:
```bash
readlink -f /proc/$$/exe
```
Más adelante explicaremos el significado de esta línea.
:::info
Esta línea funciona en Linux y varias otras versiones de UNIX, pero no en todas (en particular, no funciona en macOS).
:::
## Ejecución de un shell script
Como se ha dicho, los shell script son ficheros de texto, por lo cual se pueden crear con cualquier editor: vi, nano, gedit, etc.
Empecemos con el siguiente ejemplo:
```bash
#!/bin/bash
# Programa: escribe3.sh
# Propósito: Escribe tres veces el parámetro pasado
# Autor: Javier Macías
# Fecha: 12/11/2019
echo $1 $1 $1
exit 0
```
El símbolo `#` como ya conocemos indica el comienzo de comentario (puede ir en cualquier parte de la línea, pero si va precedido de una orden debe ir separado por un blanco). El comentario finaliza con el final de línea.
En el ejemplo, la primera línea, aunque parece un comentario, tiene un significado especial. `#!/bin/bash` se llama shebang (el nombre viene del sonido de las dos primeras letras de #, SHarp, y de ! que se denomina bang en la cultura hacker). Esta línea dice a la shell que lo ejecuta qué intérprete de comandos usar de no especificarse uno concreto para su ejecución. En este primer caso, es la `bash` quien ejecutaría ese script, pero podría haber sido cualquier otro intérprete, por ejemplo:
```bash
#!/usr/bin/python3
# Programa: holapy.py
# Proposito: Hola mundo en python
# Autor: Oscar Garcia
# Fecha: 12/11/2019
print("Hola mundo, soy Python!")
```
*Nota: El shebang es optativo (pero muy recomendable) y debe aparecer siempre en la primera línea. Por otro lado, Linux no usa las extensiones de los ficheros para saber su tipo, aunque es recomendable su utilización (así además evitamos que el nombre del script coincida con alguna orden de Linux). Para saber el tipo de fichero Linux utiliza los "números mágicos". En el caso de los script, que son ficheros de texto, el shebang ayuda a reconocerlos.*
Ahora podemos ejecutar el primer programa, por ejemplo, especificando que sea con bash:
```bash
$ bash escribe3.sh Hola
Hola Hola Hola
```
También podríamos haber utilizado dash:
```bash
$ dash escribe3.sh Hola
Hola Hola Hola
```
Se puede hacer que sea la propia shell actual la que ejecute el script (con lo cual, por ejemplo, se puede hacer que el script varíe las variables de la shell actual) mediante la orden `source` (que también se puede abreviar como `.` ):
```bash
$ source escribe3.sh Hola #comentar antes la línea exit 0 ¿sabes por qué?
Hola Hola Hola
$ . escribe3.sh Hola #comentar antes la línea exit 0 ¿sabes por qué?
Hola Hola Hola
```
Para hacer que el script se ejecute con la shell especificada en el shebang, el fichero tiene que tener como mínimo permisos de lectura y ejecución:
```bash
$ chmod 755 escribe3.sh
```
Ahora ya podemos llamarlo directamente desde la línea de comando:
```bash
$ ./escribe3.sh Hola
Hola Hola Hola
```
Es necesario especificar la ruta del fichero porque, por razones de seguridad, Linux no busca por defecto el fichero en el directorio actual (solo lo hace en los directorios que se encuentran en la variable de entorno PATH).
:::info
Ejercicio: Explica qué ocurre aquí:
```bash
$ chmod 755 holapy.py
$ bash holapy.py
holapy.py: line 7: syntax error near unexpected token `"Hola mundo, soy Python!"'
holapy.py: line 7: `print("Hola mundo, soy Python!")'
$ ./holapy.py
Hola mundo, soy Python!
```
:::
:::spoiler (solución)
El error que aparece en la primera llamada al script es debido a que estamos intentando ejecutar un script Phyton con bash. En la segunda, como no especificamos ninguna shell y existe un shebang, se utiliza éste (/usr/bin/python3) para su ejecución, obteniéndose el resultado esperado.
:::
Para depurar scripts, las shell disponen de diversas opciones. Por ejemplo, bash tiene entre otras `-x` (*Print commands and their arguments as they are executed*) y `-v` (*Print shell input lines as they are read*). Estas opciones se pueden incluir en el shebang y/o en la ejecución explícita con bash. Ejemplo
```bash
$ bash -x escribe3.sh Hola
+ echo Hola Hola Hola
Hola Hola Hola
```
### Paso de argumentos a un script
Los argumentos pasados en la línea de órdenes a un script se almacenan en unas variables especiales (las variables las vimos en el laboratorio pasado) denominadas parámetros. Estos parámetros son posicionales y vienen identificados por un número, correspondiendo el 1 al 1º y así sucesivamente. Para su expansión se usa, como para las variables, el símbolo `$`. Por ejemplo `$i` (con i entre 1 y 9) es la expansión del i-esimo parámetro. Para valores de i mayores de 9 hay que usar obligatoriamente las llaves: `${i}`
La shell también dispone de varios parámetros especiales que pueden ser de utilidad en un script. Son de solo lectura (se les ha puesto precedidos ya de `$` para su expansión):
+ `$0` Nombre del fichero shell script tal y como se puso en la llamada (incluyendo path si se usó).
+ `$#` Número de parámetros posicionales pasados a un script.
+ `$*` Todos los parámetros posicionales. Si se pone entre comillas, `"$*"`, equivale a `"$1 $2..."`
+ `$@` Todos los parámetros posicionales. Si se pone entre comillas, `"$@"`, equivale a `"$1" "$2"...`
+ `$?` Resultado (exit status) de la última orden.
+ `$$` PID de la shell.
:::info
Como se dijo al inicio, con el siguiente comando se puede conocer la shell que se está ejecutando:
```bash
readlink -f /proc/$$/exe
```
Tal y como viene en el documento "La consola Linux", `/proc` es un directorio virtual que contiene información de los procesos y `$$` se expande al PID de la shell actual, siendo `exe` un enlace simbólico al nombre con la ruta de la orden (en este caso, de la shell). Como ya se conoce, con `readlink -f` obtenemos el nombre "final" del fichero/orden ejecutada, siguiendo recursivamente todos los enlaces simbólicos que pudiera haber.
:::
### Devolución del resultado de la ejecución del script
Como en el caso de las órdenes, cuando se ejecuta un script también se va a devolver un valor (*exit status*) que indica si el resultado de la ejecución ha sido correcto (0) o ha habido algún problema (>0). Este valor se puede especificar mediante la orden `exit i`, que termina el script y devuelve un exit status `i` (o 0 si no se especifica `i`). En caso de finalizar el script con otra orden, el exit status sería el correspondiente a esa útima orden.
## Declaración de variables
Al igual que en la línea de comando, en un script se pueden definir variables. Como se dijo, éstas pueden ser "locales" (existen solo en la shell donde se han declarado) o "globales" o de entorno, que son además accesibles a todas las subshell que se ejecuten desde la shell en la que se han declarado.
Por ejemplo, sean los siguientes script:
```bash
#!/bin/bash
# Programa: varPrimero.sh
# Propósito: Crea una var. local y otra de entorno, las
# muestra y llama al script varSegundo
# Autor: Javier Macías
# Fecha: 14/11/2020
varLocal=1
export VAR_ENTORNO=2
echo "varPrimero.sh: varLocal=$varLocal VAR_ENTORNO=$VAR_ENTORNO"
./varSegundo.sh
#!/bin/bash
# Programa: varSegundo.sh
# Propósito: Muestra dos variables. Es llamado
# por varPrimero.sh
# Autor: Javier Macías
# Fecha: 14/11/2020
echo "varSegundo.sh: varLocal=$varLocal VAR_ENTORNO=$VAR_ENTORNO"
```
Razónese el resultado obtenido por las siguientes órdenes:
```bash
$ ./varPrimero.sh
varPrimero.sh: varLocal=1 VAR_ENTORNO=2
varSegundo: varLocal= VAR_ENTORNO=2
$ echo $varLocal $VAR_ENTORNO
$ source ./varPrimero.sh
varPrimero.sh: varLocal=1 VAR_ENTORNO=2
varSegundo.sh: varLocal= VAR_ENTORNO=2
$ echo $varLocal $VAR_ENTORNO
1 2
```
::: spoiler (Solución)
Al invocar varPrimero.sh con `source`, dicho script se ejecuta en la propia shell por lo cual se puede acceder al contenido de ambas variables.
:::
## Lecturas interactivas
En bash se pueden hacer lecturas interactivas usando la orden interna `read`, que además puede escribir, con la opción `-p`, un prompt o mensaje previo. Además se puede especificar la variable donde se guardará la entrada del usuario (de no especificarse, se guarda en la variable REPLY)
Por ejemplo:
```bash
$ read -p "¿Edad? " edad
¿Edad? 20
$ echo $edad
20
```
Hay otras opciones interesantes, como `-s` (de **silence** que hace que la entrada de teclado no se muestre por pantalla), `-n X` que hace que la lectura sea como máximo de *X* caracteres (si se llega al tope sin pulsar intro, se termina la lectura y se pasa a la siguiente orden) y `-u fd` que hace que lea del descriptor de fichero *fd* en vez de la entrada estándar. Un uso común de `-sn` se muestra a continuación:
```bash
$ read -sn 1 -p "Pulsa una tecla para terminar"
```
## Estructuras condicionales
En shell scripts es posible alterar el camino de ejecución en función del valor de retorno de una instrucción cualquiera. Por ejemplo:
```bash
if mkdir carpeta &>/dev/null
then
echo Acabo de crear una carpeta
else
echo No puedo puedo crear carpeta porque ya existe así que la borraré
rm -Rf carpeta
fi
```
Es importante tener esto en cuenta: lo que evalúa `if` no es una condición booleana sino el resultado (*exit status*) de ejecutar una instrucción. Es fácil olvidarse de esto dado que, en la mayoría de los lenguajes de programación, la clausula `if` evalúa una expresión lógica que puede ser `true` o `false`.
:::info
Ejercicio: escribir un script que reciba como argumento único un nombre de directorio, (por ejemplo, paco). El script debe verificar si existe algún usuario que tenga como directorio home /home/*argumento* (en este caso, /home/paco) y emitir un mensaje por stdout indicando si existe o no. Se recuerda que el directorio home de cada usuario se especifica en el archivo `/etc/passwd`)
:::
:::spoiler (solución)
Se puede hacer evaluando en un `if` el resultado de hacer `grep ":/home/$1:" /etc/passwd` . Nótese que se se usa `:` porque es el separador de campos en el fichero passwd (si no se usuara, podría encontrar "subcadenas" no deseadas, como /home/pacopepe).
:::
:::info
Ejercicio: En bash existe `true` y `false` pero, ¿qué son?
:::
Si realmente necesitamos hacer comprobaciones lógicas entonces necesitamos una orden que las haga por nosotros: la orden `test`
:::info
NOTA: Aunque aquí estemos hablando de estructuras de control para un fichero de shell script, también se pueden escribir directamente en el prompt de la shell, dejando retornos de carro (y/o usando `;`) donde proceda.
Por ejemplo con retornos de carro quedaría así (los > los pone automáticamente la shell; es el prompt que aparece a partir de la 2ª línea):
**$ if mkdir carpeta &>/dev/null
\> then
\> echo Acabo de crear una carpeta
\> else
\> echo No puedo puedo crear carpeta porque ya existe así que la borraré
\> rm -Rf carpeta
\> fi**
Y usando `;` quedaría (todo en una única línea):
**$ if mkdir carpeta &>/dev/null; then echo Acabo de crear una carpeta; else echo No puedo puedo crear carpeta porque ya existe así que la borraré; rm -Rf carpeta; fi**
:::
### Orden `test`
test es una orden de la shell que evalúa expresiones booleanas simples de ficheros, números y cadenas, devolviendo (**exit status**) 0 en caso verdadero y 1 en caso falso.
```bash
$ test 10 -lt 5 #¿es 10<5?
$ echo $?
1
```
Nota: se pueden usar la notación de corchetes, por ejemplo `[ 10 -lt 5 ]`, donde el corchete de apertura equivale a `test` (por eso es necesario que tras el corchete vaya un espacio en blanco; también se necesita un blanco delante del corchete de cierre).
Algunos argumentos comunes de `test` son (nótese que cada uno es un argumento y que por tanto deben estar separados por espacios):
Comprobación de `test` con ficheros
`-d nombre` *nombre* es un directorio
`-f nombre` *nombre* es un fichero regular
`-r nombre` *nombre* existe y se puede leer
`-w nombre` *nombre* existe y se puede escribir
`-x nombre` *nombre* existe y es ejecutable
`-s nombre` nombre existe y su tamaño no es cero
`f1 -nt f2` *f1* es más reciente que *f2* (nt =>newer than)
`f1 -ot f2` *f1* es más antiguo que *f2* (ot=>older than)
Comprobaciones de `test` con cadenas
`s1 = s1` *s1* igual a *s2*
`s1 != s2` *s1* distinta a *s2*
`-z s` *s* tiene longitud 0
`-n s` *s* tiene longitud mayor que cero
test numéricos (números enteros)
`a -eq b` *a* igual a *b*
`a -ne b` *a* distinto de *b*
`a -gt b` *a* mayor que *b*
`a -ge b` *a* mayor o igual a *b*
`a -lt b` *a* menor que *b*
`a -le b` *a* menor o igual que *b*
Combinación y negación de test
`t1 && t2` And: los test *t1* y *t2* son ciertos. Evalúa en cortocircuito
`t1 || t2` Or: *t1* o *t2* es cierto. Evalúa en cortocircuito
`! t` Niega *t*
`\( \)` Agrupa test
Ejemplo:
```bash
$ test $i -gt 5 && test $i -lt 20 && ! test $i -eq 10
```
:::info
Nótese que para And y Or estamos usando los operadores de control de la shell && y ||. Existen unos operadores específicos de test para And y Or, pero no está recomendado su uso.
:::
### Órdenes `if` / `if else` / `if elif`
La sintaxis de la orden `if` es:
```bash
if condición
then
orden(es)
fi
```
Es muy habitual poner el `then` en la misma línea que el `if`, lo cual requiere un `;`
```bash
if condición; then
orden(es)
fi
```
Como ya se ha visto, es posible también utilizar un `if-else`:
```bash
if condición; then
orden(es)
else
orden(es)
fi
```
Cuando intervienen varias condiciones, es más cómodo usar el `if-elif`:
```bash
if condición1; then
orden(es)
elif condición2; then
orden(es)
elif condición3; then
orden(es)
...
else
orden(es)
fi
```
Ejemplo:
```bash
#!/bin/bash
# Programa: ordenados3.sh
# Propósito: Indica si los tres argumentos pasados están en orden de
# menor a mayor.
# Autor: Javier Macías
# Fecha: 12/11/2019
if test $# -ne 3; then
echo "Número de parámetros erróneo. Uso: $0 arg1 arg2 arg3"
exit 1
fi
if test $1 -le $2 && test $2 -le $3; then
echo "Están ordenados: $1<=$2<=$3"
else
echo "No están ordenados"
fi
exit 0
```
### Orden `case`
Cuando dependiendo del resultado de una expresión haya múltiples opciones, es mejor utilizar `case` en vez de `if`. Su sintaxis es:
```bash
case expresion in
caso1)
orden(es)
;;
caso2)
orden(es)
;;
...
*)
orden(es)
;;
esac
```
Es importante que la última orden de cada caso termine en `;;` y por ello, para que resalte y sea fácil descubrir un olvido, se suele poner en la línea siguiente.
Ejemplo (nótese que en los casos se pueden usar patrones como los que se utilizan en la expansión de nombres de fichero):
```bash
case $1 in
[Yy]es)
echo "Sí"
;;
[Nn]o)
echo "No"
;;
*)
echo "Opción inválida"
;;
esac
```
## Iteración (bucles)
Hay varias instrucciones en la shell que permiten realizar bucles. Se muestran a continuación mediante ejemplos.
### blucle `while`
```bash
i=0
while test $i -lt 10; do
echo $i
i=$((i+1))
done
```
### blucle `until`
```bash
c=0
until test $c -ge 10; do
echo $c
c=$((c+1))
done
```
### bucle `for`
La primera sintaxis del for es muy similar a C:
```bash
for ((i=0; i<10; i++)); do
echo $i
done
```
En su segunda sintaxis, permite el recorrido de listas. Por ejemplo:
```bash
#!/bin/bash
# Programa: colores.sh
# Proposito: Ej. recorrido lista de argumentos
# Autor: Oscar Garcia
# Fecha: 12/11/2019
for color in $*
do
echo "Me gusta el $color"
done
````
Un ejemplo de ejecución sería:
```bash
$ ./colores.sh azul rojo naranja
Me gusta el azul
Me gusta el rojo
Me gusta el naranja
```
Una de las posibles aplicaciones es el recorrido de listas de argumentos cuando dicha lista es muy larga, como por ejemplo por el resultado de la expansión del metacaracter `*`. ¿Qué ocurriría si el script anterior lo invocamos haciendo `./colores.sh /etc/*`?
Las listas se pueden dar también explícitamente (si algún elemento contuviera espacios en blanco habría que entrecomillarlo), por ejemplo:
```bash=
for var in USER HOME PWD; do
echo "Valor de $var= ${!var}"
done
```
Un ejemplo de ejecución del código anterior sería:
```
Valor de USER= jose
Valor de HOME= /home/jose
Valor de PWD= /home/jose/aso2020
```
:::info
En el código anterior se usa el mecanismo de expansión indirecta de una variable/parámetro que vimos en el laboratorio anterior.
:::
### Modificación flujo ejecución de bucles
Se dispone de las órdenes `break` para terminar un bucle y `continue` para forzar la siguiente iteración de un bucle sin terminar la actual.
### Redirección
Es posible redireccionar la salida o entrada de un bucle completo (también de una condicional, pero es menos frecuente).
Por ejemplo, este código generaría un fichero redireccionando la salida de un bucle:
```bash=
#!/usr/bin/bash
# Programa: ficheroAleatorios.sh
# Propósito: Crea un fichero con valores aleatorios
# Uso: ficheroAleatorios.sh numAleatorios nombreFichero
# Autor: Javier Macías
# Fecha: 14/11/2020
numAleatorios=$1
nombreFichero=$2
for ((v=0; v<$numAleatorios; v++)); do
echo $RANDOM #funcion interna devuelve pseudoaleatorio
done > $nombreFichero
```
Y este código redirecciona un fichero a un bucle:
```bash=
#!/usr/bin/bash
# Programa: sumaFichero.sh
# Propósito: Suma los valores contenidos en un fichero
# Uso: sumaFichero.sh nombreFichero
# Autor: Javier Macías
# Fecha: 14/11/2020
suma=0
while read aux; do
suma=$((suma+aux))
done < $1
echo "La suma del fichero $1 es $suma"
```
# Ejercicios:
1. Modifique el programa sumaFichero para que compruebe que recibe solo y un solo parámetro. En caso contrario, escribirá la forma correcta de uso y terminará. Además comprobará que el fichero pasado como parámetro existe y su tamaño es mayor que cero (en caso contrario mostrará un error y terminará).
2. (Resuelva mentalmente este test y luego compruebe su respuesta). Sea el script muestra.sh que contiene solo esta línea:
`echo "$10"`
¿Qué se mostrará en la salida estándar cuando se ejecute lo siguiente?
`$ bash muestra.sh 1 2 3 4 5 6 7 8 9 a b`
a) 9
b) a
c) 10
d) $10
3. Cree un script que muestre por pantalla las letras de la **a** a la **z**, cada una en una línea, esto es:
a
b
c
...
z
Cuando lo haya conseguido, intente hacer lo mismo pero sin necesidad del script y en una sola línea de comando (pista: si no le sale, escriba la estructura de control en el prompt con líneas en blanco como en el script, ejecútelo y luego consulte el comando en la historia de comandos).
4. Crear un shell script llamado gestUsu.sh para añadir y borrar usuarios en el sistema, tal y como se detalla a continuación.
Tendrá como argumento el fichero con las altas y bajas de las cuentas a crear. El script primero validará que el número de argumentos sea correcto (en otro caso mostrará un error al respecto por pantalla así como la forma de uso y terminará) y después comprobará que el fichero exista y tenga un tamaño mayor de 0 (en otro caso mostrará un error al respecto por pantalla y terminará)
Si se pasan las validaciones anteriores, el script procesará el fichero con los usuarios, escribiendo información de lo que va ocurriendo en un fichero de log llamado **/tmp/gestUsu.log** (si existía, se sobreescribirá). El error estándar también se redireccionará a dicho fichero.
Un ejemplo de contenido de fichero de usuarios sería éste:
**$cat fichUsu
#Fichero de mayo
+pepe;José García
+eva;Eva Pérez
+alfonso;Alfonso López
-oscar
#El siguiente usuario quizás no exista
-ana
pedro;Pedro Ruiz**
Las líneas que comienzan por **#** son comentarios y se graban tal cual en el fichero de log.
Las líneas que comienzan por **+** son altas de usuario, y contienen el login que tendrá el usuario y, separado por un **;** el nombre real del usuario. El usuario se creará con las opciones que indican que se le cree el directorio del usuario si no existe y que se añada como comentario el nombre real del usuario. Si la creación es exitosa se añadirá en el fichero una línea similar a:
**Añadido usuario pepe (José García)**
Si no es exitosa, se añadirá una línea similar a:
**Error en línea 4 al añadir al usuario alfonso (Alfonso López)**
Las líneas que comienzan por **–** son bajas de usuario y contienen solo el login del usuario a borrar. El usuario se borrará con la opción que elimina el home del usuario y ficheros hubiera, y correo. Si el borrado es exitoso se añadirá en el fichero de log una línea similar a:
**Borrado usuario oscar**
Si no es exitoso, se añadirá una línea similar a:
**Error en línea 7 al borrar al usuario ana**
Si una línea es errónea (no comienza por ninguno de los tres caracteres vistos), se añadirá al log una línea similar a:
**Línea 8 errónea: pedro;Pedro Ruiz**
Por ejemplo, la ejecución del script con los parámetros fichUsu (el fichero dado como ejemplo) se ejecutaría sin presentar ningún error por pantalla y el contenido del log podría ser el siguiente:
**$ cat /tmp/gestUsu.log
#Fichero de mayo
Añadido usuario pepe (José García)
Añadido usuario eva (Eva Pérez)
useradd: user 'alfonso' already exists
Error en línea 4 al añadir al usuario alfonso (Alfonso López)
userdel: oscar mail spool (/var/mail/usu2) not found
Borrado usuario oscar
#El siguiente usuario quizás no exista
userdel: user 'ana' does not exist
Error en línea 7 al borrar al usuario ana
Línea 8 errónea: pedro;Pedro Ruiz****
Notas:
- Todo lo que necesita para hacer este ejercicio aparece en la documentación de Laboratorio y se recomienda tenerla “estudiada” antes de comenzar el ejercicio.
- Se recomienda no hacer todo el ejercicio de golpe sino ir probando partes poco a poco.
- El script resultante no debería ser demasiado largo (menos de 65 líneas).
- Tenga en cuenta que el fichero con los usuarios utiliza los caracteres **#** y **;** que tienen un significado especial en la shell.
- Recuerde que se requieren permisos de superusuario para crear y borrar usuarios.
- Si se queda atascado en algo y necesita alguna simplicación (por ejemplo, que el separador del fichero sea otro) hágalo y continúe con el resto.
# Bibliografía:
+ The Linux Command Line. Willian Shotts. 2ª ed. No Starch Press.
+ Linux Pocket Guide. Daniel J. Barrett. 3ª ed. O’Reilly.
+ Mastering Linux Shell Scripting. Mokhtar Ebrahim, Andrew Mallett. 2º ed. Packt.