← Automatización con Scripts en Bash Bloque 1 · Del comando al script · Tema 3 de 42

Tema 3

Cómo lee Bash una línea

Entre lo que escribes y lo que el comando recibe, Bash transforma la línea en varias etapas: la corta en palabras, reemplaza variables, ejecuta sustituciones, vuelve a cortar, expande comodines y quita comillas. Casi todos los errores desconcertantes de un script ocurren en alguna de esas etapas. Este tema recorre el proceso completo, en orden, para que puedas predecir qué recibirá cada comando antes de ejecutarlo.

1. Por qué importa en un script

En la terminal, cuando una línea no hace lo esperado, lo ves enseguida y la corriges. En un script, la misma línea se ejecuta con datos que no elegiste tú: un nombre de archivo con espacios, una variable que llegó vacía, un texto que contiene un asterisco. Y nadie está mirando. Fíjate en estas dos líneas, que se diferencian solo en un par de comillas:

terminal
$ archivo="informe final.txt"
$ rm $archivo
rm: cannot remove 'informe': No such file or directory
rm: cannot remove 'final.txt': No such file or directory
$ rm "$archivo"                          # borra el archivo correcto

En este caso no pasó nada grave, porque no existían archivos llamados informe ni final.txt. Si hubieran existido, la primera línea los habría borrado a ellos en lugar del que se quería. El comando rm no tiene la culpa: hizo exactamente lo que le pidieron. Lo que cambió es lo que recibió, y eso lo decidió Bash antes de ejecutarlo.

El tema 9 del curso de Terminal, Comodines, comillas y expansión, presentó estas transformaciones una por una. Aquí las ordenamos en un modelo completo, porque para escribir scripts confiables no basta con conocer cada pieza: hay que saber en qué orden ocurren, ya que ese orden explica casi todos los errores.

2. Un banco de pruebas: args

Para ver qué recibe un comando necesitamos uno que muestre sus argumentos, cada uno por separado y bien delimitado. echo no sirve, porque une sus argumentos con espacios y no se distingue si recibió una palabra o dos. Definamos una función que los cuente y los muestre entre ángulos:

terminal
$ args() { printf '%d argumentos:' "$#"; for a in "$@"; do printf ' <%s>' "$a"; done; echo; }
$ args uno   dos     tres
3 argumentos: <uno> <dos> <tres>
$ args "uno   dos" tres
2 argumentos: <uno   dos> <tres>
$ echo uno   dos     tres                # echo oculta la diferencia
uno dos tres

No hace falta entender todavía cómo está escrita la función: $# es la cantidad de argumentos y "$@" son los argumentos mismos, que el bucle for recorre uno por uno; los veremos en los temas 18 y 20. La usaremos durante todo el tema. Si quieres tenerla siempre a mano, guárdala como script en ~/bin/args, con su línea shebang, tal como aprendiste en el tema 2.

3. El recorrido completo

Cada vez que Bash ejecuta una línea, sea escrita en la terminal o leída de un script, la procesa en ocho etapas, siempre en este orden:

Las ocho etapas con que Bash procesa una línea: 1, leer y separar en palabras y operadores; 2, reconocer la estructura; 3, expansión de llaves; 4, tilde, variables, aritmética y sustitución de comandos; 5, división en palabras; 6, expansión de nombres de archivo; 7, eliminación de comillas; 8, redirigir y ejecutar. Las etapas 3 a 6 son las expansiones expansiones 1. Leer palabras y operadores 2. Estructura comandos, =, redirecciones 3. Llaves {a,b} {1..5} 4. Parámetros ~ $var $(( )) $( ) 5. División en palabras, según IFS 6. Comodines * ? [ ] nombres de archivo 7. Comillas se quitan ' " \ 8. Ejecutar redirigir y poner en marcha
Las cuatro etapas del centro son las expansiones. Cada una trabaja sobre el resultado de la anterior, y ninguna vuelve atrás.
EtapaQué haceEjemplo
1. LeerSepara la línea en palabras y operadores, respetando las comillas.ls -l "mis docs" | wc → ls, -l, "mis docs", |, wc
2. EstructuraReconoce comandos, tuberías, asignaciones y redirecciones.Dos comandos unidos por una tubería.
3. LlavesGenera palabras a partir de listas y rangos.f{1..3} → f1 f2 f3
4. ParámetrosReemplaza la tilde, las variables, la aritmética y las sustituciones de comandos, de izquierda a derecha.$HOME → /home/ana
5. DivisiónVuelve a cortar en palabras los resultados de la etapa 4 que no estaban entre comillas.$archivo → informe, final.txt
6. ComodinesReemplaza las palabras con *, ? o [ ] sin comillas por los nombres de archivo que coinciden.*.txt → a.txt b.txt
7. ComillasQuita las comillas y barras invertidas que quedaron de la línea original."mis docs" → mis docs
8. EjecutarAplica las redirecciones y pone en marcha el comando con la lista final de palabras.ls recibe -l y mis docs.

Dos ideas de este cuadro son las que más pesan en la práctica, y las retomaremos varias veces. La primera: las etapas 5 y 6 se aplican a los resultados de la etapa 4. El contenido de una variable sin comillas se vuelve a cortar y se trata como comodín. La segunda: el orden es fijo, y una etapa no puede usar lo que produce otra posterior. Las llaves, por ejemplo, ocurren antes que las variables.

4. Etapa 1: palabras y operadores

Lo primero que hace Bash es cortar la línea en piezas. Corta donde encuentra un metacarácter: un espacio, un tabulador, un salto de línea, o uno de estos caracteres: | & ; ( ) < >. Los espacios solo separan y desaparecen; los demás forman operadores, como |, &&, ; o >>. Todo lo demás son palabras.

terminal
$ args uno;args dos                     # ; es un operador: no hace falta espacio
1 argumentos: <uno>
1 argumentos: <dos>
$ args a=b c-d e.f g/h                  # = - . / no cortan
4 argumentos: <a=b> <c-d> <e.f> <g/h>
$ args 'uno; dos' "tres | cuatro"       # entre comillas, nada corta
2 argumentos: <uno; dos> <tres | cuatro>

En esta etapa las comillas ya cumplen su primera función: todo lo que está dentro de ellas forma parte de la misma palabra, aunque tenga espacios u operadores. Las comillas no desaparecen todavía: quedan marcadas, porque las etapas siguientes necesitan saber qué partes estaban protegidas.

También en esta etapa se reconocen los comentarios. Un # inicia un comentario solo si está al comienzo de una palabra:

terminal
$ args uno #dos tres
1 argumentos: <uno>
$ args uno#dos tres                     # dentro de una palabra, # es un carácter más
2 argumentos: <uno#dos> <tres>

5. Etapa 2: la estructura del comando

Con la línea ya cortada, Bash identifica qué papel cumple cada pieza. Tres reconocimientos de esta etapa explican errores frecuentes.

Las palabras reservadas

Palabras como if, then, for, do, done o while tienen un significado especial, pero solo en la posición de comando: al comienzo de la línea, o después de un operador como ; o &&. En cualquier otro lugar son palabras comunes. Por eso echo if then fi muestra esas tres palabras sin protestar, y por eso if y then necesitan un ; o un salto de línea entre ellos.

Las asignaciones

Una palabra de la forma nombre=valor en la posición de comando es una asignación, siempre que no haya espacios alrededor del =. Con espacios, la etapa 1 ya cortó la línea en tres palabras, y la primera se interpreta como el nombre de un comando:

terminal
$ destino=/respaldos                    # asignación
$ destino = /respaldos                  # el comando «destino» con dos argumentos
destino: command not found
$ destino= /respaldos                   # destino vacía, solo para ejecutar «/respaldos»
bash: /respaldos: Is a directory

El último caso es el más curioso. destino= es una asignación con valor vacío, y una asignación seguida de un comando no cambia la variable en la shell: la define solo para ese comando. Es la forma LANG=C sort archivo que viste en el tema 34 del curso de Terminal. Aquí, el «comando» resultó ser /respaldos.

Las redirecciones

Los operadores >, >>, <, 2> y sus parientes, con la palabra que los sigue, se separan del resto. No serán argumentos del comando: se aplicarán en la etapa 8. Por eso pueden escribirse en cualquier lugar de la línea, y estas dos son equivalentes:

terminal
$ args uno dos > salida.txt
$ > salida.txt args uno dos
$ cat salida.txt
2 argumentos: <uno> <dos>

6. Etapa 3: las llaves

