← Terminal y Línea de Comandos en Linux Bloque 5 · Redirecciones y tuberías · Tema 25 de 43

Tema 25

Encadenar y sustituir comandos

La tubería une comandos por sus datos. Hay otras dos maneras de unirlos, igual de útiles. Una es por su resultado: «haz esto, y solo si salió bien, haz aquello». La otra es incrustar la salida de un comando dentro de la línea de otro, como si la hubieras tecleado. Con las tres, una sola línea deja de ser una orden y se convierte en un pequeño programa. Es el último paso antes de los scripts, y el cierre del bloque.

1. Tres maneras de unir comandos

Se unen por…OperadoresSignificaTema
Sus datos|Lo que uno escribe, el otro lo lee.24
Su orden y su resultado; && ||Uno se ejecuta después del otro, siempre o según cómo terminó el primero.Este
Su texto$( )La salida de uno pasa a formar parte de la línea del otro.Este

Las tres se mezclan libremente en una misma línea. Lo que tienen en común los operadores de la segunda fila es que no conectan ningún canal: cada comando conserva su entrada y su salida. Lo único que deciden es si y cuándo se ejecuta el siguiente.

2. El punto y coma: uno después del otro

El punto y coma separa comandos escritos en la misma línea. La shell los ejecuta en orden, de a uno, esperando a que cada uno termine. Es exactamente lo mismo que escribirlos en líneas separadas:

terminal
$ cd ~/practica
$ date; whoami; pwd
lun 12 oct 2026 20:15:08 -03
ana
/home/ana/practica

Lo que define al punto y coma es que es incondicional. El comando siguiente se ejecuta haya pasado lo que haya pasado con el anterior:

terminal
$ ls nada.txt; echo "Sigo de todos modos"
ls: cannot access 'nada.txt': No such file or directory
Sigo de todos modos

Sirve para tareas independientes entre sí, y para dejar encargada una secuencia larga sin quedarse esperando entre un paso y otro. Cuando los pasos dependen unos de otros, es la herramienta equivocada, como veremos en la sección 6.

3. El código de salida como verdadero o falso

Para ejecutar según el resultado hace falta saber cuál fue. Lo dice el código de salida, que conocemos desde el tema 3 y que en el tema 22 llamamos la cuarta vía de un comando: un número que cada programa deja al terminar, y que la shell guarda en $?.

CódigoSignificaPara la shell es
0El comando tuvo éxito.Verdadero
Cualquier otro, de 1 a 255El comando falló. El número indica de qué manera.Falso

Quien haya programado en otros lenguajes notará que está al revés de lo habitual, donde el cero es falso. La razón es práctica: hay una sola manera de salir bien y muchas de salir mal, y conviene tener un número para cada una.

Hay dos comandos que no hacen nada más que terminar con un código fijo. Parecen inútiles, y son la forma más limpia de experimentar:

terminal
$ true; echo $?
0
$ false; echo $?
1

Qué cuenta como «éxito» lo define cada programa, y conviene consultarlo, porque no siempre es «no hubo errores». El caso más útil es grep: sale con 0 si encontró alguna línea y con 1 si no encontró ninguna, lo que lo convierte en una pregunta sobre el contenido de un archivo.

4. && : solo si salió bien

En comando1 && comando2, el segundo comando se ejecuta solo si el primero tuvo éxito, es decir, si terminó con código 0.

Los operadores condicionales: después del primer comando, si su código de salida es cero se sigue por el operador doble ampersand; si es distinto de cero, por la doble barra éxito (0) && fallo (≠ 0) || comando 1 comando 2 comando 3
El código de salida del primer comando decide qué camino se toma: && continúa tras un éxito, || tras un fallo.

Se lee como una «y» con sentido de condición: «haz esto, y si funcionó, haz aquello».

terminal
$ ls notas.txt && echo "El archivo está"
notas.txt
El archivo está
$ ls nada.txt && echo "El archivo está"
ls: cannot access 'nada.txt': No such file or directory

En el segundo caso, ls falló y el echo no llegó a ejecutarse. Es el operador para encadenar pasos donde cada uno supone que el anterior salió bien, que es lo normal:

LíneaSe lee
mkdir proyecto && cd proyectoCrear el directorio, y si se pudo, entrar en él.
sudo apt update && sudo apt upgradeActualizar la lista de paquetes, y si funcionó, actualizar los paquetes.
tar -czf copia.tar.gz datos && rm -r datosEmpaquetar, y solo si el paquete se creó, borrar el original.
cd /srv/app && git pull && ./desplegar.shTres pasos; el primero que falle detiene la cadena.

La tercera fila muestra por qué importa. Con un punto y coma, un tar que fallara por falta de espacio sería seguido igualmente por el borrado, y los datos se perderían sin copia.

5. || : solo si salió mal

En comando1 || comando2, el segundo comando se ejecuta solo si el primero falló, es decir, si terminó con un código distinto de 0.

Es el complemento, y se lee como una «o»: «haz esto, o si no se pudo, haz aquello». Sirve para reaccionar a un fallo: avisar, intentar una alternativa, o abandonar.

terminal
$ cd /noexiste || echo "No se pudo entrar al directorio"
bash: cd: /noexiste: No such file or directory
No se pudo entrar al directorio
$ cd /tmp || echo "No se pudo entrar al directorio"
$ cd -
/home/ana/practica
LíneaSe lee
ping -c 1 servidor || echo "Sin conexión"Probar la conexión, y si falla, avisar.
mkdir datos 2>/dev/null || echo "Ya existía"Crear el directorio; si no se pudo, decir por qué.
comando-nuevo || comando-viejoIntentar con una herramienta, y si no está o falla, con otra.
cd /srv/app || exit 1En un script: si no se puede entrar, terminar ahí.

Los nombres de los dos operadores vienen de la lógica, y el comportamiento también. Para saber si «A y B» es verdadero, si A resulta falso ya no hace falta mirar B. Para «A o B», si A resulta verdadero tampoco. La shell evalúa lo mínimo necesario, y ese ahorro es justamente la ejecución condicional.

6. Por qué cd y rm se unen con &&

La diferencia entre ; y && deja de ser una sutileza cuando el segundo comando es destructivo. Hay un caso que aparece en miles de scripts, y que conviene tener grabado:

dos líneas que parecen iguales
cd /tmp/obra; rm -rf *              # peligrosa
cd /tmp/obra && rm -rf *            # correcta

Si el directorio existe, las dos hacen lo mismo. La diferencia aparece el día que /tmp/obra no existe, o está mal escrito:

Con ;Con &&
cd fallaSe informa el error, y se continúa.Se informa el error, y se detiene.
rm -rf *Se ejecuta en el directorio donde se estaba.No se ejecuta.
ResultadoSe borró otro directorio: quizá el personal.Nada.
Todo comando que depende de un cd va unido a él con &&

Un cd seguido de punto y coma supone que el cambio de directorio siempre funciona. Cuando no funciona, lo que viene después actúa en el lugar equivocado. La regla es general: el punto y coma solo es adecuado entre comandos que no dependen uno del otro. Ante la duda, &&.

7. Combinar && y ||

Los dos operadores se pueden encadenar en una misma línea. La shell los recorre de izquierda a derecha, y en cada operador decide con el código de salida de lo último que se ejecutó:

terminal
$ ls notas.txt &> /dev/null && echo "Existe" || echo "No existe"
Existe
$ ls nada.txt &> /dev/null && echo "Existe" || echo "No existe"
No existe

La forma A && B || C se usa mucho como un «si… entonces… si no…» de una línea, y casi siempre funciona como tal. No lo es exactamente, y la diferencia es una trampa:

Si A…y B…Se ejecuta C
Tiene éxitoTiene éxitoNo
Falla(no se ejecuta)Sí
Tiene éxitoFallaSí: se ejecutan B y también C

En la tercera fila corren las dos ramas, cosa que un «si no» verdadero nunca haría. Con un echo en el lugar de B, que prácticamente no falla, la construcción es segura. Con un comando que puede fallar, hay que usar la estructura condicional propiamente dicha, if, que es materia del curso siguiente.

