1. El problema de las listas en texto
En el tema 3 quedó un problema sin resolver. Queríamos guardar en una variable las opciones de un comando, una de las cuales tenía un espacio, y no había forma de escribirla para que funcionara:
args del tema 3$ opciones='-l "mis documentos"'
$ args $opciones # sin comillas: se parte por todos los espacios
3 argumentos: <-l> <"mis> <documentos">
$ args "$opciones" # con comillas: un solo argumento
1 argumentos: <-l "mis documentos">
El fondo del problema es que una variable de texto no puede representar una lista: no tiene forma de marcar dónde termina un elemento y empieza el siguiente. La división en palabras corta por los espacios, y un espacio puede ser parte de un dato. Lo que hace falta es una variable que guarde varios textos por separado, cada uno con su contenido intacto.
Un array es una variable que guarda una lista de valores, cada uno identificado por un índice. En un array indexado, los índices son números enteros que empiezan en 0. En un array asociativo, son textos cualesquiera, llamados claves.
$ opciones=(-l "mis documentos")
$ args "${opciones[@]}"
2 argumentos: <-l> <mis documentos>
Dos argumentos, exactamente los que se escribieron. El resto del tema explica esta sintaxis y todo lo que se puede hacer con ella.
2. Crear un array
Un array indexado se crea asignando una lista de valores entre paréntesis, separados por espacios. Cada valor sigue las reglas de cualquier palabra de Bash: si tiene espacios, va entre comillas.
$ servidores=(web01 web02 db01)
$ archivos=("informe final.pdf" notas.txt "foto 1.jpg")
$ vacio=() # un array sin elementos
$ declare -p archivos
declare -a archivos=([0]="informe final.pdf" [1]="notas.txt" [2]="foto 1.jpg")
declare -p, que conocimos en el tema 5, es la mejor forma de ver un array: muestra cada índice con su valor entre comillas. La -a indica que es un array indexado. También se puede declarar así explícitamente, con declare -a nombre, aunque no es necesario: la asignación con paréntesis ya crea el array.
Una lista larga puede escribirse en varias líneas, sin barras invertidas, porque los paréntesis mantienen abierta la asignación. Admite comentarios, lo que la vuelve ideal para listas de configuración:
DIRECTORIOS=(
"$HOME/proyectos"
"$HOME/documentos"
/etc/nginx # configuración del servidor web
)
3. Leer un elemento
Un elemento se lee con su índice entre corchetes, y las llaves son obligatorias. Los índices negativos cuentan desde el final:
$ lista=(uno dos "tres cuatro")
$ echo "${lista[0]}"
uno
$ echo "${lista[2]}"
tres cuatro
$ echo "${lista[-1]}" # el último
tres cuatro
$ echo "$lista[1]" # sin llaves: $lista, seguido del texto [1]
uno[1]
$ echo "$lista" # el nombre solo es el elemento 0
uno
Los dos últimos casos son errores frecuentes. Sin llaves, Bash lee la variable lista y deja [1] como texto. Y el nombre del array a secas da solo su primer elemento, sin aviso; ShellCheck lo señala con SC2128. El índice, por su parte, es una expresión aritmética: ${lista[i]}, ${lista[i+1]} y ${lista[$i]} funcionan igual.
4. Todos los elementos: @ y *
Para obtener todos los elementos se usa @ o * como índice. Las dos formas dan lo mismo sin comillas, pero entre comillas se comportan de manera muy distinta, y esa diferencia es la clave de los arrays:
$ lista=(uno dos "tres cuatro")
$ args "${lista[@]}" # cada elemento, un argumento
3 argumentos: <uno> <dos> <tres cuatro>
$ args "${lista[*]}" # todos unidos en un solo argumento
1 argumentos: <uno dos tres cuatro>
$ args ${lista[@]} # sin comillas: se dividen por los espacios
4 argumentos: <uno> <dos> <tres> <cuatro>
| Forma | Produce | Cuándo usarla |
|---|---|---|
"${lista[@]}" | Un argumento por elemento, cada uno intacto. | Casi siempre. Para recorrer, pasar a un comando o copiar. |
"${lista[*]}" | Un solo texto, con los elementos unidos por el primer carácter de IFS (un espacio, por defecto). | Para mostrar la lista en un mensaje, o unirla con un separador (sección 13). |
${lista[@]} o ${lista[*]} | Los elementos, divididos otra vez por los espacios y expandidos como comodines. | Nunca: pierde justamente lo que el array protege. |
La forma "${lista[@]}" es la única excepción a una regla que vimos en el tema 3: una expansión entre comillas produce siempre una sola palabra. Aquí produce tantas como elementos tenga el array, y cada una conserva sus espacios. Es el mismo comportamiento que tiene "$@" con los argumentos de un script, como veremos en el tema 20.
5. Cantidad e índices
Con # delante se obtiene la cantidad de elementos, y con ! la lista de índices:
$ lista=(uno dos "tres cuatro")
$ echo "${#lista[@]}" # cantidad de elementos
3
$ echo "${!lista[@]}" # índices
0 1 2
$ echo "${#lista[2]}" # longitud de un elemento
11
Con la cantidad se comprueba si un array está vacío, algo muy común antes de procesar una lista de archivos: (( ${#lista[@]} == 0 )). Y conviene no confundir ${#lista[@]}, la cantidad de elementos, con ${#lista}, que es la longitud del primer elemento.
6. Agregar, cambiar y borrar
El operador += con paréntesis agrega elementos al final, sin necesidad de conocer el último índice. Un elemento concreto se cambia asignándole por índice:
$ lista=(uno dos "tres cuatro")
$ lista+=(cinco) # agregar al final
$ lista+=("seis" "siete") # varios a la vez
$ lista[0]=UNO # reemplazar el elemento 0
$ declare -p lista
declare -a lista=([0]="UNO" [1]="dos" [2]="tres cuatro" [3]="cinco" [4]="seis" [5]="siete")
lista+=x no es lista+=(x)
Sin paréntesis, += agrega texto al primer elemento, como con una variable común: lista+=cinco convierte UNO en UNOcinco. Para agregar un elemento, los paréntesis son imprescindibles.
Los arrays de Bash pueden tener huecos: los índices no tienen por qué ser consecutivos. Se crean al asignar un índice lejano o al borrar un elemento con unset:
$ lista=(uno dos tres)
$ lista[7]=siete # los índices 3 a 6 no existen
$ unset 'lista[1]' # borrar el elemento 1
$ declare -p lista
declare -a lista=([0]="uno" [2]="tres" [7]="siete")
$ echo "${#lista[@]} elementos, índices ${!lista[@]}"
3 elementos, índices 0 2 7
$ lista=("${lista[@]}") # reconstruir sin huecos
$ declare -p lista
declare -a lista=([0]="uno" [1]="tres" [2]="siete")
Con huecos, la cantidad de elementos ya no coincide con el último índice más uno, y un bucle que recorra los índices de 0 a ${#lista[@]} - 1 se saltará elementos y leerá otros vacíos. Por eso conviene recorrer siempre con "${lista[@]}" o con "${!lista[@]}", como en la sección siguiente, y no con índices calculados.
En unset 'lista[1]', las comillas no son un capricho: sin ellas, [1] es un patrón de comodines, y si en el directorio existe un archivo llamado lista1, la palabra se reemplaza por ese nombre. Para borrar el array entero, unset lista; para vaciarlo y seguir usándolo, lista=().
7. Recorrer un array
La forma habitual de procesar cada elemento es un bucle for sobre "${lista[@]}". El bucle se estudia en el tema 14, pero su forma básica se entiende sola:
servidores=(web01 web02 db01)
for servidor in "${servidores[@]}"; do
ping -c1 -W2 "$servidor" > /dev/null && echo "$servidor responde"
done
Cuando además del valor hace falta su posición, por ejemplo para numerar una lista, se recorren los índices:
$ archivos=("informe final.pdf" notas.txt "foto 1.jpg")
$ for i in "${!archivos[@]}"; do printf '%d) %s\n' $(( i + 1 )) "${archivos[i]}"; done
1) informe final.pdf
2) notas.txt
3) foto 1.jpg
8. Rebanadas y transformaciones
Las expansiones de parámetros del tema 6 funcionan también sobre arrays. Aplicadas a [@], transforman cada elemento por separado, sin necesidad de un bucle:
$ rutas=(/home/ana/a.txt /var/log/b.txt /tmp/c.txt)
$ echo "${rutas[@]##*/}" # el nombre de cada archivo
a.txt b.txt c.txt
$ echo "${rutas[@]/%.txt/.bak}" # cambiar la extensión de todos
/home/ana/a.bak /var/log/b.bak /tmp/c.bak
$ nombres=(ana luis eva)
$ echo "${nombres[@]^}" # mayúscula inicial en cada uno
Ana Luis Eva
Y la forma de subcadena se convierte en una rebanada: ${lista[@]:inicio:cantidad} da los elementos desde una posición, en lugar de los caracteres:
$ semana=(lun mar mié jue vie sáb dom)
$ echo "${semana[@]:0:5}" # los cinco primeros
lun mar mié jue vie
$ echo "${semana[@]:5}" # desde el sexto hasta el final
sáb dom
El resultado de una transformación puede guardarse como un nuevo array, con paréntesis y comillas: nombres=("${rutas[@]##*/}").
9. Llenar un array desde afuera
Los arrays rara vez se escriben a mano: casi siempre se llenan con datos que el script obtiene. Hay una forma correcta para cada fuente, y una forma tentadora que conviene evitar.
Desde un patrón de archivos
Un comodín dentro de los paréntesis llena el array con los nombres de archivo que coinciden, cada uno como un elemento, con sus espacios intactos:
$ registros=(/var/log/app/*.log)
$ echo "Hay ${#registros[@]} registros"
Hay 4 registros
Recuerda la regla del tema 3: si ningún archivo coincide, el patrón queda tal cual, y el array tendrá un elemento con el texto /var/log/app/*.log. La opción shopt -s nullglob, que veremos en el tema 14, hace que en ese caso el array quede vacío.
Desde las líneas de un archivo o de un comando
El comando mapfile, también llamado readarray, lee líneas y guarda cada una como un elemento. La opción -t quita el salto de línea del final de cada una, y casi siempre se usa:
$ mapfile -t servidores < servidores.txt # una línea del archivo, un elemento
$ mapfile -t grandes < <(find ~ -size +100M)
$ printf '%s\n' "${grandes[@]}"
/home/ana/videos/viaje 2025.mp4
/home/ana/descargas/ubuntu.iso
La forma < <(comando) es una sustitución de procesos: hace que la salida de un comando se lea como si fuera un archivo. Hace falta porque comando | mapfile -t lista no funciona como se espera: en una tubería, mapfile se ejecuta en un proceso aparte, y el array se llena allí y se pierde al terminar. Volveremos sobre este problema en el tema 16.
Desde los campos de una línea
Para separar un texto en campos por un carácter, se usa read -a con IFS indicando el separador. Es el tema del tema 9, pero la receta es breve:
$ linea="ana:x:1000:1000:Ana Pérez:/home/ana:/bin/bash"
$ IFS=: read -ra campos <<< "$linea"
$ echo "Usuario ${campos[0]}, nombre ${campos[4]}, shell ${campos[6]}"
Usuario ana, nombre Ana Pérez, shell /bin/bash
La forma que conviene evitar
Es tentador llenar un array con lista=($(comando)). Funciona mientras ningún dato tenga espacios ni comodines, porque la salida del comando pasa por la división en palabras y la expansión de nombres de archivo:
mi informe.txt y notas.txt$ mal=($(ls))
$ declare -p mal
declare -a mal=([0]="mi" [1]="informe.txt" [2]="notas.txt")
$ bien=(*)
$ declare -p bien
declare -a bien=([0]="mi informe.txt" [1]="notas.txt")
ShellCheck avisa de este patrón con SC2207 y sugiere mapfile. La regla: comodines para archivos, mapfile -t para líneas, read -a para campos.
10. Construir comandos con arrays
Llegamos al uso que justifica los arrays en la mayoría de los scripts: armar los argumentos de un comando de a poco, según las condiciones, y ejecutarlo al final con todos ellos intactos. Es la solución al problema de la sección 1.
opciones=(--archive --human-readable)
[[ "${SIMULAR:-0}" == 1 ]] && opciones+=(--dry-run)
[[ "${DETALLE:-0}" == 1 ]] && opciones+=(--verbose)
for patron in '*.tmp' '.cache/' 'node_modules/'; do
opciones+=(--exclude "$patron")
done
rsync "${opciones[@]}" "$origen/" "$destino/"
Cada opción y cada valor es un elemento: --exclude y *.tmp van por separado, y el patrón conserva su asterisco sin expandirse, porque está entre comillas al agregarlo y "${opciones[@]}" no vuelve a expandir comodines. Con SIMULAR=1 DETALLE=1, rsync recibe exactamente:
<--archive> <--human-readable> <--dry-run> <--verbose> <--exclude> <*.tmp> <--exclude> <.cache/> <--exclude> <node_modules/> </home/ana/proyecto/> </mnt/respaldos/>
Un beneficio adicional: el array puede mostrarse antes de ejecutarlo, para dejar constancia en un registro o para revisarlo. printf '%q ' "${opciones[@]}" muestra cada elemento entrecomillado de forma que podría copiarse y pegarse en la terminal.
11. Arrays asociativos
En un array asociativo, los índices son textos, llamados claves. Sirven para relacionar nombres con valores: una configuración, la dirección de cada servidor, el total de cada categoría. A diferencia de los indexados, hay que declararlos con declare -A antes de usarlos:
$ declare -A conf=([destino]=/respaldos [dias]=30)
$ conf[servidor]=web01 # agregar o cambiar una clave
$ echo "Copiar a ${conf[servidor]}:${conf[destino]}, ${conf[dias]} días"
Copiar a web01:/respaldos, 30 días
$ declare -p conf
declare -A conf=([destino]="/respaldos" [dias]="30" [servidor]="web01" )
declare -A, no es asociativo
Si se omite la declaración, conf[servidor]=web01 no da error: crea un array indexado, y como el índice se evalúa aritméticamente, servidor se toma como una variable inexistente que vale 0. Todas las claves terminan en el índice 0, pisándose entre sí. Si un array asociativo se comporta de forma extraña, lo primero es mirarlo con declare -p: debe decir -A.
Las operaciones son las mismas que con los indexados, cambiando números por claves:
| Operación | Forma |
|---|---|
| Leer un valor | "${conf[clave]}" |
| Todos los valores | "${conf[@]}" |
| Todas las claves | "${!conf[@]}" |
| Cantidad de pares | "${#conf[@]}" |
| ¿Existe la clave? | [[ -v conf[clave] ]] |
| Valor, o uno por defecto | "${conf[clave]:-valor}" |
| Borrar una clave | unset 'conf[clave]' |
Las claves pueden contener espacios y cualquier carácter, y pueden venir de una variable: conf[$nombre]=valor. Lo que no tienen es un orden: "${!conf[@]}" las devuelve en un orden interno, que no es el de inserción ni el alfabético. Si el orden importa, hay que ordenarlas, como en la sección 13.
Un uso clásico es reemplazar una cadena de condiciones por una tabla. En lugar de preguntar por cada caso, se busca la respuesta:
declare -A puerto=([web]=443 [db]=5432 [cache]=6379)
servicio="${1:?Uso: verificar SERVICIO}"
if [[ ! -v puerto[$servicio] ]]; then
echo "Servicio desconocido: $servicio (opciones: ${!puerto[*]})" >&2
exit 2
fi
nc -z localhost "${puerto[$servicio]}" && echo "$servicio responde"
12. Contar con un array asociativo
Otra aplicación muy frecuente es contar o acumular por categoría: cuántos errores de cada tipo, cuántos accesos por usuario, cuántos bytes por extensión. La clave es la categoría, y el valor, el acumulado. El valor por defecto :-0 resuelve el caso de la primera aparición, cuando la clave todavía no existe:
declare -A veces=()
for palabra in rojo verde rojo azul rojo verde; do
veces[$palabra]=$(( ${veces[$palabra]:-0} + 1 ))
done
for color in "${!veces[@]}"; do
echo "$color: ${veces[$color]}"
done
azul: 1
verde: 2
rojo: 3
Para contar líneas de un archivo, sort | uniq -c o awk siguen siendo más rápidos. El array asociativo conviene cuando los datos ya están en el script, o cuando cada elemento requiere un tratamiento que no se expresa con una tubería, como en el script de la sección 15.
13. Unir, ordenar y mostrar
Bash no tiene funciones para unir u ordenar arrays, pero las tres operaciones se resuelven en una línea.
Mostrar un elemento por línea es trabajo de printf, que repite su formato para cada argumento:
$ printf '%s\n' "${servidores[@]}"
web01
web02
db01
Unir con un separador aprovecha que "${lista[*]}" usa el primer carácter de IFS. Para no alterar IFS en el resto del script, se cambia dentro de una subshell, entre paréntesis, o dentro de una función con local:
$ (IFS=,; echo "${servidores[*]}")
web01,web02,db01
Ordenar se delega en sort: se muestran los elementos uno por línea, se ordenan, y el resultado se vuelve a leer con mapfile:
mapfile -t ordenados < <(printf '%s\n' "${servidores[@]}" | sort)
for clave in $(printf '%s\n' "${!conf[@]}" | sort); do # claves sin espacios
echo "$clave=${conf[$clave]}"
done
El segundo bucle usa una sustitución sin comillas, que se divide por los espacios: es aceptable solo si las claves no los tienen. Si pueden tenerlos, se ordena primero con mapfile en otro array y se recorre ese.
14. Lo que los arrays no hacen
Los arrays de Bash son útiles, pero limitados. Conocer sus límites ayuda a reconocer cuándo una tarea ha crecido más allá de lo que Bash maneja bien, como discutimos en el tema 1:
| Límite | Consecuencia | Qué hacer |
|---|---|---|
| Una sola dimensión | No hay arrays de arrays ni tablas con filas y columnas. | Claves compuestas como m[fila,col], o pasar a otro lenguaje. |
| No se exportan | Un programa hijo no recibe los arrays del script, aunque se use export. | Pasar los elementos como argumentos: otro-script "${lista[@]}". |
| No se devuelven fácilmente | Una función no puede «devolver» un array. | Variables globales o referencias con declare -n (tema 19). |
| Asociativos desde Bash 4 | macOS trae Bash 3.2, sin declare -A ni mapfile. | Instalar un Bash actual, o evitarlos si el script debe correr allí. |
| Sin orden en los asociativos | Las claves salen en un orden interno impredecible. | Ordenar al recorrer. |
15. Un script completo
Este script responde una pregunta habitual cuando se acaba el espacio: ¿qué tipo de archivo ocupa más en este directorio? Recorre todos los archivos, incluidos los de subdirectorios, y acumula en dos arrays asociativos la cantidad y el tamaño de cada extensión. Al final muestra una tabla ordenada por tamaño.
#!/usr/bin/env bash
#
# por-extension — cuánto ocupan los archivos de un directorio, por extensión.
#
# Uso: por-extension [DIRECTORIO]
#
set -euo pipefail
dir="${1:-.}"
declare -A cantidad=() bytes=()
# find escribe «tamaño nombre», terminado en un carácter nulo, para cada archivo.
while IFS= read -r -d '' linea; do
tam="${linea%% *}" # hasta el primer espacio
nombre="${linea#* }" # el resto
if [[ "$nombre" == ?*.* ]]; then # tiene un punto que no es el inicial
ext="${nombre##*.}"
ext="${ext,,}" # JPG y jpg cuentan juntos
else
ext="(sin extensión)"
fi
cantidad[$ext]=$(( ${cantidad[$ext]:-0} + 1 ))
bytes[$ext]=$(( ${bytes[$ext]:-0} + tam ))
done < <(find "$dir" -type f -printf '%s %f\0')
printf '%10s %8s %s\n' KiB Archivos Extensión
for ext in "${!cantidad[@]}"; do
printf '%10d %8d %s\n' $(( (${bytes[$ext]} + 512) / 1024 )) "${cantidad[$ext]}" "$ext"
done | sort -rn
$ por-extension ~/documentos
KiB Archivos Extensión
845210 37 mp4
127448 412 jpg
30122 58 pdf
2215 146 txt
318 9 (sin extensión)
41 3 sh
- Los dos arrays comparten las claves: para cada extensión,
cantidadguarda cuántos archivos hay ybytescuánto suman. Es la forma de tener «dos columnas» por clave, dado que los arrays tienen una sola dimensión. find -printf '%s %f\0'escribe el tamaño y el nombre de cada archivo, terminados en un carácter nulo en lugar de un salto de línea. Como ningún nombre de archivo puede contener ese carácter, el script funciona incluso con nombres que tienen saltos de línea.read -d ''lee hasta cada carácter nulo; lo veremos en los temas 16 y 32.- El patrón
?*.*exige al menos un carácter antes del punto. Así,.bashrccuenta como archivo sin extensión, el caso límite que señalamos en el tema 6. - El bucle final se conecta a
sort -rn, que ordena las líneas numéricamente, de mayor a menor. El encabezado se imprime antes, fuera de la tubería, para que no se mezcle con los datos al ordenar.
16. Errores frecuentes
| Escribiste | Qué pasa | Corrección |
|---|---|---|
echo "$lista" | Muestra solo el elemento 0. | "${lista[@]}" |
echo "$lista[2]" | Muestra el elemento 0 seguido de [2]. | "${lista[2]}" |
cmd ${lista[@]} | Los elementos con espacios se parten. | cmd "${lista[@]}" |
cmd "${lista[*]}" | El comando recibe un solo argumento. | cmd "${lista[@]}" |
lista+=x | Agrega texto al elemento 0. | lista+=(x) |
lista=($(comando)) | Divide por espacios y expande comodines. | mapfile -t lista < <(comando) |
comando | mapfile -t lista | El array queda vacío: se llenó en otro proceso. | mapfile -t lista < <(comando) |
m[clave]=v sin declare -A | Crea un array indexado; todas las claves van al índice 0. | declare -A m antes. |
${#lista} para contar | Da la longitud del primer elemento. | ${#lista[@]} |
unset lista[1] | Puede expandirse como comodín. | unset 'lista[1]' |
17. Resumen
- Un array guarda una lista de valores, cada uno intacto. Resuelve lo que una variable de texto no puede: listas cuyos elementos tienen espacios.
- Un array indexado se crea con
lista=(a b "c d"); sus índices empiezan en 0, y-1es el último. - Un elemento se lee con
"${lista[i]}"; las llaves son obligatorias, y$listada solo el elemento 0. "${lista[@]}"produce un argumento por elemento;"${lista[*]}"los une en uno; sin comillas, se vuelven a dividir.${#lista[@]}es la cantidad de elementos y${!lista[@]}la lista de índices.lista+=(x)agrega al final;unset 'lista[i]'borra y deja un hueco;lista=("${lista[@]}")los elimina.- Las expansiones del tema 6 aplicadas a
[@]transforman cada elemento, y${lista[@]:i:n}extrae una rebanada. - Se llenan con comodines para archivos,
mapfile -tpara líneas yread -rapara campos.lista=($(comando))se evita. - Construir los argumentos de un comando en un array y ejecutarlo con
"${opciones[@]}"es el uso más importante de los arrays en scripts. - Un array asociativo se declara con
declare -A, usa claves de texto y no tiene orden.[[ -v m[clave] ]]comprueba si una clave existe. - Con
${m[$clave]:-0}se acumula por categoría sin preocuparse por la primera aparición. - Los arrays son de una sola dimensión, no se exportan y requieren Bash 4 para los asociativos.
18. Preguntas de repaso
Intenta responder cada una antes de abrir la respuesta.
1. ¿Por qué no se puede guardar de forma fiable una lista de nombres de archivo en una variable de texto?
Porque una variable de texto no tiene forma de marcar dónde termina cada elemento. Al usarla, la división en palabras corta por los espacios, que pueden ser parte de los nombres.
2. Con a=(x "y z" w), ¿qué dan ${a[1]}, ${a[-1]} y $a?
y z, w y x. El nombre solo, sin índice, da el elemento 0.
3. ¿Cuántos argumentos recibe un comando con "${a[@]}", con "${a[*]}" y con ${a[@]}, si a=(x "y z" w)?
Tres, uno y cuatro. "${a[@]}" da un argumento por elemento; "${a[*]}" los une en uno solo; sin comillas, y z se divide en dos.
4. ¿Qué diferencia hay entre ${#a[@]} y ${#a}?
${#a[@]} es la cantidad de elementos del array. ${#a} es la longitud del elemento 0.
5. ¿Qué hace a+=nuevo, y qué hace a+=(nuevo)?
a+=nuevo agrega el texto al final del elemento 0. a+=(nuevo) agrega un elemento nuevo al final del array.
6. Después de a=(p q r s) y unset 'a[1]', ¿qué dan ${#a[@]} y ${!a[@]}?
3 y 0 2 3. El elemento 1 desapareció y dejó un hueco: los demás conservan sus índices.
7. ¿Por qué conviene recorrer un array con "${a[@]}" o "${!a[@]}" en lugar de contar de 0 a ${#a[@]} - 1?
Porque un array puede tener huecos. Si los tiene, los índices no son consecutivos, y contar de 0 en adelante se salta elementos y lee posiciones vacías.
8. ¿Qué da "${rutas[@]##*/}" si rutas contiene rutas de archivo?
El nombre de cada archivo, sin el directorio. La expansión se aplica a cada elemento por separado.
9. ¿Por qué lista=($(ls)) es una mala forma de obtener los archivos de un directorio?
Porque la salida de ls pasa por la división en palabras y por la expansión de comodines: un nombre con espacios se convierte en varios elementos. La forma correcta es lista=(*).
10. ¿Por qué find . -name '*.log' | mapfile -t lista deja el array vacío?
Porque cada parte de una tubería se ejecuta en un proceso aparte. mapfile llena el array en ese proceso, que termina al acabar la tubería, y el script nunca lo ve. Se usa mapfile -t lista < <(find …).
11. ¿Cómo se agrega la opción --dry-run a un comando solo cuando SIMULAR vale 1?
Guardando las opciones en un array y agregándola de forma condicional: [[ $SIMULAR == 1 ]] && opciones+=(--dry-run). Luego se ejecuta el comando con "${opciones[@]}".
12. ¿Qué ocurre si se usa m[web]=443 sin haber declarado m con declare -A?
Se crea un array indexado. El índice web se evalúa como expresión aritmética, como una variable inexistente que vale 0, y el valor queda en el índice 0. Otras claves también irían al 0, pisándose.
13. ¿Cómo se comprueba si un array asociativo tiene una clave?
Con [[ -v m[clave] ]], que es verdadero si la clave existe, aunque su valor esté vacío.
14. ¿Por qué se escribe ${veces[$p]:-0} al contar con un array asociativo?
Porque la primera vez que aparece una categoría su clave no existe. El valor por defecto 0 permite sumar desde ahí, y además evita el error de set -u.
15. ¿Cómo se unen los elementos de un array con comas?
Con (IFS=,; echo "${lista[*]}"). La forma [*] une los elementos con el primer carácter de IFS, y los paréntesis limitan el cambio de IFS a esa subshell.
Para practicar
- Crea un array con cinco nombres de ciudades, alguna con espacios. Muéstralo con
declare -p, conprintf '%s\n'y conargs, usando[@]y[*], con y sin comillas. - Agrega dos ciudades, borra la segunda y muestra la cantidad y los índices. Reconstruye el array sin huecos.
- Llena un array con los archivos
.txtde un directorio y muestra cuántos hay. Pruébalo en un directorio sin archivos.txt: ¿qué cantidad informa? - Usa
mapfilepara leer/etc/shellsen un array y muestra solo las líneas que no empiezan con#. - Separa en campos una línea de
/etc/passwdconread -ray muestra el usuario, su directorio personal y su shell. - Escribe un script que construya un comando
lsen un array, agregando-lsi la variableLARGOvale 1 y-asiOCULTOSvale 1, y lo ejecute sobre un directorio con espacios en el nombre. - Cuenta con un array asociativo cuántas veces aparece cada shell en
/etc/passwd(el séptimo campo de cada línea). - Ejecuta
por-extensionen tu directorio personal. Modifícalo para que muestre también el porcentaje del total que representa cada extensión, con lo aprendido en el tema 7.