La expansión de llaves genera texto: {a,b,c} produce tres palabras, y {1..5}, un rango. Es la primera de las expansiones, y la única que no sabe nada de variables ni de archivos: trabaja solo con el texto escrito.

terminal
$ args informe.{txt,pdf}
2 argumentos: <informe.txt> <informe.pdf>
$ args dia{01..03}
3 argumentos: <dia01> <dia02> <dia03>
$ cp config.ini{,.bak}                    # cp config.ini config.ini.bak

Que ocurra antes que todas las demás tiene una consecuencia que sorprende a todo el mundo alguna vez: un rango con una variable no funciona.

terminal
$ n=3
$ args {1..$n}
1 argumentos: <{1..3}>

En la etapa 3, Bash ve {1..$n}. Como $n todavía no es un número, no es un rango válido, y lo deja como está. En la etapa 4 se reemplaza $n, y queda el texto {1..3}, pero las llaves ya pasaron y no se vuelven a procesar. Para recorrer un rango variable se usa seq 1 "$n" o un bucle for al estilo C, que veremos en el tema 14.

7. Etapa 4: tilde, variables, aritmética y sustitución

Esta etapa agrupa cuatro expansiones que ocurren juntas, de izquierda a derecha a lo largo de cada palabra. Son las que introducen datos en la línea: el valor de una variable, el resultado de un cálculo, la salida de un comando.

ExpansiónFormaResultadoTema
Tilde~, ~/docs, ~mariaEl directorio personal, propio o de otro usuario.—
Parámetros$var, ${var}, $1, ${var:-otro}El valor de una variable o de un argumento, con posibles transformaciones.5, 6 y 20
Aritmética$(( 2 + 3 * 4 ))El resultado del cálculo: 14.7
Sustitución de comandos$(date +%F)La salida del comando, sin los saltos de línea finales.5
terminal
$ args ~ "$HOME" $(( 60 * 24 )) "$(date +%F)"
4 argumentos: </home/ana> </home/ana> <1440> <2026-10-14>
$ args "~/docs" '$HOME'                    # entre comillas, la tilde no se expande
2 argumentos: <~/docs> <$HOME>

La tilde tiene una regla propia: solo se expande sin comillas y al comienzo de una palabra, o después de = o : en una asignación. Por eso destino=~/respaldos funciona, pero destino="~/respaldos" guarda literalmente la virgulilla, y un cd "$destino" posterior fallará con No such file or directory. Dentro de comillas se usa "$HOME/respaldos".

Lo importante de esta etapa es lo que no hace: el valor que inserta no se vuelve a analizar como código. Si una variable contiene $HOME, un ; o un |, esos caracteres llegan al comando como texto, sin ningún significado especial. Las comillas guardadas dentro de una variable, tampoco:

terminal
$ texto='hola; echo $HOME | wc'
$ args $texto
5 argumentos: <hola;> <echo> <$HOME> <|> <wc>

El ; y el | no separaron comandos, y $HOME no se expandió, porque la etapa 1, donde se reconocen los operadores, ya había pasado. Lo único que sí le ocurrió al valor fue la división en palabras de la etapa siguiente. Esa es la buena noticia: los datos no se convierten en código por accidente. La mala noticia viene ahora.

8. Etapa 5: la división en palabras

La división en palabras (word splitting) es la etapa en que Bash vuelve a cortar en palabras el resultado de las expansiones de la etapa 4 que no estaban entre comillas. Corta por los caracteres de la variable IFS, que por defecto son el espacio, el tabulador y el salto de línea.

Tres precisiones definen exactamente su alcance:

terminal
$ nombre="  Ana   María  Pérez "
$ args $nombre
3 argumentos: <Ana> <María> <Pérez>
$ args "$nombre"
1 argumentos: <  Ana   María  Pérez >
$ args $(printf 'linea uno\nlinea dos')      # también la salida de un comando
4 argumentos: <linea> <uno> <linea> <dos>

El caso más traicionero es el de la variable vacía. Sin comillas, una variable vacía no produce una palabra vacía: no produce nada, y el comando recibe un argumento menos de los que el script esperaba.

