Algoritmos y Estructuras de Datos Herramientas Lenguaje de programación
!Prog C/C++ Rust
Linux Matemáticas
Mates Discretas
Programación Orientada a Objetos Sistemas Operativos

Shell y Bash

[date: 09-02-2024 13:27] [last modification: 28-05-2024 14:04]
[words: 3944] [reading time: 19min] [size: 72836 bytes]

En este artículo se hará un overview del uso de la shell, más concretamente Bash. Algunos temas a tratar serán comandos, expansiones, redirecciones, tratamiento de caracteres especiales…

Shell

El shell es un programa que provee una interfaz de usuario para acceder a los servicios del Sistema Operativo o ejecutar los diferentes programas. Este puede ser de varios tipos:

En este artículo se hablará de los shells CLI, dado que los otros se han diseñado para que sean intuitivos y sencillos de usar.

Shells CLI principales
shBourne Shell, el primer Shell en las primeras versiones de Unix.
cshC Shell, desarrollado para Unix BSD. Su principal diferencia es que su sintaxis se parecía a C, pero aún así era fácil y rápido de usar de forma interactiva.
tcshTenet C Shell, versión mejorada de csh y retrocompatible con esta.
kshKorn Shell, basada en sh y características de csh.
bashBourne-Again Shell
zshZ Shell, versión extendida de sh, con muchas características de bash, ksh y tcsh. Además, permite añadir temas y customizar el prompt.
PowerShellPowerShell, es el shell por defecto de Windows.
fishFriendly-Interactive Shell, es una shell exótica, lo que quiere decir que no sigue el estándar POSIX con el fin de ser más sencilla y rápida de usar.

Puedes ver una comparativa en Wikipedia. Nótese que en Unix, el shell no está integrado en el kernel, por tanto el usuario puede utilizar otros o escribir el suyo propio.

En Linux, el archivo /etc/shells contiene las rutas a las shells conocidas por el sistema y el archivo /etc/passwd especifica la shell por defecto de cada usuario (también se puede ver en la variable $SHELL). Se puede cambiar con el el comando chsh.

Línea de comandos

Nos centraremos en Bash, que cumple con la especificación POSIX y es la shell por defecto en muchos sistemas (no solo Linux). Actualmente, algunos prefieren zsh, pero todo lo que vamos a ver también se le puede aplicar.

bash se ejecuta como si fuese un comando más:

bash

Y para salir puedes ejecutar exit o Ctrl-D.

Funcionamiento de Bash

El shell es un proceso interactivo que ejecuta comandos, de los que se distinguen dos tipos:

Nótese que los comandos externos son ajenos al shell, almacenados como archivos ejecutables en algún lugar del sistema. Para encontrar estos comandos externos, se busca en las rutas que contenga la variable PATH. Esta es una lista de rutas separadas normalmente por :.

PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/opt/bin

Tenga en cuenta que se busca de forma lineal, por lo que conviene que las rutas más usadas se coloquen al inicio.

Se puede comprobar rápidamente si un comando es interno o externo de la siguiente forma:

type comando

Componentes de un comando y orden de evaluación

Cada comando tiene 3 componentes:

Nótese que en programas más complejos se puede distinguir otro componente, el subcomando, por ejemplo git clone.

Desde que introducimos un comando hasta que se ejecuta, el shell realiza los siguientes pasos en orden:

  1. Redirección E/S
  2. Sustitución de variables
  3. Sustitución de nombres de ficheros

Pero este comportamiento se modifica al usar eval:

  1. Primero se realizan todas las sustituciones
  2. Luego se ejecuta el comando

Ejecución en segundo plano

Varias formas de ejecutar un comando:

sleep 10     # Espera 10 segundos
sleep 10 &   # Lanza el mismo comando pero en segundo plano
             # Se puede lanzar otro comando
jobsMostrar los procesos en segundo plano
fg nTraer el proceso n al primer plano
bg nReanudar un proceso parado en segundo plano

A mayores, para manejar un proceso en primer plano se puede usar Ctrl-C para matarlo y Ctrl-Z para detener su ejecución.

Tabulador y edición de comandos

Uso de tabulador para completar comandos y rutas a archivos. Dando dos veces al tabulador se pueden listar todas las posibilidades.

