Un consejo que los programadores noveles de bash escucharán una y otra vez:
¡Pon comillas a tus variables!
¿Y 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 distintos.
Poner comillas es el mecanismo principal para garantizar 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 fichero.
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 por 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 verlo 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
# ..........^.........^
da como resultado:
1 The quick brown fox jumps.
Pero sin comillas:
i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^
da como resultado:
1 The
2 quick
3 brown
4 fox
5 jumps.
Veámoslo con otro valor de $IFS:
csv="first,second,third"
IFS=","
for word in $csv; do echo "$word"; done
da como resultado:
first
second
third
Una técnica para desactivar la división de palabras es asignar a $IFS 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
# .........^.........^
da como resultado:
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 fichero. Veremos cómo hacerlo más abajo.
Los patrones «glob» sirven para expresar de forma concisa un patrón que coincida con un conjunto de ficheros.
Por ejemplo, puedes hacer coincidir todos los ficheros 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 con exactamente un carácter cualquiera,
[...] coincide con exactamente un carácter del conjunto de caracteres indicado entre los corchetes.
[abc] coincide con exactamente un carácter que sea una a, una b o una c.
^ opcional)
[][] es un conjunto de caracteres que coincidirá con un corchete de cierre o uno de apertura.[[\]]
[x-y] coincide con exactamente 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 sea un -, una x o una y
[x\-y]
[0-9A-Fa-f] coincide con un dígito hexadecimal[^abc] coincide con exactamente un carácter que NO sea una a, una b o una c,[^x-y] coincide con exactamente un carácter que NO esté en el rango de x a y
[[:character_class:]] coincide con un carácter que pertenezca a la «character_class» indicada
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 -- signos de puntuacióncntrl -- caracteres de controlgraph -- caracteres «visibles»print -- caracteres visibles más el espacio[[:alpha:]] está formada por [[:lower:][:upper:]]
[[:alnum:]] está formada por [[:alpha:][:digit:]]
[[:word:]] está formada por [[:alnum:]_]
[[:word:]] como a [[:punct:]]
[[:graph:]] está formada por [[:alnum:][:punct:]]
[[:print:]] está formada por [[:graph:] ] -- solo el espacio (octal 040), no ningún otro espacio en blanco.Puedes iterar sobre una lista de ficheros con un bucle for
for file in *.csv; do
do_something_with "$file"
done
Fíjate en que la variable "$file" está entre comillas.
El patrón glob puede devolver ficheros cuyo nombre contenga espacios.
Necesitamos poner comillas a la variable para que el comando do_something_with reciba exactamente un argumento, el nombre del fichero.
Los patrones glob no son expresiones regulares.
.
?
.*
*
.* significa «coincidir con un nombre de fichero en el que el primer carácter sea 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 fichero. Gestionar la entrada del usuario es una de ellas. Fíjate en este fragmento de código
read -p "Enter something: " user_input
echo $user_input
Si el usuario introduce un asterisco (*), ¿qué se mostrará como salida?
La lista de ficheros 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:]'
da como resultado:
my puppet
Un error habitual es olvidarse de poner comillas a los argumentos de tr: tr [:upper:] [:lower:]
¿Por qué es un problema?
Los patrones glob sin comillas se expandirán a la lista de ficheros que coincidan.
Supón que la persona que usa tu script tiene ficheros llamados p, u y w en el directorio actual:
touch p u w
var="My Puppet"
echo "$var" | tr [:upper:] [:lower:]
¡Esto da lugar a 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 ficheros p y u con el primer patrón, y el fichero w con el segundo, y se invoca tr así
echo "$var" | tr p u w
Y ese no es el número correcto de argumentos para tr.
Puedes usar el comando set para desactivar la expansión de nombres de fichero.
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 fichero.
Un par de las interesantes son:
shopt -s nocaseglob -- realiza coincidencias sin distinguir mayúsculas y minúsculasshopt -s extglob -- activa los patrones extendidosshopt -s nullglob -- si no coincide ningún fichero, sustituye el patrón por nada.
El comportamiento predeterminado es dejar el patrón tal cual, como string literal.