terminal
$ vacia=""
$ args $vacia
0 argumentos:
$ args "$vacia"
1 argumentos: <>
$ [ $vacia = "si" ] && echo coincide      # el comando recibe: [ = si ]
bash: [: =: unary operator expected

La división en palabras tiene una sola utilidad legítima: convertir deliberadamente un texto con espacios en varios argumentos. Casi nunca es lo que se quiere, y en los scripts modernos ese trabajo lo hacen los arrays (tema 8). El ejemplo que lo muestra mejor es el intento de guardar opciones en una variable:

terminal
$ opciones='-l "mis documentos"'
$ args $opciones
3 argumentos: <-l> <"mis> <documentos">

Las comillas que están dentro del valor son datos, no sintaxis: se reconocerían en la etapa 1, que ya pasó. La división corta por los espacios y deja las comillas pegadas a los pedazos. No existe forma de escribir el valor para que funcione; la solución es un array, y la veremos en el tema 8.

9. Etapa 6: los nombres de archivo

Después de la división, cada palabra que contenga *, ? o [ sin comillas se trata como un patrón y se reemplaza por la lista de archivos que coinciden, en orden alfabético. Como en la etapa anterior, esto incluye a las palabras que salieron de una variable sin comillas:

terminal · en un directorio con informe.txt y notas.txt
$ args *.txt
2 argumentos: <informe.txt> <notas.txt>
$ nota="Total: 3 * 4"
$ echo $nota
Total: 3 informe.txt notas.txt 4
$ echo "$nota"
Total: 3 * 4

El echo $nota es un error silencioso típico: el resultado depende de qué archivos haya en el directorio donde se ejecuta el script. En el tuyo funciona; en el servidor muestra una lista de archivos.

La otra regla que conviene recordar es qué pasa cuando un patrón no coincide con nada: Bash lo deja tal cual, con el asterisco incluido.

terminal · en un directorio sin archivos PDF
$ args *.pdf
1 argumentos: <*.pdf>
$ gzip *.pdf
gzip: *.pdf: No such file or directory

En la terminal es apenas un mensaje. En un script, un bucle que recorre *.log en un directorio vacío ejecuta una vez su cuerpo con el nombre *.log, literalmente. Por eso el script del tema 1 tenía la línea [[ -e "$archivo" ]] || continue. Bash ofrece una opción, shopt -s nullglob, que hace que un patrón sin coincidencias desaparezca; la usaremos en el tema 14.

10. Etapa 7: la eliminación de comillas

Las comillas y barras invertidas de la línea original cumplieron su función en las etapas anteriores: agrupar palabras y proteger de las expansiones. En la etapa 7 se eliminan, y el comando nunca las ve. Por eso partes con distinto tipo de comillas, escritas juntas, forman una sola palabra:

terminal
$ args "a"'b'\c
1 argumentos: <abc>
$ args "Hola, $USER"'. Saldo: $0'
1 argumentos: <Hola, ana. Saldo: $0>
$ args ""                                  # una palabra vacía, explícita
1 argumentos: <>

Solo se eliminan las comillas que venían escritas en la línea. Las que llegaron dentro del valor de una variable, como en el ejemplo de opciones, son datos y se conservan.

11. Etapa 8: redirigir y ejecutar

Con la lista final de palabras, Bash hace dos cosas. Primero aplica las redirecciones, de izquierda a derecha. La palabra que sigue a un > también pasó por las expansiones, pero debe dar exactamente un nombre de archivo; si la división la parte en dos, Bash se niega:

terminal
$ salida="mi informe.txt"
$ echo hola > $salida
bash: $salida: ambiguous redirect
$ echo hola > "$salida"                     # correcto

Que se apliquen de izquierda a derecha explica por qué importa el orden de 2>&1, como viste en el curso de Terminal: cmd > log 2>&1 manda las dos salidas al archivo, mientras que cmd 2>&1 > log manda los errores a donde iba la salida antes de redirigirla, es decir, a la pantalla.

Después, Bash toma la primera palabra como nombre del comando y lo busca en este orden: una función con ese nombre, un comando interno, y por último un programa en los directorios de PATH. El resto de las palabras son sus argumentos. Desde este momento, el trabajo de Bash terminó: el comando recibe una lista de cadenas y no sabe nada de comillas, variables ni comodines.

12. Una línea, de principio a fin

Sigamos una línea realista por todas las etapas. Es parte de un script de respaldo, con estas variables y archivos:

respaldo.sh · fragmento
nombre="proyecto"
registro="/tmp/registro respaldo.log"
# en el directorio actual hay: docs/  fotos/  app.conf  red.conf
tar -czf ~/respaldos/$nombre-$(date +%F).tgz {docs,fotos} *.conf > "$registro" 2>&1
EtapaQué ocurre
1. LeerPalabras: tar, -czf, ~/respaldos/$nombre-$(date +%F).tgz, {docs,fotos}, *.conf. Operadores de redirección: > "$registro" y 2>&1. El espacio dentro de $(date +%F) no corta, porque está dentro de la sustitución.
2. EstructuraUn comando simple, tar, con dos redirecciones que se guardan para la etapa 8.
3. Llaves{docs,fotos} → docs fotos.
4. Parámetros~ → /home/ana; $nombre → proyecto; $(date +%F) → 2026-10-14. En la redirección, "$registro" → "/tmp/registro respaldo.log".
5. División/home/ana/respaldos/proyecto-2026-10-14.tgz no tiene espacios: sigue siendo una palabra. $registro estaba entre comillas: no se divide.
6. Comodines*.conf → app.conf red.conf.
7. ComillasSe quitan las de "$registro": queda /tmp/registro respaldo.log, una sola palabra.
8. EjecutarLa salida estándar va a /tmp/registro respaldo.log y los errores al mismo lugar. Se ejecuta tar.

Lo que tar recibe, finalmente, son seis argumentos:

los argumentos de tar
<-czf> </home/ana/respaldos/proyecto-2026-10-14.tgz> <docs> <fotos> <app.conf> <red.conf>

La línea funciona, pero tiene una fragilidad. Supón que mañana alguien cambia la primera línea a nombre="mi proyecto". En la etapa 5, la ruta del archivo se parte en dos: /home/ana/respaldos/mi y proyecto-2026-10-14.tgz. tar crea un archivo llamado mi e intenta incluir en él un archivo inexistente. La corrección es entrecomillar la expansión; como la tilde no se expande entre comillas, se usa $HOME:

respaldo.sh · corregido
tar -czf "$HOME/respaldos/$nombre-$(date +%F).tgz" {docs,fotos} *.conf > "$registro" 2>&1

Las llaves y el *.conf quedan sin comillas a propósito: ahí sí queremos que Bash genere varias palabras.

13. Qué protege cada tipo de comillas

Con el modelo de las ocho etapas, cada forma de entrecomillar se describe con precisión por las etapas que desactiva:

EtapaSin comillasComillas dobles "…"Comillas simples '…'
1. Espacios y operadores cortanSíNoNo
3. LlavesSíNoNo
4. TildeSíNoNo
4. $var, $(( )), $( )SíSíNo
5. División en palabrasSíNoNo
6. ComodinesSíNoNo

La columna central es la que hace a las comillas dobles tan útiles en un script: dejan entrar los datos (variables y sustituciones) pero impiden que esos datos se dividan o se expandan como comodines. Las comillas simples lo bloquean todo, y sirven para texto literal: patrones de grep, programas de awk, mensajes con signos de dólar.

Dentro de las comillas dobles, cuatro caracteres siguen siendo especiales: $, la comilla invertida `, la barra invertida \ cuando precede a uno de ellos o a ", y el propio ". Para incluir una comilla doble se escribe \"; para un dólar literal, \$. Dentro de comillas simples no hay forma de incluir una comilla simple: se cierra, se escribe \' y se vuelve a abrir, como en 'It'\''s'.

Una tercera forma: $'…'

Bash tiene unas comillas especiales, $'…', que se comportan como las simples pero interpretan secuencias de escape como \n (salto de línea), \t (tabulador) o \'. Son útiles para escribir caracteres invisibles: IFS=$'\n' asigna a IFS solo el salto de línea. Las usaremos en el tema 16.

14. Ver lo que Bash hace: set -x

No hace falta deducir de memoria qué recibió cada comando. La opción -x hace que Bash muestre cada comando después de todas las expansiones, justo antes de ejecutarlo, precedido de un +. Cuando un argumento contiene espacios o caracteres especiales, lo muestra entre comillas simples, para que se vea dónde empieza y dónde termina:

terminal
$ destino="/home/ana/mis respaldos"
$ set -x                                    # activar la traza
$ cp ~/docs/{informe,notas}.txt "$destino"
+ cp /home/ana/docs/informe.txt /home/ana/docs/notas.txt '/home/ana/mis respaldos'
$ cp ~/docs/informe.txt $destino
+ cp /home/ana/docs/informe.txt /home/ana/mis respaldos
cp: target 'respaldos': No such file or directory
$ set +x                                    # desactivarla

En la segunda traza se ve el problema sin necesidad de razonarlo: la ruta aparece sin comillas, partida en dos, y cp interpretó la última palabra como destino. Para un script entero, se ejecuta con bash -x script.sh, o se escribe set -x en la parte que se quiere observar. El tema 26 trata la depuración en detalle.

15. Errores clásicos, explicados

Con el modelo completo, los errores más comunes de los scripts dejan de ser misterios. Cada uno ocurre en una etapa concreta:

LíneaSíntomaEtapaCorrección
rm $archivoCon espacios en el nombre, borra otros archivos o falla.5rm -- "$archivo"
[ $resp = si ]unary operator expected si la variable está vacía.5[ "$resp" = si ], o [[ $resp = si ]]
echo $mensajeAparece una lista de archivos donde había un *.6echo "$mensaje"
for f in $(ls)Los nombres con espacios se parten en varios.5for f in *
for f in *.logSin archivos, el bucle recibe el texto *.log.6[[ -e $f ]] || continue, o nullglob
echo {1..$n}Muestra {1..5} literalmente.3seq 1 "$n"
dir="~/datos"cd "$dir" no encuentra el directorio.4dir="$HOME/datos"
total = 0total: command not found1total=0
if [$x -gt 0][5: command not found1if [ "$x" -gt 0 ]: los corchetes necesitan espacios.
echo hola > $salidaambiguous redirect5 y 8> "$salida"
cmd $opcionesLas comillas guardadas en la variable llegan al comando.5 y 7Un array: cmd "${opciones[@]}" (tema 8)

El error de if [$x -gt 0] merece un comentario: [ no es un signo de puntuación, sino un comando, otro nombre de test. Como cualquier comando, se separa de sus argumentos con espacios. Sin el espacio, la etapa 1 produce la palabra [$x, que tras la expansión es [5, y Bash busca un programa con ese nombre. Lo veremos en el tema 11.

16. La regla de oro

De todo lo anterior se desprende una regla sencilla, que evita la mayoría de los errores de esta tabla:

Entrecomilla toda expansión

Escribe siempre "$var", "${var}", "$(comando)" y "$@" entre comillas dobles, salvo que quieras, a propósito, que el resultado se divida en palabras o se expanda como comodín. Lo excepcional debe ser la falta de comillas, y cuando ocurra, conviene que un comentario explique por qué.

Hay lugares donde Bash no aplica la división en palabras ni los comodines, y por eso las comillas no son necesarias: el lado derecho de una asignación (copia=$original), el interior de [[ ]] y la palabra de un case. Ponerlas igual no hace daño, y muchos prefieren hacerlo para no tener que recordar las excepciones. Lo que nunca se entrecomilla es lo que debe expandirse como patrón: *.txt, {a,b}, ~.

Esta regla tiene un aliado: ShellCheck, que presentamos en el tema 1, señala cada expansión sin comillas con el aviso SC2086: Double quote to prevent globbing and word splitting. Es, con diferencia, el aviso que más vas a ver, y el tema 4 lo pone a trabajar.

17. Resumen

18. Preguntas de repaso

Intenta responder cada una antes de abrir la respuesta. La función args de la sección 2 te permite comprobar tus respuestas.

1. ¿Por qué echo no sirve para saber cuántos argumentos recibe un comando?

Porque muestra sus argumentos unidos por un espacio. Una palabra con un espacio dentro y dos palabras separadas se ven exactamente igual.

2. ¿Cuál es el orden de las expansiones?

Llaves; luego tilde, parámetros y variables, aritmética y sustitución de comandos (juntas, de izquierda a derecha); luego división en palabras; luego nombres de archivo. Al final, la eliminación de comillas.

3. ¿Qué recibe args en args a#b #c?

Un solo argumento, a#b. El primer # está dentro de una palabra y es un carácter común; el segundo está al comienzo de una palabra e inicia un comentario.

4. ¿Qué ocurre al ejecutar contador = 5, y por qué?

La etapa 1 corta la línea en tres palabras, y la primera, contador, se toma como nombre de un comando. La respuesta es contador: command not found. Una asignación no admite espacios alrededor del =.

5. Con n=4, ¿por qué echo {1..$n} no muestra 1 2 3 4?

Porque la expansión de llaves ocurre antes que la de variables. En ese momento el texto es {1..$n}, que no es un rango válido, y queda sin cambios. Cuando luego se reemplaza $n, las llaves ya no se procesan.

6. ¿Cuántos argumentos recibe args $v si v=" a b "? ¿Y args "$v"?

Sin comillas, dos: a y b; los espacios repetidos y los de los extremos desaparecen. Con comillas, uno: < a b >, con todos sus espacios.

7. ¿Por qué [ $resp = si ] da unary operator expected cuando resp está vacía?

Porque una variable vacía sin comillas no produce ningún argumento. El comando [ recibe = si ], y encuentra un operador donde esperaba un valor. Con "$resp" recibe una cadena vacía y la comparación funciona.

8. Si texto='a; ls', ¿ejecuta ls la línea echo $texto?

No. El ; solo funciona como operador si aparece en la etapa 1. Cuando la variable se expande, en la etapa 4, su contenido es texto. echo recibe a; y ls como argumentos y los muestra.

9. ¿Por qué no funciona guardar -l "mis documentos" en una variable y pasarla como ls $opciones?

Porque las comillas que están dentro del valor son datos, no sintaxis. La división en palabras corta por los espacios y produce -l, "mis y documentos", con las comillas pegadas. La solución es usar un array.

10. ¿Qué recibe gzip en gzip *.log si no hay ningún archivo .log?

El texto literal *.log, porque un patrón sin coincidencias se deja tal cual. gzip responde que no existe un archivo con ese nombre.

11. ¿Por qué dir="~/datos"; cd "$dir" falla?

Porque la tilde solo se expande sin comillas. La variable contiene literalmente ~/datos, y cd busca un directorio llamado ~. Se corrige con dir="$HOME/datos" o con dir=~/datos.

12. ¿Qué etapas desactivan las comillas dobles, y cuáles no?

Desactivan el corte por espacios y operadores, las llaves, la tilde, la división en palabras y los comodines. Permiten las variables, la aritmética y la sustitución de comandos.

13. ¿Qué significa ambiguous redirect?

Que la palabra que sigue a un operador de redirección, después de las expansiones, dio más de un nombre (o ninguno). Ocurre típicamente con una variable sin comillas que contiene espacios. Se corrige entrecomillándola.

14. ¿Por qué if [$x -gt 0] da un error como [5: command not found?

Porque [ es un comando y necesita estar separado por espacios. Sin el espacio, [$x forma una sola palabra, que tras la expansión es [5, y Bash busca un programa con ese nombre.

15. ¿Qué muestra set -x?

Cada comando justo antes de ejecutarse, después de todas las expansiones, precedido de +. Los argumentos con espacios o caracteres especiales aparecen entre comillas simples, de modo que se ve qué recibe exactamente el programa.

16. ¿En qué lugares no hacen falta comillas alrededor de una variable?

En el lado derecho de una asignación, dentro de [[ ]] y en la palabra de un case, porque allí no se aplican la división en palabras ni los comodines. Ponerlas no hace daño.

Para practicar

  1. Define la función args o guárdala como script en ~/bin/args. Pruébala con todos los ejemplos del tema.
  2. Predice, antes de ejecutarlo, cuántos argumentos recibe args x{1,2} "$HOME" '$HOME' ~ "a b"c. Compruébalo.
  3. Crea un directorio de pruebas con los archivos uno.txt, dos.txt y mi informe.txt. Ejecuta for f in $(ls); do args "$f"; done y luego for f in *; do args "$f"; done. Explica la diferencia.
  4. En ese mismo directorio, guarda m="Precio: 2 * 3" y compara echo $m con echo "$m". Repite en un directorio vacío.
  5. Prueba n=5; echo {1..$n} y luego seq 1 "$n".
  6. Activa set -x y ejecuta la línea tar de la sección 12 con tar -tzf en lugar de -czf, o simplemente con args en lugar de tar. Observa la traza.
  7. Escribe un pequeño script con tres de los errores de la tabla de la sección 15 y pásalo por shellcheck. ¿Los detecta todos?