Durante la edición de comandos en Bash, permite algunos atajos de teclado de Emacs por defecto. Puede usar set -o vim para usar los atajos de Vi.

Movimientos en Bash
Ctrl-A / Ctrl-EIr al inicio / final
Alt-B / Alt-FMoverse una palabra hacia delante / atrás
Ctrl-W / Alt-DBorrar palabra anterior / siguiente
Ctrl-U / Ctrl-KBorra hasta el principio / final
Ctrl-N / Ctrl-PComando siguiente / anterior
Ctrl-LLimpia pantalla
Alt-.Escribir el último argumento
Ctrl-YDeshacer
Ctrl-DSalir

Expansiones

Más información en man 7 glob

Una expansión en Bash es una secuencia que este interpreta y expande antes de ejecutar el comando. También se les llaman comodines o globbing. Su uso principal es para sustituir nombres de archivos (wildcards):

ls -l *.html  # Lista todos los archivos html

Nótese que si la expansión falla (no hay archivos que coincidan), el argumento se dejará como está.

Comodines para archivos
*Expande a 0 o más caracteres. No cuentan los archivos ocultos
?Expande a 1 carácter
[ ]Expande solo a los caracteres que estén entre los corchetes
[! ] [^ ]Igual que antes, pero que no estén los caracteres

Bash tiene algunos conjuntos de caracteres predeterminados:

Expansión de comandos
$(comando)Ejecuta un comando y obtiene su resultado (stdout).
`comando`Equivalente al anterior.
Otras expansiones
Hola{1,2,3}Generación de strings. Genera Hola1, Hola2 y Hola3 (importante que no haya espacios entre las comas).
Hola{1..3}Equivalente al anterior. Funciona con letras (según códigos ASCII).
Hola{0..50..5}Va de 0 a 50 saltando de 5 en 5.
~Expande al directorio de usuario.
~rootExpande al directorio del usuario especificado.

Historial de comandos

Bash mantiene una lista de los comandos previamente usados. Esta tiene un tamaño definido por HISTSIZE, por defecto 1000, y se almacena por defecto en el archivo ~/.bash_history.

Los parámetros que denoto entre corchetes pueden ser:

Gestión del historial
historyMuestra todo el historial. Se suele combinar con grep.
history -cBorra todo el historial
fc [inicio] [fin]Permite ver, editar y reejecutar el rango de los comando inicio - fin.
fc -l [inicio] [fin]Igual que antes, pero solo se listan.
fc -s ANTES=DESPUES [cmd]Sustituye en un comando anterior y lo reejecuta.
Expansiones relativas al historial
Ctrl-RBúsqueda de un comando en el historial
!!Expande al comando anterior. Muy útil para sudo !!.
![cmd]Expande el comando especificado.
!?textoExpande el comando que contenga texto
^cadena1^cadena2Expande al comando que contiene la cadena1 y la cambiar por cadena2.

Variables

Bash permite la creación de variables. Se distinguen dos tipos:

Variables en Bash
variable=valorCrea una variable local.
export VARIABLE=valorCrea una variable global.
export VARIABLECrea una variable global a partir de una local.
VARIABLE=valor comandoCrea una variable global solamente para el comando dado.
readonly variableMarca una variable como solo lectura.
$variableExpande al valor de la variable.
variable=
variable=""
unset variable
Borra el contenido de la variable.

El comando declare permite declarar variables con determinadas características, además de mostrar información sobre las variables existentes.

Si se usa readonly sin parámetros, se mostrará una lista con todas las variables de solo lectura. Si se usa printenv o env se mostrarán todas las variables de entorno.

Importante

No poner espacios alrededor de =.
Fíjese que una palabra se interpreta como un comando:

una_variable = hola
  • Comando: una_variable
  • Parámetro 1: =
  • Parámetro 2: hola
Operaciones aritméticas
$(( (4+11)/3 ))Resuelve una expresión aritmética de enteros usando una sintaxis similar a C. El texto expande a 0.
$[ (4+11)/3 ]Equivalente al anterior.
bc -lRecibe una expresión de stdin y la procesa. Permite variables y variados operadores del estilo de C. Ver man bc.

Redirecciones

Recuerde que cada proceso tiene asociados 3 archivos especiales para su Entrada/Salida (toda la E/S se hace a través de archivos en Linux):