Dos precisiones sobre cómo se leen las líneas mixtas. && y || tienen la misma prioridad y se agrupan de izquierda a derecha. Y los dos «atan» más fuerte que el punto y coma: en a && b; c, el comando c se ejecuta siempre.

8. Comandos que son preguntas

Los operadores condicionales rinden de verdad con comandos cuyo único propósito es responder sí o no mediante su código de salida, sin escribir nada. Ya vimos uno; hay dos más que conviene conocer desde ahora:

ComandoPreguntaÉxito (0) si…
grep -q texto archivo¿El archivo contiene ese texto?Hay al menos una línea que lo contiene. La -q, de quiet, evita que la muestre.
[ -e ruta ]¿Existe?Hay un archivo o un directorio con ese nombre.
[ -f ruta ]¿Es un archivo común?Existe y es un archivo.
[ -d ruta ]¿Es un directorio?Existe y es un directorio.
[ -r ruta ], -w, -x¿Puedo leerlo, modificarlo, ejecutarlo?Tu usuario tiene ese permiso.
[ -s ruta ]¿Tiene contenido?Existe y no está vacío.
command -v nombre¿Existe ese comando?La shell lo encuentra.

El corchete es un comando, por extraño que parezca: se llama también test, y sus argumentos son lo que hay entre los corchetes. Por eso los espacios son obligatorios después del que abre y antes del que cierra.

terminal, en ~/practica
$ [ -d fotos ] && echo "fotos es un directorio"
fotos es un directorio
$ [ -e nada.txt ] || echo "nada.txt no existe"
nada.txt no existe
$ grep -q "pan" tareas.txt && echo "Hay que comprar pan"
Hay que comprar pan
$ command -v tree > /dev/null || echo "tree no está instalado"

Con ellos, las líneas se vuelven prudentes. [ -d respaldo ] || mkdir respaldo crea el directorio solo si no existe. [ -f config ] && cp config config.bak hace la copia solo si hay algo que copiar. El comando test tiene muchas más comprobaciones, de textos y de números, que se estudian a fondo al escribir scripts.

9. Agrupar: llaves y paréntesis

A veces se quiere tratar varios comandos como una unidad: redirigir la salida de todos a un mismo archivo, o hacer depender todo un grupo de una condición. Sin agrupar, una redirección afecta solo al comando que la lleva:

terminal
$ date; whoami > /tmp/quien.txt              # solo whoami va al archivo
lun 12 oct 2026 20:22:40 -03
$ { date; whoami; } > /tmp/quien.txt          # los dos
$ cat /tmp/quien.txt
lun 12 oct 2026 20:22:51 -03
ana

Hay dos formas de agrupar, con una diferencia de fondo:

{ comandos; }( comandos )
Se ejecutan enLa shell actualUna shell aparte: una subshell
Un cd dentro del grupoCambia tu directorioNo lo cambia: afecta solo al grupo
EscrituraEspacios junto a las llaves, y punto y coma antes de cerrarSin requisitos

La segunda fila es la razón de ser de los paréntesis. Permiten hacer algo en otro directorio sin tener que volver:

terminal
$ (cd /etc && ls | wc -l)
218
$ pwd                                           # seguimos donde estábamos
/home/ana/practica

El cd ocurrió dentro de un proceso hijo, que terminó. Es el mismo principio que vimos con sudo cd y con las tuberías: lo que un proceso cambia de su propio entorno no alcanza al que lo lanzó. Aquí se lo aprovecha a propósito.

Un grupo cuenta como un solo comando para los operadores: cd datos && { limpiar; procesar; } ejecuta los dos pasos solo si se pudo entrar.

10. La sustitución de comandos: $( )

La tercera manera de unir comandos es de otra naturaleza. No conecta canales ni decide ejecuciones: reescribe la línea.

La sustitución de comandos, $(comando), ejecuta el comando que está entre los paréntesis y reemplaza toda la construcción por lo que ese comando escribió en su salida estándar.

Es una de las expansiones que enumeramos en el tema 9, y como todas ellas ocurre antes de que el comando principal se ejecute:

