jueves, 27 de agosto de 2009

El uso de comillas en bash

Lo básico de usar comillas es simple, usar comillas simples o dobles cunado el valor contiene espacios:
a="hola mundo"
a='hola mundo'

Pero las comillas simples y las dobles son diferentes. Las comillas simples no soportan cualquier tipo de sustitución de variables o caracteres especiales dentro de una cadena encerrada en comillas. Por otro lado las comillas dobles soportan ambos:

a="hola \"ahi\" mundo"
a='hola "ahi" mundo'
a='hola \'ahi\' mundo' # causa un error
a="hola 'ahi' mundo"

b="ahi"
a='hola \"$b\" mundo' # a es >>hola \"$b\" mundo
a="hola \"$b\" mundo" # a es >>hola "ahi" mundo

Una forma sobre el problema con las comillas simples no soportan sustitución de variables es citar la parte individual literal de una cadena y luego poner la sustitución de la variable entre ellas:

b='"ahi"'
a='"hola" '$b' "mundo"' # a es: >>"hola" "ahi" "mundo"<<

Nota que "$b" no esta actualmente dentro de la parte encerrada entre comillas. Por que no hay espacio entre las dos partes literales de la cadena y "$b", bash coloca el resultado final en una sola cadena y luego la asigna a a.

Un lugar donde ocurren seguido problemas con las comillas es cuando estas usando archivos que contienen espacios en sus nombres. Por ejemplo, si tienes un archivo llamado "archivo con espacios" y ejecutas el siguiente código:

#/bin/bash

for i in $(find .)
do
echo $i
done

No se obtiene lo que se quiere:

$ bash t1.sh
.
./archivo
con
espacios
./t2.sh
./t1.sh
...

Una solución es poner el nombre de los archivos en un arreglo de bash cuando el Separador Interno de Campos (Internal Field Separator). Entonces se usa el valor "${array[@]}" para la lista de la iteracion for.

#/bin/bash

nuevalinea='
'

OIFS=$IFS
IFS=$nuevalinea
files=($(find .))
IFS=$OIFS

for i in "${files[@]}"
do
echo $i
done

Esto producirá la salida correcta:

$ bash t2.sh
.
./archivo con espacios
./t2.sh
./t1.sh
...

La diferencia entre "${array[*]}" y "${array[@]}" es análoga a la diferencia entre "$*" y "$@", a saber que en el caso anterior el resultado es que cada palabra llega a estar separadamente entre comillas en vez de estar entre comillas como una cadena simple.

Otra opción, la cual evita entre comillas especial es simplemente configurar la variable Separador Interno de Campos antes de ejecutar el bucle for y resetearlo como la primera declaración en el cuerpo del bucle:

#/bin/bash

nuevalinea='
'

OIFS=$IFS
IFS=$nuevalinea

for i in $(find .)
do
IFS=$OIFS
echo $i
done

Otro problema con el uso de comillas es cuando tienes una cadena la cual te gustaría cortarla en palabras separadas pero también quisieras honrar cualquier subcadena encerrada entre comillas en la cadena. Considera que tienes lo siguiente:

a="hola \"ahi gran\" mundo"

Y supón que quisieras cortarlo en tres partes:

  • hola
  • ahi gran
  • mundo

Si ejecutas lo siguiente:

#!/bin/bash

a="hola \"ahi gran\" mundo"

for i in $a
do
echo $i
done

Producirá esto:

$ bash t4.sh
hola
"ahi
gran"
mundo

El truco aquí es usar la forma especial del comando set para resetear $* (y por lo tanto $1, $2, etc) Podría ser algo así:

#!/bin/bash

a="hola \"ahi gran\" mundo"
set -- $a

for i in "$@"
do
echo $i
done

Y por supuesto no funcionara:

$ bash t5.sh
hola
"ahi
gran"
mundo

Entonces recuerda que tienes que eval el comando set:

#!/bin/bash

a="hola \"ahi gran\" mundo"
eval set -- $a

for i in "$@"
do
echo $i
done

Ahora si funcionara:

$ bash t6.sh
hola
ahi gran
mundo

No hay comentarios: