detomas.net Curso de Bash desde cero
Descargar PDF

Curso de Bash desde cero / Capítulo 4

Comandos avanzados

Lectura de archivos

Consejo

Para desplazarte por la paginación es habitual usar flechas, barra espaciadora, scroll o PgUp/PgDown.

Búsqueda: grep

grep busca un texto dentro de archivos, o dentro de lo que le llegue de otro comando. Devuelve las líneas enteras en las que aparece.

grep "error" registro.log        # líneas que contienen "error"

Las opciones que más se usan:

Opción Qué hace
-i No distingue mayúsculas de minúsculas
-r Busca dentro de todo un directorio, recursivamente
-n Muestra el número de línea de cada resultado
-v Al revés: muestra las líneas que NO contienen el texto
-c En vez de las líneas, cuenta cuántas hay
-l Solo dice en qué archivos aparece, no muestra las líneas
-w Solo la palabra entera (grep -w red no encuentra «redes»)

Y se combinan como cualquier otra opción:

grep -rn "TODO" .            # dónde y en qué línea, por todo el proyecto
grep -ic "error" *.log       # cuántos errores por archivo, sin distinguir
                             # mayúsculas
grep -v "^#" config.txt      # el archivo sin sus líneas de comentario

Consejo

Ese ^# del último ejemplo es una expresión regular: el ^ significa «al principio de la línea». Con grep bastan tres símbolos para el 90% de los casos:

  • ^ principio de línea
  • $ final de línea
  • . un carácter cualquiera

Recuento: wc

wc (word count) cuenta lo que le eches.

Opción Cuenta
(ninguna) líneas, palabras y bytes
-l líneas
-w palabras
-c bytes
-m caracteres (distinto de bytes si hay tildes o eñes)
wc -l notas.txt              # cuántas líneas tiene
wc -lw notas.txt             # líneas y palabras

Los tres canales de un comando

Antes de redirigir nada hay que saber qué se redirige. Todo comando en Linux tiene tres canales abiertos, y cada uno tiene un número:

Nombre Qué es
0 stdin Por donde entra la información
1 stdout Por donde sale el resultado normal
2 stderr Por donde salen los mensajes de error

La clave está en que el resultado y los errores salen por sitios distintos, aunque los veas mezclados en pantalla. Eso es justo lo que permite separarlos:

ls /carpeta_que_no_existe > salida.txt

El archivo salida.txt queda vacío y el error sigue apareciendo en pantalla. Porque > redirige solo el canal 1, y el error va por el 2.

Redirecciones

Símbolo Qué hace
> Manda la salida a un archivo, sobrescribiéndolo
>> Manda la salida a un archivo, añadiendo al final
< Coge la entrada de un archivo
2> Manda solo los errores a un archivo
2>&1 Manda los errores al mismo sitio que la salida
&> Manda las dos cosas a la vez (atajo de > archivo 2>&1)
echo "texto" > notas.txt        # crea o sobrescribe notas.txt
echo "más texto" >> notas.txt   # añade una línea al final
sort < notas.txt                # ordena el contenido del archivo

ls /noexiste 2> errores.txt     # solo el error va al archivo
ls /noexiste &> todo.txt        # salida y error, los dos al archivo

Precaución

> borra el archivo antes de escribir, sin avisar. Si te equivocas y escribes > donde querías >>, te has cargado el contenido anterior. Cuando dudes, usa >>.

El agujero negro: /dev/null

/dev/null es un archivo especial que se traga todo lo que le mandes. Sirve para silenciar lo que no te interesa:

comando 2>/dev/null     # ejecuta y no me enseñes los errores
comando >/dev/null      # ejecútalo y no me enseñes el resultado
comando &>/dev/null     # ejecútalo en silencio absoluto

Aviso

Ojo con el orden en 2>&1: significa «manda el canal 2 donde está ahora el canal 1». Por eso va después de la redirección:

  • comando > archivo 2>&1 guarda las dos cosas en el archivo. Correcto.
  • comando 2>&1 > archivo NO hace lo mismo: cuando se lee 2>&1 el canal 1 todavía apunta a la pantalla, así que los errores se quedan en pantalla y solo la salida va al archivo.

Pipes

El símbolo | conecta la salida de un comando con la entrada del siguiente, sin pasar por ningún archivo. Es lo que hace de Bash una herramienta potente: comandos pequeños que se encadenan.

cat notas.txt | grep "bash" | wc -l

