README.md |
Consejos y trucos de shell
Autor
Telegram
- @tenshalito Tuxliban Torvalds
ÍNDICE
- Suspender y reanudar un comando
- Uso de heredoc en lugar de múltiples echo
- Uso de built-in en lugar de comandos externos
- Forma correcta para listar nombre de archivos en un bucle
- Evaluación de expresiones
- Evaluación múltiple de expresiones
- Expresiones aritmétricas
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.