terminal
$ echo "Hoy es $(date +%A)"
Hoy es lunes
$ echo "Hay $(ls | wc -l) elementos en $(pwd)"
Hay 4 elementos en /home/ana/practica
lo que hace la shell con la segunda línea
echo "Hay $(ls | wc -l) elementos en $(pwd)"
           └─────┬─────┘              └─┬──┘
            se ejecuta: 4          se ejecuta: /home/ana/practica

echo "Hay 4 elementos en /home/ana/practica"        ← la línea que recibe echo

echo nunca supo que hubo otros comandos: recibió un texto ya armado. La diferencia con la tubería es precisamente esa. En una tubería, la salida de un comando llega al otro como datos, por la entrada. En la sustitución, llega como texto de la línea: como argumentos.

Por eso sirve con los comandos que no leen su entrada, los mismos para los que en el tema anterior necesitamos xargs:

terminal (el tamaño y la fecha varían)
$ ls -l $(which bash)
-rwxr-xr-x 1 root root 1446024 mar 31  2024 /usr/bin/bash
$ wc -l $(find . -name "*.txt")
    1 ./notas.txt
  361 ./informe.txt
    5 ./tareas.txt
  367 total

En el primer ejemplo, which bash responde con una ruta, y esa ruta se convierte en el argumento de ls -l. Dos preguntas —«¿dónde está?» y «¿qué permisos tiene?»— en una línea.

La usamos una vez sin explicarla, en el tema 15, para poner la fecha en el nombre de un respaldo: tar -czf practica-$(date +%F).tar.gz. Es su uso más típico: fabricar un nombre con un dato que solo se conoce en el momento.

11. $( ) en detalle

Cuatro propiedades completan el cuadro.

Se quitan los saltos de línea finales. La salida de date termina en un salto de línea, que estorbaría en medio de una frase. La shell lo elimina.

Solo captura la salida estándar. Los mensajes de error del comando interior siguen yendo a la pantalla, por el canal 2, como en una tubería.

Se puede anidar. Los paréntesis permiten poner una sustitución dentro de otra sin ambigüedad:

terminal
$ echo "Este directorio se llama $(basename $(pwd))"
Este directorio se llama practica

basename entrega el último componente de una ruta. Primero se resuelve la sustitución de adentro, pwd; su resultado es el argumento de basename.

Sin comillas, el resultado se parte en palabras. Es el punto delicado. El texto que deja la sustitución pasa por la separación en argumentos y por la expansión de comodines, como si lo hubieras escrito. Si contiene espacios, se convierte en varios argumentos:

Se escribeSi la salida es mis notas.txt, el comando recibe
cat $(comando)Dos argumentos: mis y notas.txt.
cat "$(comando)"Un argumento: mis notas.txt.
Entre comillas dobles, salvo que se quieran varios argumentos

Cuando la sustitución produce un valor —una ruta, una fecha, un número— se la escribe entre comillas dobles, "$(…)", que la protegen sin impedir que se expanda. Sin comillas se la deja solo cuando se espera una lista y cada elemento debe ser un argumento, como en el ejemplo de wc -l $(find …); y eso falla si algún nombre tiene espacios.

Existe una forma antigua de la misma construcción, con comillas invertidas o acentos graves: `date` equivale a $(date). La verás en scripts viejos y en mucha documentación. Hace lo mismo, pero se confunde a la vista con las comillas simples y no se puede anidar con comodidad; la forma con paréntesis es la que conviene escribir.

12. Sustitución, tubería o xargs

Hay ya tres maneras de hacer que el resultado de un comando llegue a otro. No son intercambiables:

a | bb $(a)a | xargs b
La salida de a llega a b comoDatos, por la entradaArgumentosArgumentos
Sirve si b esUn filtroCualquier comandoCualquier comando
Los dos corren a la vezSíNo: a termina antesSí
Con una salida enormeSin límitePuede dar Argument list too longLa reparte en varias ejecuciones
El resultado puede ir en el medio de la línea—Sí, donde se escribaSolo con -I
SituaciónConviene
El segundo comando procesa texto: ordena, filtra, cuenta.Tubería
Hace falta un valor dentro de una línea: una fecha, una ruta, una cantidad.Sustitución
Hay que aplicar un comando a una lista de archivos, quizá larga.xargs, o find -exec

