Consejos y trucos de shell
Go to file
Tuxliban Torvalds 4ba3d109b6 README.md: Se añade índice de contenido 2023-02-13 16:58:17 -06:00
README.md README.md: Se añade índice de contenido 2023-02-13 16:58:17 -06:00

README.md

Consejos y trucos de shell

Autor

Telegram

  • @tenshalito Tuxliban Torvalds

Mail


ÍNDICE


Suspender y reanudar un comando

Si están utilizando una consola virtual (tty) y tienen un programa ejecutándose en primer plano, no es necesario finalizar esa tarea o por ejemplo abrir otra tty para poder realizar otra tarea, sólo se necesita teclear ^Z (control-z) para suspender temporalmente la tarea actual y estará esperando a que sea reanudado nuevamente. En este momento obtendrá un prompt de shell y podrá emitir cualquier comando.

Cuando se esté listo para continuar, puede reanudar la tarea anterior fg %1 (primer plano: el primer comando en espera).

Si por el contrario, se quiere ejecutar el comando en segundo plano, se puede emitir también un bg %1 (segundo plano: primer comando en espera).

Por último, con el comando jobs es posible listar todos los comandos que se encuentran en espera.

Uso de "heredoc" en lugar de múltiples echo

Los comandos echo múltiples pueden ser sustituidos por "heredoc", esto hace que el script sea más rápido y fácil de leer.

Ejemplo:

echo "Por favor, introduzca su elección:"
echo "1 - listar el directorio actual"
echo "2 - listar los usuarios actuales"
echo "3 - cerrar la sesión"

Lo anterior puede ser sustituido por:

cat << EOF
Por favor, introduzca su elección
1 - listar el directorio actual
2 - listar los usuarios actuales
3 - cerrar la sesión
EOF 

NOTA: "heredoc" no necesariamente debe de ser EOF, puede ser cualquier caracter que sirva como delimitador. Por ejemplo: !, ECHO, end, END_OF_TEXT

Otra opción es que si se desea que las líneas no se vean afectadas por la sintaxis del shell y no se quiere generar un proceso adicional al invocar al comando cat, se puede optar por hacerlo así:

# Opción 1: usar `echo` para el estandar POSIX o `print` para ksh
echo "Por favor, introduzca su elección:
1 - listar el directorio actual
2 - listar los usuarios actuales
3 - cerrar la sesión"

# Opción 2: usar printf (más eficiente)
printf %s "\
Por favor, introduzca su elección:
1 - listar el directorio actual
2 - listar los usuarios actuales
3 - cerrar la sesión

En el ejemplo de printf, el \ en la primera línea evita una nueva línea extra al principio del bloque de texto.

Uso de built-in en lugar de comandos externos

Muchas veces, es común utilizar comandos externos como basename, dirname y tr por comodidad o porque no se dan cuenta de que pueden utilizar los módulos integrados de ksh.

Una ventaja añadida es que los built-in son más rápidos y requieren menos recursos del sistema porque no se genera ningún subproceso.

Reemplazo de basename:

fullfile="/foo_dir1/foo_dir2/archivo.txt" 