Se lee de izquierda a derecha: muestra el archivo, quédate con las líneas que contienen «bash», y cuenta cuántas son.

Algunos encadenados que se usan a diario:

ls -l | grep "^d"                    # solo los directorios
history | grep git                   # qué comandos de git he usado
ps aux | grep node                   # procesos de node en marcha
cat registro.log | tail -n 50        # las últimas 50 líneas

Consejo

Los pipes solo conectan el canal 1. Si quieres que también pasen los errores, usa |& (o 2>&1 |).

Variables de entorno

Puedes definir variables temporalmente (locales) o que existan en todo momento (globales).

Variable de shell

Vive solo en la terminal donde la creaste. Si desde ahí lanzas otro programa, ese programa no la ve.

Variable de entorno

Se crea con export. La diferencia es que la heredan los programas que lances desde esa terminal: pasa a formar parte de su entorno.

Aviso

«Exportar» una variable no la hace permanente. Cuando cierras la terminal desaparece igual que las demás. Lo único que consigues con export es que los programas hijos puedan verla.

Para que exista siempre hay que escribirla en un archivo de configuración, como se explica un poco más abajo.

Se ve muy claro con un ejemplo:

saludo="hola"           # variable de shell
export despedida="adiós"  # variable de entorno

# bash -c lanza una terminal hija:
bash -c 'echo "saludo=[$saludo] despedida=[$despedida]"'
# saludo=[] despedida=[adiós]

La hija no ve saludo, pero sí despedida.

Algunas variables de entorno que ya existen

echo $HOME     # /home/nica     -> tu directorio personal
echo $USER     # nica           -> tu nombre de usuario
echo $PWD      # /home/nica     -> el directorio en el que estás
echo $PATH     # /usr/local/bin:/usr/bin:/bin: ...

$PATH merece una parada. Es la lista de carpetas donde Bash busca los programas, separadas por dos puntos. Cuando escribes ls, Bash recorre esas carpetas por orden hasta encontrar un ejecutable llamado ls.

Por eso, para lanzar un script tuyo que está en el directorio actual hay que escribir ./script.sh y no script.sh a secas: el directorio actual no está en el PATH, y si lo estuviera sería un problema de seguridad (bastaría con dejar un archivo llamado ls en una carpeta para engañarte).

Crear una variable de entorno

export NOMBRE_VARIABLE="su valor"

Que sobreviva al cierre de la terminal

Hay que escribir esa línea en un archivo de configuración, que Bash lee solo al arrancar. Verás mencionados cuatro:

Archivo Cuándo se lee
~/.bashrc Cada vez que abres una terminal normal
~/.bash_profile Solo al iniciar sesión (por ejemplo, por SSH)
~/.bash_login Alternativa al anterior, si aquel no existe
~/.profile El más antiguo; lo leen también otras shells

Consejo

No te compliques: usa ~/.bashrc. Es el que se lee al abrir una terminal, que es el 95% de los casos. Los otros tres existen por razones históricas.

# Añadir la línea al final del archivo
echo 'export MI_VARIABLE="hola"' >> ~/.bashrc

# Aplicar los cambios sin cerrar la terminal
source ~/.bashrc

source (o su atajo .) hace que Bash relea el archivo en la sesión actual. Si no, tendrías que cerrar y volver a abrir la terminal.

Ejercicios

  1. Muestra todo el contenido de un archivo.

  2. Muestra el contenido paginado de un archivo.

  3. Muestra las 15 primeras líneas de un archivo.

  4. Muestra las 15 últimas líneas de un archivo.

  5. Busca una palabra en un archivo.

  6. Cuenta las líneas de un archivo.

  7. Redirige una salida y guárdala en un archivo.

  8. Añade una nueva salida al archivo anterior.

  9. Encadena 3 comandos con pipes.

  10. Crea una variable de shell y muéstrala.

  11. Lanza ls sobre una carpeta que no exista redirigiendo con > a un archivo. ¿Qué contiene el archivo? ¿Dónde ha ido a parar el mensaje de error? Repítelo con 2> y luego con &>.

  12. Busca una palabra en un archivo mostrando el número de línea, y después al revés: muestra las líneas que no la contienen.

  13. Cuenta cuántas veces aparece una palabra en un archivo, encadenando grep y wc.

  14. Muestra tu $PATH y cuenta cuántas carpetas tiene (pista: están separadas por :, y tr ':' '\n' las pone una por línea).