13. Un paso más: la sustitución de procesos

Queda un caso que las herramientas anteriores no cubren. Algunos comandos necesitan dos entradas, y las esperan como nombres de archivo. El ejemplo típico es diff, que compara dos archivos y que estudiamos en el tema 31. Una tubería solo puede alimentar una entrada.

Bash ofrece una construcción para eso, pariente de la anterior. <(comando) se reemplaza por el nombre de un archivo especial, del que se puede leer la salida de ese comando:

terminal
$ echo <(ls)                                    # en qué se convierte
/dev/fd/63
$ diff <(ls /bin) <(ls /usr/bin)                # comparar dos listados
$ 

diff recibió dos nombres de archivo y los comparó, sin saber que eran las salidas de dos comandos. No escribió nada, que en su idioma significa «son idénticos»: una confirmación, por otro camino, de que /bin es un enlace a /usr/bin, como vimos en el tema 8.

El nombre /dev/fd/63 es un descriptor de archivo, de los del tema 22. No hace falta usar esta construcción a menudo; conviene saber que existe para el día en que haga falta comparar o combinar dos salidas sin crear archivos temporales.

14. De la línea al programa

Con todos los operadores del bloque se pueden escribir líneas que hacen un trabajo completo, con sus precauciones. Por ejemplo, un respaldo del directorio de práctica:

terminal
$ mkdir -p /tmp/respaldos && tar -czf "/tmp/respaldos/practica-$(date +%F).tar.gz" -C ~ practica && echo "Respaldo listo" || echo "El respaldo falló" >&2
Respaldo listo
ParteHaceRecurso
mkdir -p /tmp/respaldosAsegura que el destino existe.Tema 11
&&Sigue solo si se pudo.Este tema
tar -czf "…" -C ~ practicaCrea el paquete.Tema 15
$(date +%F)Pone la fecha de hoy en el nombre.Este tema
&& echo "Respaldo listo"Confirma, si todo salió bien.Este tema
|| echo "…" >&2Avisa si algo falló, por el canal de errores.Tema 23

La línea funciona, y marca también un límite. Es larga, cuesta leerla, y la próxima vez habrá que reescribirla o buscarla en el historial. Cuando una línea llega a este punto, ha dejado de ser un comando y es un programa que pide un archivo propio: un script. Guardada en un archivo, con cada paso en su renglón, se vuelve legible, repetible y fácil de mejorar. Ese paso lo damos en el tema 41.

La tabla completa de operadores que controlan la ejecución, para cerrar el bloque:

Se escribeSignifica
a ; ba, y después b, pase lo que pase.
a && bb solo si a tuvo éxito.
a || bb solo si a falló.
a | bLa salida de a es la entrada de b; los dos a la vez.
a &a en segundo plano: la shell no espera a que termine. Tema 33.
{ a; b; }Un grupo, en la shell actual.
( a; b )Un grupo, en una subshell.
$(a)Se reemplaza por la salida de a.
<(a)Se reemplaza por un archivo del que se lee la salida de a.

La quinta fila completa la lista y previene una confusión: un solo & no tiene nada que ver con &&. Lanza el comando sin esperar su resultado, y lo vemos al estudiar los procesos.

Con este tema termina el quinto bloque. Sabemos conectar comandos de todas las formas posibles; lo que falta es conocer mejor las piezas que se conectan. El sexto bloque está dedicado a los filtros de texto, y empieza en el tema 26 con los que ya usamos de pasada: sort, uniq, cut y tr.

15. Resumen

16. Preguntas de repaso

Intenta responder cada una antes de abrir la respuesta.

1. ¿Qué diferencia hay entre a ; b y a && b?

Con punto y coma, b se ejecuta siempre. Con &&, solo si a terminó con éxito.

2. ¿Qué código de salida significa éxito, y cómo lo interpreta la shell?

El 0, que la shell trata como verdadero. Cualquier otro valor indica un fallo y cuenta como falso.