# Reemplazo de file=$(basename $fullfile) 
file=${fullfile##*/} 
echo $file archivo.txt

Reemplazo de dirname:

fullfile="/foo_dir1/foo_dir2/archivo.txt"

# Reemplazo de dir=$(dirname $fullfile)
dir=${fullfile%/*}
echo $dir
/foo_dir1/foo_dir2

Reemplazo tr:

foo="EjEmPlO"
#Reemplazo de: echo $foo | tr [A-Z] [a-z]

typeset -l foo
echo $foo
ejemplo

#Reemplazo de: echo $foo | tr [a-z] [A-Z]

typeset -u foo
echo $foo
EJEMPLO

Forma correcta para listar nombre de archivos en un bucle

No se recomienda el uso del comando ls en un bucle para listar el nombre de archivos debido a que el output de ls puede ser confuso y difícil de manipular en scripts de shell. El output de ls es una secuencia de caracteres que puede incluir espacios y otros caracteres especiales que no son fácilmente procesables por scripts de shell.

Además, el comportamiento de ls puede variar entre sistemas y es posible que no se comporte de la misma manera en diferentes sistemas operativos. Por estas razones, es mejor evitar el uso de ls en scripts de shell y optar por soluciones más fiables y portables.

El siguiente ejemplo es uno de los errores comunes al escribir un bucle en un script de shell:

for i in $(ls *.xyz); do
  <comando> $i
done

En el ejemplo anterior, si el nombre de archivo contiene espacios en blanco al analizarlo tendrá una división de palabras. Suponiendo que hay un archivo llamado Tesis - Version 2.odt en el directorio actual, el ciclo for repetirá cada palabra en el nombre del archivo resultante: Tesis, -, Version, 2.odt

Tampoco se puede simplemente citar la sustitución ya que eso hará que toda la salida de ls se trate como una sola palabra. En lugar de iterar sobre cada nombre de los archivos, el bucle solo se ejecutará una vez asignando a i una cadena con todos los nombres de archivo juntos.

Por lo tanto, algunas las soluciones a este tipo de problemas son las siguiente:

Usar un glob simple (en caso de no necesitar recursividad):

for i in ./*.mp3; do
  <comando> "$i"
done

Nota: Observen que es necesario utilizar la comilla doble para evitar la división de palabras

Asimismo, si en el directorio que se analiza no existen coincidencias, para evitar que el bucle no lea esos archivos basta con añadir lo siguiente:

# Estándar POSIX
for i in ./*.mp3; do
  [ -e "$i" ] || continue
  <comando> "$i"
done

# ksh y shells similares
for i in ./*.mp3; do
  [[ -e $i ]] || continue
  <comando> "$i"
done

Usar find (si se necesita recursividad)

# Estándar POSIX
find . -type f -name '*.xyz' -exec <comando> {} \;

En el caso de ksh (y shells similares) se puede usar la opción -print0 de find de GNU o BSD, junto con la opción read -d '' para lograr la recursividad:

while IFS= read -r -d '' file; do
  <comando> "$file"
done < <(find . -type f -name '*.xyz' -print0)

La ventaja aquí es que (técnicamente todo el cuerpo del bucle while) se ejecuta en el shell actual. Por lo tanto, es posible establecer variables y hacer que persistan una vez finalizado el bucle.

Evaluación de expresiones

Cuando la variable contiene espacios en blanco internos, entonces se dividirá en palabras separadas antes de que el comando [ (test) pueda leerlo. Por ejemplo:

foo="texto de ejemplo"
[ $foo = "texto de ejemplo" ] && echo $foo

El código de ejemplo anterior tiene un error de sintaxis en lo que respecta a [ , así que para corregir el código basta con encerrar en comillas dobles la variable:

# Estándar POSIX
foo="texto de ejemplo"
[ "$foo" = "texto de ejemplo" ] && echo "$foo"

Nota: observen que siempre es necesario añadir un espacio después de [ y un espacio antes de ], de lo contrario tendrán un error de sintaxis

En el caso de ksh y shell similares, es posible utilizar la versión mejorar de [

foo="texto de ejemplo"
[[ $foo == "texto de ejemplo" ]] && print "$foo"

Como se puede observar, no es necesario citar la variable que se ubica del lado izquierdo de la evaluación (==) dentro de [[ ]] porque con esta versión mejorara de [ no se dividen ni agregan palabras, incluso las variables en blanco se manejarán correctamente. Como se puede observar, para hacer que la cadena de la derecha se interprete literalmente, hay que citarla si se utilizan caracteres que tienen un significado especial como por ejemplo !, #, $, etc..

Evaluación múltiple de expresiones

Otro error que se comete cuando comenzamos a crear nuestros propios scripts es que puede suceder que queramos utilizar && dentro [. Al realizar esto nuestro shell nos indicará que hay un error de sintaxis y por lo tanto, nuestro script no podrá ejecutarse correctamente.

Código incorrecto:

[ -e "$foo" && "$foo" = "ejemplo" ] && <comandos>

Código correcto:

# Estándar POSIX
[ -e "$foo" ] && [ "$foo" = "ejemplo" ] && <comandos>

# ksh y shells similares

[[ -e "$foo" && "$foo" = "ejemplo" ]] && <comandos>

Expresiones aritmétricas

Si se desea comparar números por valor evite usar < > ya que estos son utilizados para comparaciones de cadenas o en el peor de los casos, como redirección de la salida estándar. Tampoco utilice => =<

Código incorrecto

foo=5
if [ "$foo" > 3 ]; then
  echo "$foo es mayor que 3"
fi

Para corregir el error arriba mencionado, utilice los operadores de comparación numérica -eq -gt -ge -lt -le que corresponden a igual que, mayor que, mayor o igual que, menor que y menor o igual que respectivamente.

Código correcto

# Estándar POSIX
foo=5
if [ "$foo" -ge 3 ]; then
  echo "$foo es mayor que 3"
fi

ksh y shells similares

Si solo desea hacer una comparación numérica (o cualquier otra aritmética de shell), es mucho mejor usar (( )) en lugar de [[ ]]

Código correcto

foo=5
if ((foo > 3)); then
  print "$foo es mayor que 3"
fi


# Este código también es válido, aunque no es muy común
if [[ $foo -gt 3 ]]; then
  print "$foo es mayor que 3"
fi

Nota: Si se usa el operador > dentro de [[ ]], la expresión será tratada como una comparación de strings y no como una comparación de enteros. Si usa > dentro de [ ] se interpretará como una redirección de salida obteniendo así un archivo llamado 3 en su directorio, y la prueba tendrá éxito siempre que $foo no esté vacío.