###### tags: `ASO-GISI`
# La *bash*
---

[TOC]
---
## Introducción
La bash es una shell de UNIX/Linux. Una shell es una aplicación que facilita la interacción del usuario con el sistema operativo. Los ordenadores de propósito general y servidores se utilizan y administran generalmente a través de la shell.
Bash está basada en la shell sh (de hecho su nombre procede de “Bourne-Again SHell”, en referencia a Stephen Bourne que fue el creador de sh). Se trata de una shell textual (las hay también de tipo gráfico, como la Windows shell), utilizada por defecto, de muchos tipos de Linux y, hasta hace unos años, era la shell textual de MacOS. Además de bash y sh, existen otras shell para UNIX/Linux como ksh, dash, tsch, zsh, etc.
La bash se ejecuta desde un terminal. Un terminal es un dispositivo, virtual o físico que permite a un operador humano enviar mensajes de texto (generalmente desde un teclado) y recibir información textual de regreso (generalmente por una pantalla; puede incluir también pitidos de “alarma”).
Desde Ubuntu, para abrir una terminal virtual desde la shell gráfica (que por defecto es Gnome Shell) se puede pulsar el icono Terminal o la combinación de teclas CTRL+ ALT+T. Se pueden abrir varios terminales a la vez según sean necesarios, identificándose cada terminal como un dispositivo (virtual) distinto.
Por ejemplo:
```bash
$ ps #desde un terminal
PID TTY TIME CMD
3261 pts/0 00:00:00 bash
5334 pts/0 00:00:00 ps
$ ps #desde un terminal
PID TTY TIME CMD
5355 pts/1 00:00:00 bash
5367 pts/1 00:00:00 ps
```
Como se ve en la columna `PID`, cada terminal está ejecutando una bash (procesos 3261 y 5355). Por otro lado en la columna `TTY` (de *TeleTYpewriter*) se identifica cada uno de los terminales (`pts/0` y `pts/1`). El identificador `pts` viene de Pseudo-Terminal Slave.
Como casi todos los dispositivos en Linux, los terminales (incluidos los virtuales) se manejan con archivos de dispositivos, que cuelgan del directorio `/dev`. Por ejemplo, `pts/0` se corresponde con `/dev/pts/0`:
```bash
$ ls -l /dev/pts
crw--w---- 1 javier tty 136, 0 sep 4 13:38 0
crw--w---- 1 javier tty 136, 1 sep 4 13:34 1
```
Como puede ver, los archivos `0` y `1` son de tipo carácter (a diferencia de los asociados a discos, que son de tipo bloque). Pruebe desde el terminal identificado como `pts/0` a ejecutar lo siguiente:
```bash
$ echo hola > /dev/pts/1
```
## Características de la bash
Muchas características de la bash son comunes a otras shell de Linux:
* Edición de línea, historial de órdenes, autocompletado, etc.
* Ejecución de órdenes internas y órdenes externas.
* Alias.
* Metacaracteres y expansiones.
* Redirecciones de la E/S.
* Estructuración de órdenes en la bash.
* Variables de shell y personalización entorno.
* Funciones.
* Aritmética entera.
* Arrays.
* Proporciona un lenguaje de programación con el que se pueden hacer *scripts*.
Bash se puede utilizar de forma interactiva (con el usuario introduciendo las órdenes por teclado) o no interactiva (en este caso las órdenes suelen darse mediante un *script*). Se pueden ejecutar órdenes externas de forma síncrona (espera hasta que terminen) o asíncrona (en el caso de que se lancen en segundo plano).
## Ejecución de órdenes internas y órdenes externas
**Órdenes internas:** Son nativas de la bash. La orden `$ help` obtiene un listado de las órdenes internas disponibles.
**Órdenes externas:** Son las no nativas. Deben de ser llamadas por el interprete de órdenes para ser ejecutadas. Son todas las que se encuentran accesibles desde la variable de entorno `$PATH`.
Para averiguar si una orden es interna o externa, además de `help` , puede utilizarse `type` y `where`.
```bash
$ type let && whereis let
let is a shell builtin
let:
$ type nano && whereis nano
nano is /usr/bin/nano
nano: /usr/bin/nano /usr/share/man/man1/nano.1
````
Las órdenes, tanto externas como internas, devuelven un valor al finalizar (*exit status*), cuyo valor es 0 si todo fue correcto y distinto de 0 si hubo un error. Existen algunas excepciones como `grep` (véase el manual).
Nótese que la mayoría de órdenes devuelven por pantalla un texto con un error, cuando este ocurre. Así, el usuario puede actuar al efecto. Sin embargo, en el caso de programar un *script*, el manejo de errores textuales es más complejo.
El valor de retorno de la última orden queda almacenada en la variable de shell `?`. Para “acceder” al valor de una variable, como veremos más adelante, se precede de `$`. Por ejemplo:
```bash
$ cd dirNoExistente
bash: cd: dirNoExistente: No such file or directory
$ echo $? #muestra el valor de retorno de la orden anterior (“cd”)
1
$ echo $? #muestra el valor de retorno de la orden anterior (“echo”)
0
```
## Alias
Un alias es un nombre alternativo para una orden.
Imagine por ejemplo que le interesa listar los archivos de un directorio en formato largo con el nombre `ll`. Puede entonces definir un alias (la bash sustituirá el alias por su definición antes de la ejecución):
```bash
$ alias ll='ls -s' # cuidado, sin blancos entre =
```
Para ver los alias definidos en su terminal, utilice `alias` y para eliminar un alias, utilice `unalias`.
La declaración de este alias es temporal (hasta que cierre la shell). Sin embargo, para que un usuario tenga permanentemente disponible un alias debe incluirlo en su archivo `~/.bashrc` que define la configuración de la bash del usuario y que se ejecuta cada vez que el usuario inicia un proceso bash.
Si se quiere que esté disponible para todos los usuarios, el administrador debe incluir el alias en el archivo `/etc/bash.bashrc` que también se ejecuta al abrir una bash (y lo hace previamente al archivo `.bashrc` del usuario).
## Metacaracteres y expansiones
La bash tiene una serie de caracteres con un significado especial, por ejemplo `*` para expansión de nombres de archivo, `&` para lanzar órdenes en segundo plano, `|` para las tuberías o *pipelines*, `>` para redireccionar la salida, `$` para sustitución de variables, `#` para comentarios, etc.
Cuando no queremos que se utilice este significado sino que se trate como un carácter “normal”, hay varias posibilidades a utilizar:
* Carácter de escape `\`. Mantiene el literal del carácter que le sigue. Se usa también al final de una línea para continuarla. Ej.:
```bash
$ echo \* \$? \>archivo
* $? >archivo
```
* Comillas simples `' '`. Preserva el valor literal de lo contenido entre las comillas. Ej.:
```bash
$ echo '* $? >archivo'
* $? >archivo
```
* Comillas dobles `" "`. Preserva el valor literal de lo contenido entre las comillas excepto para `\` y `$` . Ej.:
```bash
$ echo "* $? >archivo"
* 0 >archivo
```
Además, la bash proporciona múltiples expansiones que facilitan su utilización. Las principales son:
* Llaves `{ }`
* Tilde `~`
* Variables (y parámetros) `$`
* Sustitución de orden `$( )`
* Aritmética `$(( ))`
* Archivos y directorios (*globbing*)
### Expansión de llaves
Se utiliza para generar cadenas. Opcionalmente las cadenas generadas pueden tener un “prefijo” y/o un “sufijo”.
Ej.:
```bash
$ echo /tmp/{bin,datos,log}.aux #no poner espacios entre las comas
/tmp/bin.aux /tmp/datos.aux /tmp/log.aux
$ echo {a,e,i}{A,E,I} #piense qué saldría con {a,e,i} {A,E,I}
aA aE aI eA eE eI iA iE iI
```
Permite también generar secuencias de caracteres o números, crecientes o decrecientes, con posibilidad de especificar el incremento (por defecto, 1 o -1 según corresponda). En el caso de secuencias de números, permite tamaños fijos completando con ceros (se especifica añadiendo un 0 a la izquierda al límite inferior o superior).
Ej.:
```bash
$ echo {a..c}{15..12}
a15 a14 a13 a12 b15 b14 b13 b12 c15 c14 c13 c12
$ echo {A..D..2}y{1..9..3}
Ay1 Ay4 Ay7 Cy1 Cy4 Cy7
$ echo {a..c..2}{00..1000..100}
a0000 a0100 a0200 a0300 a0400 a0500 a0600 a0700 a0800 a0900 a1000
c0000 c0100 c0200 c0300 c0400 c0500 c0600 c0700 c0800 c0900 c1000
```
### Expansión de tilde
Se utiliza para obtener ciertos nombres de directorio.
Ej.
```bash
$ echo ~ #directorio home usuario actual
/home/jmc
$ echo ~pepe #directorio home usuario pepe
/home/pepe
$ echo ~+ #directorio actual
/home/jmc/aso
$ echo ~- #directorio anterior
/tmp
```
### Expansión de variables (y parámetros)
Nos vamos a centrar en la expansión de variable, aunque todo lo dicho en esta sección es también válido para los parámetros.
En la bash se pueden declarar variables. Ej.:
```bash
$ mes=9 #asignamos a la variable mes el valor 9. Cuidado: sin blancos entre =
$ mes= #asignamos a la variable mes la cadena vacía (es un valor válido)
$ unset mes #eliminamos la variable mes
```
Los identificadores de las variables distinguen mayúsculas y minúsculas (por tanto mes, Mes y MES son tres identificadores distintos). También, anteponiendo en la declaración `readonly` es posible definir constantes.
Ej.:
```bash
$ readonly IVA_NORMAL=21
```
Precediendo una variable con `$`, o escribiendo `${nombre_variable}`, se sustituye por el valor de la variable.
Ej.:
```bash
$ echo Es el mes $mes ... pro${mes}a
Es el mes 9 ... pro9a
````
El valor a asignar a las variables es objeto de varias expansiones si las hubiera: tilde, variable y parámetro, sustitución de orden y aritmética (nótese que no se produce expansión de llaves ni de nombres de archivos).
Ej.:
```bash
$ miDir=~/gastos${mes}.2021
$ echo $miDir
/home/jmacias/gastos9.2021
````
Las variables creadas como se ha indicado aquí se denominan variables de shell y no se exportan a las subshells.
Ej.:
```bash
$ miVar=hola
$ bash #ejecutamos otra shell desde la shell original
$ echo $miVar
$ exit #volvemos a la shell inicial
$ echo $miVar
hola
````
Existen las denominadas variables de entorno que están disponibles una vez definidas para todas las subshells. Para que una variable sea de entorno hay que precederla de `export`.
Ej.:
```bash
$ export miVar=hola
$ bash #ejecutamos otra shell desde la shell original
$ echo $miVar
hola
$ exit #volvemos a la shell inicial
$ echo $miVar
hola
```
Existen una serie de variables de entorno predefinidas. Las variables de entorno se visualizan con `env`. Podemos encontrar entre otras:
* HOME: directorio de conexión del usuario.
* PATH: Caminos en los que se buscarán los ejecutables cuando no se especifican.
* PS1: la cadena de configuración del prompt para la primera línea.
* PS2: la cadena de configuración del prompt a partir de la primera línea.
* PWD: el directorio actual.
### Sustitución de orden
Consiste en el reemplazo por la salida de una orden.
Su sintaxis es:`$(orden)`.
Ej.:
```bash
$ cal $(date +%Y) #supóngase que estamos en el año 2020
2020
Enero Febrero Marzo
do lu ma mi ju vi sá do lu ma mi ju vi sá do lu ma mi ju vi sá
1 2 3 4 5 1 2 1 2
6 7 8 9 10 11 12 3 4 5 6 7 8 9 3 4 5 6 7 8 9
...
```
### Sustitución artimética
Evalúa una expresión aritmética entera y sustituye por el resultado. Los operadores, su precedencia, asociatividad y resultados son los mismos que en lenguaje C.
Su sintaxis es: `$((expresión))`.
Ej.:
```bash
$ echo $((45*(9%2)+5))
50
```
### Expansión de nombres de archivos
Se denomina *globbing* en inglés y consiste en el archivos (o directorios) con patrones que contienen caracteres comodines. Estos son:
* `*`: Cualquier cadena, incluida la vacía
* `?`: Un carácter cualquiera
* `[ ]`: Cualquier carácter incluido entre los corchetes. Se pueden especificar rangos de caracteres con `-`.
* `[:alpha:]`, `[:digit:]`, `[:punct:]`, etc.: Clases de caracteres (letras, dígitos, cualquier signo de puntuación, etc.).
* `[^]`: cualquier carácter no incluido entre los corchetes.
Ej.:
```bash
$ ls
4abc.txt a.3b.txt a222.txt abcd.txt25
$ ls [[:alpha:]][^[:number:]]??.txt*
a.3b.txt abcd.txt25
```
## Redirecciones de la entrada/salida
Todo proceso tiene abiertos por defecto los siguientes descriptores de archivo (*file descriptors*):
* 0 => stdin (entrada estándar, del teclado)
* 1 => stdout (salida estándar, en el terminal)
* 2 => stderr (salida error estándar, en el terminal)
Las órdenes suelen usar la entrada y salidas estándar, lo que facilita el encadenamiento de órdenes utilizando *pipelines*.
Además hay varias posibilidades de redireccionar la entrada y salida a un archivo, como se verá a continuación.
### Redirección de la entrada a fichero
**`n < archivo`**: Abre, para lectura, el archivo cuyo descriptor es n. Si no se especifica n, se emplea el descriptor 0, entrada estándar. Ej.:
```bash
$ cat < fileTab.txt # Lee de la entrada estándar el archivo dado
Archivo con tabulaciones
$ tr "\t" " " < fileTab.txt
Archivo con tabulaciones
```
### Redirección de la salida a fichero
**`n > archivo`**: Abre, para escritura, el archivo cuyo descriptor es n. Si no se especifica n, es 1, salida estándar. Si fichero no existe, se crea y si existe, se trunca.
Ej.: Muestre todos los archivos que empiecen por a, b, c y d, sin el contenido de los directorios, en el archivo resultado.txt. La ejecución de esta orden no debe mostrar ningún resultado por pantalla
``` bash
$ ls -d [a-d]* > resultado.txt 2>/dev/null
```
**`n >> archivo`**: Añade contenido a un archivo existente. Si el archivo no existe, se crea. Ej.:
```bash
$ ls -d [w-z]* >>resultado.txt
````
### Redirección de la salida a descriptor
**`n > &m`** (o **`n >> &m`**): Abre para escritura (o añade) el mismo destino que haya en el descriptor m en el descriptor n. Ej.:
```bash
$ ls a* >resultado.txt 2>&1
$ ls b* >f1 2>f2 3>&1 1>&2 2>&3
```
Además, como es muy común establecer una redirección de la salida de error estándar a la salida estándar, la sintaxis reducida es `& > archivo`. Ej.:
```bash
$ ls j* &>resultado.txt
````
### Aquí documento *(here document)*
**`<<ETIQUETA`**: La entrada estándar lee del texto indicado hasta que encuentre de nuevo ETIQUETA.
Ej.:
```bash
$ ftp -n 127.0.0.100 <<FIN
user anonymous pepe@uah.es
binary
lcd $HOME
put archivo.dat
bye
FIN
```
Si se usa `<<-ETIQUETA` se ignoran tabuladores al inicio de las líneas, lo que permite, en el caso de los *scripts*, que el código quede más claro.
### Aquí cadena *(here string)*
**`<<< cadena`**: La entrada estándar lee de la cadena especificada.
Ej.:
````bash
$ dc <<<"10k 10.0 3.0 / p"
````
## Estructuración de órdenes en la bash
Las formas principales que adoptan las órdenes en la bash son:
* Simples
* Pipelines
* Listas
* Compuestas
### Órdenes simples
Secuencia de palabras separadas por espacios, terminados por uno de los operadores de control de la shell (nueva línea, `|`, `&`, `||`, `&&`, etc.). La primera palabra suele ser la orden a ejecutar y el resto los argumentos. Devuelve un *exit status* que indica el resultado de la orden.
Ej.:
```bash
$ grep "ASO $(($(date +%Y)-1))" *.txt
```
### Pipelines
Secuencia de una o más órdenes simples separadas por pipes, esto es `|` . La salida estándar de la orden precedente pasa a ser la entrada estándar de la orden siguiente. El *exit status* es el de la última orden del pipeline.
Ej.:
```bash
$ tail fichero.txt | wc -w
````
### Listas de órdenes
Secuencia de uno o más pipelines separados por `;`, `&`, `&&` o `||` y terminadas opcionalmente en `;`, `&`o nueva línea. El *exit status* es el de la última orden ejecutada.
* **orden1 ; orden2**: Las órdenes se ejecutan secuencialmente. La shell espera que termine cada una antes de ejecutar la siguiente. Ej.:
```bash
$ cd $home; touch fichero; echo Terminado
```
* **orden1 & orden2**: La orden1 se ejecuta asíncronamente en una subshell y pasa a ejecutar de forma síncrona orden2. Ej.:
```bash
$ gedit fichero & date
````
* **orden1 && orden2**: La orden2 se ejecuta solo si el *exit status* de orden1 ha sido 0. Ej.:
```bash
$ cd $dir && touch fichero
```
* **orden1 || orden2**: La orden2 se ejecuta solo si el *exit status* de orden1 ha sido distinto de 0. Ej.:
```bash
$ cd $dir 2>/dev/null || mkdir $dir
```
**Nota:** `&&` y `||` asocian por la izquierda.
### Órdenes compuestas
* Agrupación de órdenes
Listas de órdenes ejecutadas como una unidad, que pueden ejecutarse en una subshell con la sintaxis `(lista)` o en la misma shell con la sintaxis `{ lista;}`.
Ej.: (importante espacios y `;` en `{ lista; }`)
```bash
$ cd $dir 2>/dev/null || { mkdir $dir ; cd $dir; }
$ (echo "Mi $HOME"; ls $HOME) > ver.txt
```
Asimismo, se revisarán en los siguientes capítulos:
* Bucles.
* Condicionales.
## Variables de shell y personalización entorno
Los comportamientos de algunas de las expansiones se pueden configurar con la orden `shopt` (SHell OPTions). Por ejemplo, obsérvese el comportamiento para el uso de caracteres comodín:
```bash
$ ls
datos1.txt datos2.txt datosZ.txt fichero.txt
$ ls dat*.txt
datos1.txt datos2.txt datosZ.txt
$ echo *.txt
datos1.txt datos2.txt datosZ.txt fichero.txt
$ ls prueba*
ls: cannot access 'prueba*': No such file or directory
$ echo prueba*
prueba*
```
Este comportamiento es debido a que, por defecto, la opción `nullglob` de `shopt` esta desactivada. Esto significa que, si no es posible expandir la cadena a uno o más nombres de fichero, se usa la cadena tal cual. Otra posibilidad sería que, si no es posible expandir, se devolviera “vacío”, con lo que el comportamiento sería distinto. Por ejemplo:
```bash
$ shopt nullglob #consultamos estado
nullglob off
$ shopt -s nullglob #habilitamos (set) nullglob
$ ls prueba*
datos1.txt datos2.txt datosZ.txt fichero.txt
$ echo prueba*
$ shopt -u nullglob #deshabilitamos (unset) nullglob
```
Si se quiere modificar permanentemente esta u otra opción de la shell, habría que colocar la instrucción correspondiente en el archivo `~/.bashrc` del usuario (o en el archivo `/etc/bash.bashrc`, si se deseara para todos los usuarios).
## Ciclo de ejecución de una orden
De forma resumida, el ciclo de ejecución de una orden realiza los siguientes pasos:
1. Lee la orden desde el teclado o desde un archivo (shell script).
Una orden puede ocupar más de una línea, usando el continuador, `\`, al final de cada línea.
2. Divide la entrada en tokens (“palabras” y “operadores”) teniendo en cuenta el entrecomillado. En este paso se realiza también la expansión de los alias.
3. Analiza los tokens diferenciando órdenes simples y compuestas.
Las órdenes compuestas son estructuras de control del lenguaje de programación de la bash (`for`, `if`, etc.) y varias formas de agrupamiento que veremos más adelante.
4. Realiza varias expansiones de la shell, separando los tokens expandidos en listas de nombres de archivos y órdenes/argumentos.
5. Realiza las redirecciones de la entrada/salida que hubiera (eliminando de la lista de tokens los operadores y operandos relativos a las redirecciones).
6. Ejecuta la orden y, si la orden no ha sido lanzada en segundo plano, espera a que finalice recogiendo su valor de retorno (*exit status*) al terminar.
7. Repite (o termina shell, si la orden era `exit`).
Imagíne que tiene:
```bash
$ ls -l
-rw-rw-r-- 1 javier javier 50 nov 10 2020 f1.txt
-rw-rw-r-- 1 javier javier 200 feb 10 2021 f2.txt
-rw-rw-r-- 1 javier javier 200 feb 15 2021 fichero
$ ls -l *.txt > /tmp/resultado.txt
```
De manera aproximada, la última línea se procesaría como sigue:
1. Lectura de la línea.
2. División de la línea en tokens: `ls, -l, *.txt, >, /tmp/resultado.txt`.
Además, por defecto en Ubuntu, `ls` es un alias de `ls --color=auto`. Por tanto, el token `ls` se sustituiría por los tokens `ls`, `--color=auto`.
3. Se identifica y se analiza cada token como una orden simple.
4. `*.txt` se expande a `f1.txt f2.txt f3.txt`, considerando la orden y sus argumentos (`$ ls -l f1.txt f2.txt f3.txt`).
5. Se redirecciona la salida estándar a el fichero `/tmp/resultado.txt` y se elimina de la lista de tokens `>` y `/tmp/resultado.txt`.
6. Se ejecuta la orden en primer plano.
Como `ls` es una orden externa, la bash realiza una llamada al sistema operativo, a la función `exec`, y almacena el valor resultante (*exit status*) en la variable `?`.
7. Vuelve a lanzar la bash.
## Ejercicios
1. Crear el alias `listadir` para la orden `ls`. Pruébalo con alguna opción del `ls` ¿Funciona?
2. La opción `-h` (de *human*) de la orden `ls` hace que los tamaños de archivo aparezcan con un múltiplo adecuado, para que sean más fáciles de leer. Se quiere que, a veces, el ´ls´ lleve implícito `-h` y otras no.
Cree la variable `HUMANO` que tendrá el valor `-lh` y declare un alias de `ls` con dicha variable.
Observe la salida de ejecutar dicho alias. Después elimine la variable `HUMANO` y vuelva a ejecutar dicho alias. ¿Se obtiene el mismo resultado en ambos casos?
3. Escribe la orden para mover, independientemente del directorio en el que te encuentres, el archivo `prueba.txt` de directorio `$HOME` al directorio `/tmp`. En caso de no existir, no debería aparecer ningún error y se crearía un archivo en `/tmp` que contendría el calendario del mes actual (`cal`), además del resultado de ejecutar `ls` en el directorio de trabajo.
4. Escribe la orden para crear, en el directorio actual, doce archivos vacíos (`touch`) cuyos cuatro primeros dígitos sean el año que viene (tiene que funcionar independientemente del año en curso) y los dos siguientes el mes.
P. ej., si se ejecuta en 2022 crearía 202301, 202302, …, 202312.
6. Escriba una orden que utilice `grep` para comprobar si en la ruta completa del directorio actual de trabajo aparece la letra `s`. En caso de no aparecer, no muestra nada.
Por ejemplo, si estamos en el directorio `/tmp/aso`, se escribirá exclusivamente un mensaje del estilo “El directorio /tmp/aso contiene la letra s” y si no aparece, no escribe nada.
Haga dos versiones: una redireccionando la salida de `echo` a `grep` y otra usando “aquí cadena” como entrada de `grep`.
## Referencias
* Bash reference. https://www.gnu.org/software/bash/manual/html_node/index.html#SEC_Contents.
* Bash Pocket Reference. Arnold Robbins, 2016. O’Reilly.