3. ¿Qué muestra false && echo "uno"; echo "dos"?

Solo dos. El primer echo no se ejecuta porque false falla; el segundo va después de un punto y coma, y se ejecuta siempre.

4. ¿Qué muestra false || echo "uno"? ¿Y true || echo "uno"?

La primera muestra uno, porque false falló. La segunda no muestra nada.

5. ¿Por qué es peligrosa la línea cd /tmp/datos; rm -rf *?

Porque si el cd falla, el rm se ejecuta igual, en el directorio donde se estaba. Con &&, el borrado solo ocurre si se pudo entrar.

6. Escribe una línea que cree el directorio salida solo si no existe.

[ -d salida ] || mkdir salida. También sirve mkdir -p salida.

7. ¿En qué caso a && b || c no se comporta como «si a, entonces b; si no, c»?

Cuando a tiene éxito y b falla: entonces se ejecuta también c.

8. ¿Qué pregunta hace grep -q "error" registro.log, y cómo responde?

Si el archivo contiene la palabra. No escribe nada: responde con el código de salida, 0 si la encontró y 1 si no.

9. ¿Por qué hacen falta los espacios en [ -f archivo ]?

Porque el corchete es un comando, y lo que sigue son sus argumentos. Sin espacios, la shell no lo reconoce.

10. ¿Qué diferencia hay entre { cd /tmp; ls; } y ( cd /tmp; ls )?

Las llaves ejecutan en la shell actual: después, el directorio de trabajo es /tmp. Los paréntesis ejecutan en una subshell: el directorio de la shell no cambia.

11. ¿Qué hace $(comando)?

Ejecuta el comando y reemplaza la construcción por lo que escribió en su salida estándar, antes de ejecutar la línea que la contiene.

12. ¿Qué diferencia hay entre ls | wc -l y wc -l $(ls)?

En la primera, wc recibe la lista de nombres como datos y cuenta cuántos son. En la segunda recibe los nombres como argumentos, y cuenta las líneas de cada archivo.

13. ¿Por qué conviene escribir "$(comando)", con comillas?

Porque sin comillas el resultado se divide en palabras por los espacios, y un valor que los contenga llega al comando como varios argumentos.

14. ¿Qué es `date`?

La forma antigua de la sustitución de comandos, con comillas invertidas. Equivale a $(date), que es la forma recomendable.

15. ¿Cuándo conviene dejar de escribir una línea y pasar a un script?

Cuando la línea se vuelve larga y difícil de leer, o cuando se la va a necesitar otra vez. En un archivo, cada paso tiene su renglón y se puede revisar y mejorar.

Para practicar

  1. Predice y luego comprueba la salida de estas líneas: true && echo A; false && echo B; true || echo C; false || echo D; false; echo E.
  2. Predice y comprueba: true && false || echo X y false || true && echo Y. Explica cada una paso a paso.
  3. Escribe una línea que entre en /tmp/obra y liste su contenido solo si pudo entrar. Pruébala antes y después de crear el directorio.
  4. Escribe una línea que diga «Hay tareas pendientes» si tareas.txt existe y no está vacío, y «Nada que hacer» en caso contrario.
  5. Con grep -q, escribe una línea que agregue «Comprar leche» al final de una copia de tareas.txt solo si todavía no figura. Ejecútala dos veces y comprueba que la línea no se duplica.
  6. Guarda en un solo archivo de /tmp la salida de date, whoami, hostname y uname -r, usando un grupo entre llaves.
  7. Con paréntesis, cuenta cuántos elementos hay en /usr/share/doc sin cambiar tu directorio de trabajo. Verifica con pwd.
  8. Con la sustitución de comandos, muestra una frase como «Soy ana, estoy en equipo y son las 20:30», tomando cada dato de un comando.
  9. Crea un directorio en /tmp cuyo nombre incluya la fecha de hoy, y entra en él con una sola línea.
  10. Usa diff con la sustitución de procesos para comparar el listado de /etc/skel con el de los archivos ocultos de tu directorio personal: diff <(ls -A /etc/skel) <(ls -A ~).