Consejos de Stupid Bash: historial, reutilización de argumentos, archivos y directorios, funciones, etc.

Como administrador del sistema, los shells son parte de las operaciones diarias. Los shells suelen ofrecer más opciones y flexibilidad que una interfaz gráfica de usuario (GUI). Las tareas repetitivas diarias se pueden automatizar fácilmente mediante secuencias de comandos, o las tareas se pueden programar para que se ejecuten en determinados momentos del día. Un shell proporciona una manera conveniente de interactuar con el sistema y le permite hacer más en menos tiempo. Hay muchos shells diferentes, incluidos Bash, zsh, tcsh y PowerShell.

En esta publicación de blog de dos partes, comparto algunas de las frases ingeniosas de Bash que uso para acelerar mi trabajo y dejar más tiempo para tomar café. En este primer artículo, cubriré la historia, los últimos argumentos, el trabajo con archivos y directorios, la lectura del contenido de los archivos y las funciones de Bash. En la segunda parte, veré las variables de shell, el comando de búsqueda, los descriptores de archivos y la realización de operaciones de forma remota.

Índice

Usar el comando de historial

el history el orden es conveniente. History me permite ver qué comandos he ejecutado en un sistema en particular o qué argumentos se han pasado a ese comando. yo suelo history para volver a ejecutar los comandos sin tener que recordar nada.

El registro de pedidos recientes se almacena por defecto en ~/.bash_history. Esta ubicación se puede cambiar modificando la variable de shell HISTFILE. Hay otras variables, como HISTSIZE (filas para almacenar en la memoria para la sesión actual) y HISTFILESIZE (cuántas filas mantener en el archivo de historial). Si quieres saber más sobre history, ver man bash.

Digamos que ejecuto el siguiente comando:

$> sudo systemctl status sshd

Bash me dice que el servicio sshd no se está ejecutando, por lo que lo siguiente que quiero hacer es iniciar el servicio. Había comprobado su estado con mi pedido anterior. Esta orden fue registrada en history, para que pueda hacer referencia a él. solo ejecuto:

$> !!:s/status/start/
sudo systemctl start sshd

La expresión anterior tiene el siguiente contenido:

  • !! - repetir el último comando en la historia
  • :s/estado/inicio/ - reemplazar estado con inicio

El resultado es que se inicia el servicio sshd.

Luego aumento el valor predeterminado de HISTSIZE de 500 a 5000 usando el siguiente comando:

$> echo “HISTSIZE=5000” >> ~/.bashrc && source ~/.bashrc

¿Qué pasa si quiero mostrar los últimos tres pedidos en mi historial? Yo entro:

$> history 3
 1002  ls
 1003  tail audit.log
 1004  history 3

Corro tail a audit.log haciendo referencia al número de línea de historial. En este caso, uso la línea 1003:

$> !1003
tail audit.log
..
..

Imagina que has copiado algo de otro terminal o de tu navegador y accidentalmente pegas la copia (que tienes en el búfer de copia) en el terminal. Estas líneas se almacenarán en el historial, lo cual no desea. ahí está desarmar HISTFILE && salir es muy practico

$> unset HISTFILE && exit

Donde

$> kill -9 $$

Hacer referencia al último argumento del comando anterior

Cuando quiero enumerar los contenidos de los directorios para diferentes directorios, puedo cambiar de directorio con bastante frecuencia. Hay un buen truco que puede usar para hacer referencia al último argumento del comando anterior. Por ejemplo:

$> pwd
/home/username/
$> ls some/very/long/path/to/some/directory
foo-file bar-file baz-file

En el ejemplo anterior, /some/very/long/path/to/some/directory es el último argumento del comando anterior.

Si, yo quiero cd (cambiar directorio) en esta ubicación, ingreso algo como esto:

$> cd $_

$> pwd
/home/username/some/very/long/path/to/some/directory

Ahora solo usa un guión para volver a donde estaba:

$> cd -
$> pwd
/home/username/

Trabajar en archivos y directorios

Imagine que quiero crear una estructura de directorios y mover un montón de archivos con diferentes extensiones a esos directorios.

Primero, creo los directorios todos a la vez:

$> mkdir -v dir_{rpm,txt,zip,pdf}
mkdir: created directory 'dir_rpm'
mkdir: created directory 'dir_txt'
mkdir: created directory 'dir_zip'
mkdir: created directory 'dir_pdf'

Luego muevo los archivos según la extensión del archivo a cada directorio:

$> mv -- *.rpm dir_rpm/
$> mv -- *.pdf dir_pdf/
$> mv -- *.txt dir_txt/
$> mv -- *.zip dir_txt/

Caracteres de doble guión -- significa Fin de opciones. Esta marca evita que los archivos que comienzan con un guión se traten como argumentos.

A continuación, quiero reemplazar/mover todos los archivos *.txt a archivos *.log, así que ingreso:

$> for f in ./*.txt; do mv -v ”$file” ”${file%.*}.log”; done
renamed './file10.txt' -> './file10.log'
renamed './file1.txt' -> './file1.log'
renamed './file2.txt' -> './file2.log'
renamed './file3.txt' -> './file3.log'
renamed './file4.txt' -> './file4.log'

En lugar de usar el for bucle de arriba, puedo instalar el prename comando y lograr el objetivo anterior de esta manera:

$> prename -v 's/.txt/.log/' *.txt
file10.txt -> file10.log
file1.txt -> file1.log
file2.txt -> file2.log
file3.txt -> file3.log
file4.txt -> file4.log

A menudo, cuando modifico un archivo de configuración, hago una copia de seguridad del original usando un comando de copia básico. Por ejemplo:

$> cp /etc/sysconfig/network-scripts/ifcfg-eth0 /etc/sysconfig/network-scripts/ifcfg-eth0.back

Como puede ver, repetir hasta el final y agregar .back al archivo no es tan eficiente y probablemente sea propenso a errores. Hay una forma más corta y más limpia de hacer esto. Está viniendo:

$> cp /etc/sysconfig/network-scripts/ifcfg-eth0{,.back}

Puede realizar diferentes comprobaciones en archivos o variables. Correr help test para más información.

Use el siguiente comando para averiguar si un archivo es un enlace simbólico:

$> [[ -L /path/to/file ]] && echo “File is a symlink”

Aquí hay un problema con el que me encontré recientemente. Quería comprimir/descomprimir un montón de archivos de una sola vez. Sin pensarlo tecleé:

$> tar zxvf *.gz

El resultado fue:

tar: openvpn.tar.gz: Not found in archive
tar: Exiting with failure status due to previous errors

Los archivos tar eran:

iptables.tar.gz
openvpn.tar.gz
…..

¿Por qué no funcionó y por qué? ls -l *.gz trabajar en su lugar? Debajo del capó se ve así:

$> tar zxvf *.gz

Se transforma de la siguiente manera:

$> tar zxvf iptables.tar.gz openvpn.tar.gz
tar: openvpn.tar.gz: Not found in archive
tar: Exiting with failure status due to previous errors

el tar El comando debe encontrar openvpn.tar.gz en iptables.tar.gz. Resolví esto con un simple for hebilla:

$> for f in ./*.gz; do tar zxvf "$f"; done
iptables.log
openvpn.log

¡Incluso puedo generar contraseñas aleatorias usando Bash! Aquí un ejemplo :

$> alphanum=( {a..z} {A..Z} {0..9} ); for((i=0;i<=${#alphanum[@]};i++)); do printf '%s' "${alphanum[@]:$((RANDOM%255)):1}"; done; echo

Aquí hay un ejemplo que usa OpenSSL:

$> openssl rand -base64 12
JdDcLJEAkbcZfDYQ

Leer un archivo línea por línea

Supongamos que tengo un archivo con muchas direcciones IP y quiero operar con esas direcciones IP. Por ejemplo, quiero correr dig para recuperar información de DNS inverso para las direcciones IP enumeradas en el archivo. También quiero ignorar las direcciones IP que comienzan con un comentario (# o hashtag).

Usaré el archivo A como ejemplo. Su contenido es:

10.10.12.13  some ip in dc1
10.10.12.14  another ip in dc2
#10.10.12.15 not used IP
10.10.12.16  another IP

Podría copiar y pegar cada dirección IP y luego ejecutar dig a mano:

$> dig +short -x 10.10.12.13

O podría hacer esto:

$> while read -r ip _; do [[ $ip == #* ]] && continue; dig +short -x "$ip"; done < ipfile

¿Qué pasa si quiero intercambiar las columnas en el archivo A? Por ejemplo, quiero poner las direcciones IP en la columna más a la derecha para que el archivo A se vea así:

some ip in dc1 10.10.12.13
another ip in dc2 10.10.12.14
not used IP #10.10.12.15
another IP 10.10.12.16

Corro:

$> while  read -r ip rest; do printf '%s %sn' "$rest" "$ip"; done < fileA

Usar funciones de bash

Las funciones en Bash son diferentes de las escritas en Python, C, awk u otros lenguajes. En Bash, una función simple que toma un argumento y genera "Hola mundo" se vería así:

func() { local arg=”$1”; echo “$arg” ; }

Puedo llamar a la función así:

$> func foo

A veces, una función se invoca a sí misma recursivamente para realizar una determinada tarea. Por ejemplo:

func() { local arg="[email protected]"; echo "$arg"; f "$arg"; }; f foo bar

Esta recursión se ejecutará para siempre y usará muchos recursos. En Bash puede usar FUNCNEST para limitar la recursividad. En el siguiente ejemplo, configuro FUNCNEST=5 para limitar la recursividad a cinco.

func() { local arg="[email protected]"; echo "$arg"; FUNCNEST=5; f "$arg"; }; f foo bar
foo bar
foo bar
foo bar
foo bar
foo bar
bash: f: maximum function nesting level exceeded (5)

Use una función para recuperar el archivo más nuevo o más antiguo

Aquí hay una función de ejemplo para mostrar el archivo más reciente en un directorio determinado:

latest_file()
{
  local f latest
  for f in "${1:-.}"/*
    do
      [[ $f -nt $latest ]] && latest="$f"
    done
   printf '%sn' "$latest"
}

Esta función muestra el archivo más antiguo en un directorio determinado:

oldest_file()
{
  local f oldest
  for file in "${1:-.}"/*
    do
      [[ -z $oldest || $f -ot $oldest ]] && oldest="$f"
    done
  printf '%sn' "$oldest"
}

Estos son solo algunos ejemplos del uso de funciones en Bash sin invocar otros comandos externos.

A veces me encuentro escribiendo un comando una y otra vez con muchos parámetros. Un comando que uso a menudo es kubectl (CLI de Kubernetes). ¡Estoy cansado de ejecutar este largo comando! Aquí está el comando original:

$> kubectl -n my_namespace get pods

Donde

$> kubectl -n my_namespace get rc,services

Esta sintaxis requiere que incluya manualmente -n my_namespace cada vez que ejecuto el comando. Hay una manera más fácil de hacer esto usando una función:

$> kubectl () { command kubectl -n my_namespace ”[email protected]” ; }

ahora puedo correr kubectl sin tener que teclear -n namespace cada vez:

$> kubectl get pods

Puedo aplicar la misma técnica a otros comandos.

Conclusión

Estos son solo algunos de los grandes trucos que existen para Bash. En la Parte 2, mostraré algunos ejemplos más, incluido el uso de la búsqueda y la ejecución remota. Lo animo a que practique estos trucos para que sus tareas de administración de línea de comandos sean más fáciles y precisas.

Artículos de interés

Subir