El comando recibe datos del stdin y luego escribe en stdout o stderr, dependiendo del caso. Pues el usuario puede redirigir estos datos a otro archivo.

Operadores de redirección
comando < archivoEl comando toma la entrada del archivo.
comando << etiquetaToma la entrada para el comando hasta que vuelva a aparecer la etiqueta.
comando <<< "string"Envía el string como entrada para el comando.
comando > archivoEnvía la salida del comando al archivo. Lo sobreescribe si contenía algo.
comando >> archivoEnvía la salida del comando al final del archivo.
comando | comandoPipe: la salida del primer comando es la entrada del segundo.
Redirección de los descriptores
comando 2>&1Envía stderr a stdout. Si no se pone el &, creará un archivo llamado 1.
comando &> archivoEnvía stderr y stdout al archivo. Equivalente a comando > archivo 2>&1

Un lugar muy común para redirigir la salida de un comando es /dev/null, la basura, lo que evita que se muestre nada en la consola.

Es importante que al combinar varias redirecciones de distintos descriptores, 2>&1 o análogos vaya siempre al final del comando a redirigir. Es decir, si quieres redirigir stderr a stdout para pasarselo a grep, debes hacer esto:

params2stderr prueba test prueba 2>&1 | grep "p"

En lugar de:

params2stderr prueba test prueba | grep "p" 2>&1

En este último intentas redirigir la salida de grep, en lugar de la salida del primer comando.

Caracteres especiales

La siguiente lista de caracteres reciben un tratamiento especial por parte de bash:

    Globbing:               * ? [ ] [! ]
    Variables:              $
    Redirección:            < > << >> ` |
    Terminador de comando:  & ;

Por tanto, si quieren utilizar como caracteres literales (no con el significado que les da Bash), será necesario escaparlos.

Tratamiento de caracteres especiales
'Ignora todos los caracteres especiales. El texto no puede contener '.
"Ignora todos los caracteres especiales excepto $ \ y `
\Ignora el caracter especial que viene a continuación. También se puede usar para ignorar el salto de línea.

Algunos comandos

Documentación

El número potencial de comandos es muy grande, y cada uno tiene sus propios subcomandos, con opciones y diferentes parámetros; por lo que resulta muy difícil aprenderlos todos.

Normalmente, los programas disponen de una opción que muestran alguna ayuda básica, comúnmente --help o -h. Para comandos internos se puede usar help <comando>.

$ fdisk --help
Usage:
 fdisk [options] <disk>         change partition table
 fdisk [options] -l [<disk>...] list partition table(s)

Display or manipulate a disk partition table.

Options:
 -b, --sector-size <size>      physical and logical sector size
 [...]
 -h, --help                    display this help
 -V, --version                 display version

For more details see fdisk(8).

Pero el mejor lugar para buscar información concreta es el manual (man).

Manual

Muestra información detallada sobre un comando, aplicación, función C, etc.

man fdisk
  • Desplazamiento: flechas, barra espaciadora, enter o h j k l.
  • u d: mayor desplazamiento
  • q: salir
  • /texto: búsqueda
  • n: siguiente de la búsqueda

Pulsa h para ver el resto de opciones disponibles.

Formato de una página

  • Cabecera: nombre del comando y sección.
  • NAME: nombre y descripción corta del comando.
  • SYPNOSYS: sintaxis del comando.
  • DESCRIPTION: descripción detallada del comando y sus opciones.
  • Otras secciones con más información: AUTHOR, SEE ALSO, EXAMPLES

Organización de las páginas

Se busca según la variable de entorno MANPATH. Ver el comando manpath para más información.

Las secciones se buscan en el siguiente orden: 1 8 2 3 4 5 6 7 9.

SecciónContenido
1Comandos de usuario y aplicaciones
2Llamadas del sistema
3Llamadas de biblioteca
4Ficheros especiales (/dev)
5Formato de ficheros y convenciones (/etc/passwd)
6Juegos
7Ficheros varios, macros e información adicional
8Comandos de administración (root)
9Rutinas del kernel

Opciones de CLI

  • man <section>: especificar la sección
  • man -w: muestra dónde está el archivo de ayuda
  • man -wa: muestra dónde están todos los archivos que coincidan
  • man -f: da una descripción breve. También con whatis
  • man -k: busca por nombre y descripción. También con apropos

