Un consejo que los programadores nuevos de bash escucharán una y otra vez:
¡Usa comillas en tus variables!
¿Por qué?
Cuando expandes una variable (como $name), casi siempre quieres usar el valor de la variable como un solo string (o número).
No quieres que el valor se trate de forma inesperada como dos o más strings separados.
Las comillas son el mecanismo principal para asegurar que un fragmento de texto se trate como una sola palabra. Hay dos tipos principales de comillas.
Las comillas dobles permiten la expansión de parámetros, la expansión aritmética y la sustitución de comandos dentro de ellas.
echo "This is bash version $BASH_VERSION"
Puedes incluir una comilla doble literal dentro de un string entre comillas dobles si la precedes con una barra invertida.
echo "She said \"Let me teach you about bash.\""
Las comillas simples no permiten ninguna expansión dentro de ellas.
echo 'This is bash version $BASH_VERSION'
# ...^..................................^
# prints exactly: This is bash version $BASH_VERSION
No es posible incrustar una comilla simple dentro de un string entre comillas simples.
¿Qué tiene de malo echo $name, exactamente?
Cuando el shell analiza una línea de código, realiza varios tipos de expansiones, en orden. Dos de las últimas expansiones son la división de palabras y la expansión de nombres de archivo.
Cualquier resultado de la expansión de parámetros, la sustitución de comandos o la expansión aritmética que no haya ocurrido dentro de comillas dobles es candidato a la «división de palabras».
El shell toma el resultado expandido y lo divide en cualquier secuencia de caracteres presente en el valor de $IFS.
De forma predeterminada, son el espacio, el tabulador y el salto de línea.
Podemos ver esto en acción con un bucle del shell:
sentence="The quick brown fox jumps."
i=0
for word in "$sentence"; do echo "$((++i)) $word"; done
# ..........^.........^
imprime
1 The quick brown fox jumps.
Pero sin comillas:
i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^
imprime
1 The
2 quick
3 brown
4 fox
5 jumps.
Veamos qué pasa con un valor diferente de $IFS:
csv="first,second,third"
IFS=","
for word in $csv; do echo "$word"; done
imprime
first
second
third
Una técnica para desactivar la división de palabras es establecer $IFS en un string vacío
sentence="The quick brown fox jumps."
IFS= # or explicitly, IFS=""
i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^
imprime
1 The quick brown fox jumps.
Sin embargo, no se recomienda dejar las variables sin comillas a menos que también desactives la expansión de nombres de archivo. Veremos cómo hacerlo más abajo.
Los patrones «glob» se usan para expresar un patrón conciso que coincide con un conjunto de archivos.
Por ejemplo, puedes hacer coincidir todos los archivos Markdown de un directorio con *.md.
Los patrones glob existen desde la invención de Unix.
Los caracteres comodín de glob son:
* coincide con cero o más caracteres cualesquiera,
? coincide exactamente con un carácter cualquiera,
[...] coincide exactamente con un carácter del conjunto de caracteres listado dentro de los corchetes.
[abc] coincide exactamente con un carácter que es una a, una b o una c.
^ opcional)
[][] es un conjunto de caracteres que coincide con un corchete de cierre o uno de apertura.[[\]]
[x-y] coincide exactamente con un carácter que está en el rango de x a y
^ opcional) o el último carácter antes del corchete de cierre.
[-xy] o [xy-] coinciden cada uno con exactamente un carácter que es un -, una x o una y
[x\-y]
[0-9A-Fa-f] coincide con un dígito hexadecimal[^abc] coincide exactamente con un carácter que NO sea una a, una b o una c,[^x-y] coincide exactamente con un carácter que NO esté en el rango de x a y
[[:character_class:]] coincide con un carácter que está en la «character_class» nombrada
lower: letras minúsculasupper: letras mayúsculasalpha: letrasdigit: dígitosalnum: letras y dígitosspace: espacios en blancoblank: espacios en blanco horizontales (espacio y tabulador)xdigit: dígitos hexadecimalesword: caracteres permitidos en un identificador (letras, números, guion bajo)punct: puntuacióncntrl: caracteres de controlgraph: caracteres «visibles»print: caracteres visibles más el espacio[[:alpha:]] se compone de [[:lower:][:upper:]]
[[:alnum:]] se compone de [[:alpha:][:digit:]]
[[:word:]] se compone de [[:alnum:]_]
[[:word:]] como [[:punct:]]
[[:graph:]] se compone de [[:alnum:][:punct:]]
[[:print:]] se compone de [[:graph:] ]: solo el espacio (octal 040), ningún otro espacio en blanco.Puedes iterar sobre una lista de archivos con un bucle for
for file in *.csv; do
do_something_with "$file"
done
Fíjate que la variable "$file" está entre comillas.
El patrón glob puede devolver archivos cuyo nombre contiene espacios.
Necesitamos poner la variable entre comillas para que el comando do_something_with reciba exactamente un argumento, el nombre del archivo.
Los patrones glob no son expresiones regulares.
.
?
.*
*
.* significa «coincidir con un nombre de archivo donde el primer carácter es un punto literal seguido de cero o más caracteres cualesquiera»..+
?*
Bash ofrece patrones extendidos que se acercan más a las expresiones regulares.
Hay ocasiones en las que quieres suprimir la expansión de nombres de archivo. Manejar la entrada del usuario es una de ellas. Considera este fragmento de código
read -p "Enter something: " user_input
echo $user_input
Si el usuario escribe un asterisco (*), ¿qué se mostrará?
La lista de archivos del directorio actual.
Probablemente eso no sea lo que quieres hacer.
Otro ejemplo es pasar patrones como argumentos a programas que esperan patrones.
El programa tr se usa a menudo para manipular texto. Para convertir a minúsculas el valor de una variable, puedes hacer esto
var="My Puppet"
echo "$var" | tr '[:upper:]' '[:lower:]'
imprime
my puppet
Un error común es olvidar poner comillas en los argumentos de tr: tr [:upper:] [:lower:]
¿Por qué es un problema?
Los patrones glob sin comillas se expandirán a la lista de archivos que coincidan.
Supongamos que la persona que usa tu script tiene archivos llamados p, u y w en el directorio actual:
touch p u w
var="My Puppet"
echo "$var" | tr [:upper:] [:lower:]
¡Esto da como resultado un error!
El patrón [:upper:] sin comillas coincide con cualquiera de :, u, p, e o r.
El patrón [:lower:] sin comillas coincide con cualquiera de :, l, o, w, e o r.
Bash hace coincidir los archivos p y u con el primer patrón, y el archivo w con el segundo, y tr se invoca así
echo "$var" | tr p u w
Y esa es la cantidad equivocada de argumentos para tr.
Puedes usar el comando set para desactivar la expansión de nombres de archivo.
echo * # prints a list of files
set -f # disable filename expansion
echo * # prints a literal asterisk
set +f # enable filename expansion
Hay varias opciones del comando integrado shopt que controlan cómo funciona la expansión de nombres de archivo.
Un par de las interesantes son:
shopt -s nocaseglob: realiza coincidencias sin distinguir mayúsculas de minúsculasshopt -s extglob: habilita los patrones extendidosshopt -s nullglob: si ningún archivo coincide, reemplaza el patrón por nada.
El comportamiento predeterminado es dejar el patrón en su lugar como un string literal.