El comando whatis mantiene una base de datos para rápidamente realizar búsquedas. Se construye con diferentes comandos, dependiendo de la distribución:

1makewhatis
2catman
3mandb

Otro comando interesante puede ser info, que es el sistema de manuales de GNU. Por lo general es más flexible y completo que man.

Útiles con pipes y redirecciones

tee

Copia la entrada que recibe de stdin y la copia a stdout y otros archivos.

ls -l /bin | tee archivos.txt | less

El comando anterior lista el contenido de /bin, lo guarda en el archivo archivos.txt y luego muestra el resultado con less.

Con el parámetro -a añade al final del archivo en lugar de sobreescribir.

xargs

Lee el stdin y pasa esos datos como parámetros a otro comando.

locate README | xargs cat

El comando anterior encuentra todos los archivos que se llamen README y luego los imprime por pantalla. Nótese que locate solo devuelve las rutas.

locate README | xargs -I % 'cat %; cp % /tmp'

En este otro ejemplo, se hace lo mismo, pero también se hace una copia del archivo a /tmp. xargs sustituye cada línea que lee de stdin por % en el comando especificado.

Procesado de texto

awk

El comando por excelencia para el procesamiento de texto es awk. En realidad es su propio lenguaje de programación.

La idea es dividir la entrada en diferentes partes, para poder operar sobre ellas como si fuese una tabla:

  • records: awk procesa un record a la vez. Por defecto líneas de texto, pero se puede cambiar el delimitador con RS='' (Record Separator).
  • fields: cada record se puede dividir en partes, a las que se puede acceder con las variables especiales $0, $1 .... Por defecto palabras de cada línea, configurable con FS='' (Field Separator).

Algunas variables útiles de acuerdo a esto:

  • NR: número de record actual. Cuando RS='\n', equivale al número de línea.
  • NF: número de campos que tiene el record actual.

La estructura de un comando de awk es la siguiente:

patron { accion1; accion2; }

Si no hay ningún patrón se interpretará como true, es decir, siempre se ejecuta. El patrón BEGIN hace que se ejecute antes de empezar a procesar records y END después de procesar el último. Este también puede ser un regex sobre el record: /regex/.

Se pueden declarar variables sin necesidad de declarar su tipo, al igual que en otros lenguajes: sum += $1. No es necesario declararlas, tendrán por defecto "", que es implicitamente 0.

Puedes encontrar más características de awk con ejemplos en esta página.

sed

sed procesa un archivo línea a línea aplicando una serie de acciones, aplicando una serie de comandos. Hay una gran variedad de ellos, pero habitualmente se utiliza para reemplazar texto:

1sed 's/regex/replace' < archivo.txt

El comando s intenta hacer match del regex de cada línea para luego reemplazarlo. Se pueden usar \1 y demás para los grupos.

Se recomienda el uso del parámetro -E para poder utilizar expresiones regulares extendidas.

cut

cut escribe partes seleccionadas de un archivo a la salida estándar, puede usarse para seleccionar columnas o campos de un fichero en específico:

  • -b: selecciona los bytes especificados
  • -c: selecciona los caracteres especificados
  • -f: selecciona los campos especificados. Con -d se puede cambiar el delimitador.
tr

Este comando permite reemplazar la entrada a nivel de caracteres.

  • tr 'abc' 'xyz': sustituye todos los caracteres a por x, todos los b por y y c por z.
  • tr 'a-z' 'A-Z': también se puede especificar como rangos. Este comando pasa de minúsculas a mayúsculas.
  • tr -d '...': borrar un conjunto o rango de caracteres.
  • tr -s ' ': elimina espacios duplicados.
  • tr -s ' ' '_': cambia los espacios duplicados por _.
  • echo $PATH | tr ':' '\n': se interpretan los caracteres especiales.

Y un largo etcétera: hay muchas más opciones, se pueden combinar entre ellas…

Otros comandos:

Scripting

La Bourne Shell, aunque se use como intérprete de comandos, también se pensó para que se utilizase como un lenguaje de scripting.

La idea es básicamente tomar los comandos que estábamos ejecutando en la línea de comandos y guardarlos en un archivos para reutilizar luego. La extensión típica para los scripts de Bash es .sh, pero técnicamente solo son archivos de texto plano.

Para ejecutar el archivo se puede le puede pasar a Bash directamente: bash script.sh. Otra alternativa es ejecutarlo como un programa normal, pero es necesario asignar permisos adecuadamente:

1chmod u+x script.sh
2./script.sh

Como existen otros lenguajes de scripting que se pueden utilizar (Python, Perl…) con este método es necesario indicar el intérprete a utilizar en la primera línea con un shebang:

1#!/bin/bash

Aquí tienes una cheatsheet bastante útil.

Variables útiles

Parámetros de script o función
$0es el nombre del script.
$1 a $9son los primeros parámetros
${10} ${11} ...si hace falta usar más
$#número de todos los parámetros
$* $@lista de todos los parámetros
Otras
$?código de retorno del comando anterior
$$PID del script actual

Estructuras de control

Para los condicionales solo se chechea el código de retorno del comando $?.

En los bucles también se pueden usar break y continue (se les puede pasar un número para salir de ese número de bucles).

1if cmd1
2then
3    cmd2
4elif cmd3
5then
6    cmd4
7else
8    cmd5
9fi
 1case valor in
 2    patron1)
 3        cmd
 4        ;;
 5
 6    patron2)
 7        cmd
 8        ;;
 9
10    *)      # Caso por defecto
11        cmd;
12        ;;
13esac
1for variable in lista
2do
3    cmd $variable
4done

Lista debe ser texto, cada elemento estará separado por espacios. Por tanto, es posible usar las expansiones de Bash vistas anteriormente.

1for ((i=0; i < 5; i++))
2do
3    cmd $i
4done
1while cmd
2do
3    cmd $i
4done
1until cmd
2do
3    cmd $i
4done

Checks

Operadores
[[ ! EXPR ]]Negación
[[ EXPR && EXP ]]Y lógico
[[ EXPR || EXP ]]O lógico
[[ !(EXPR && (EXPR || EXP)) ]]Se pueden usar paréntesis
Comprobaciones de strings
[[ -z "STR" ]]Vacio
[[ -n "STR" ]]No vacio
[[ "STR" == "STR" ]]Son iguales
[[ "STR" != "STR" ]]No son iguales
[[ "STR" ~= REGEX ]]Comprueba expresión regex
Comprobaciones numéricas
[[ NUM -eq NUM ]]Iguales
[[ NUM -ne NUM ]]Diferentes
[[ NUM -lt NUM ]]Menor
[[ NUM -gt NUM ]]Mayor
[[ NUM -le NUM ]]Menor o igual
[[ NUM -ge NUM ]]Mayor o igual
(( NUM == NUM ))Alternativamente se puede usar esta sintaxis para poder usar los símbolos matemáticos habituales.
Comprobaciones de archivos
[[ -e ARCHIVO ]]Existe
[[ -s ARCHIVO ]]Si su tamaño es mayor que 0 bytes
[[ -d ARCHIVO ]]Es directorio
[[ -f ARCHIVO ]]Es archivo
[[ -r ARCHIVO ]]Se puede leer
[[ -w ARCHIVO ]]Se puede escribir
[[ -x ARCHIVO ]]Se puede ejecutar

Comando read

El comando read se puede utilizar para bastantes cosas. Por ejemplo, leer de teclado a una variable:

1read -p "Introduzca un dato: " res
2printf "Ha instroducido: \"$res\""

Leer la primera línea de un fichero (dado que el delimitador es \n. Esto se puede cambiar con -d):

1read linea < archivo.txt

También se puede iterar sobre un archivo:

1i=1
2while read buff
3do
4    printf "$i: $buff\n"
5    i=$[$i+1]
6done < archivo.txt

Funciones

1nombre() {
2    cmd
3    return 0
4}

Para pasar parámetros se usa la misma sintaxis que con los parámetros de un script: $1 ... $9 ${10} ..., $* o $@. Para obtener el nombre de la función debes usar ${FUNCNAME[0]}, dado que $0 siempre es el nombre del script.

Rendimiento

Anterior: Entrada/Salida Volver a Sistemas Operativos Siguiente: Comunicación y Sincronización de Procesos