Bash no analiza las comillas al convertir una cadena en argumentos

Este es mi problema. En bash 3:

$ test='One "This is two" Three' $ set -- $test $ echo $2 "This 

Cómo conseguir bash para entender las cotizaciones y devolver $ 2 ya que This is two y no "This ” Desafortunadamente no puedo alterar la construcción de la variable llamada test en este ejemplo.

La razón por la que esto sucede es debido al orden en que el intérprete de comandos analiza la línea de comando: analiza (y elimina) las comillas y los escapes, luego reemplaza los valores de las variables. Para cuando $test se reemplaza con One "This is two" Three , es demasiado tarde para que las cotizaciones tengan el efecto deseado.

La forma simple (pero peligrosa) de hacer esto es agregar otro nivel de análisis con eval :

 $ test='One "This is two" Three' $ eval "set -- $test" $ echo "$2" This is two 

(Tenga en cuenta que las comillas en el comando echo no son necesarias, pero son una buena práctica general).

La razón por la que digo que esto es peligroso es que no solo se remonta y se vuelve a rastrear para cadenas entrecomilladas, sino que regresa y repasa todo , tal vez incluyendo cosas que no quería que se interpretaran como sustituciones de comandos. Supongamos que ha establecido

 $ test='One `rm /some/important/file` Three' 

eval ejecutará realmente el comando rm . Entonces, si no puede contar con que el contenido de $test es “seguro”, no use este constructo .

Por cierto, la forma correcta de hacer este tipo de cosas es con una matriz:

 $ test=(One "This is two" Three) $ set -- "${test[@]}" $ echo "$2" This is two 

Desafortunadamente, esto requiere el control de cómo se crea la variable.

Ahora tenemos bash 4 donde es posible hacer algo como eso:

 #!/bin/bash function qs_parse() { readarray -t "$1" < <( printf "%s" "$2"|xargs -n 1 printf "%s\n" ) } tab=' ' # tabulation here qs_parse test "One 'This is two' Three -n 'foo${tab}bar'" printf "%s\n" "${test[0]}" printf "%s\n" "${test[1]}" printf "%s\n" "${test[2]}" printf "%s\n" "${test[3]}" printf "%s\n" "${test[4]}" 

Resultados, según lo esperado:

 One This is two Three -n foo bar # tabulation saved 

En realidad, no estoy seguro, pero probablemente sea posible hacer eso en situaciones como esa:

 function qs_parse() { local i=0 while IFS='' read -r line || [[ -n "$line" ]]; do parsed_str[i]="${line}" let i++ done < <( printf "%s\n" "$1"|xargs -n 1 printf "%s\n" ) } tab=' ' # tabulation here qs_parse "One 'This is two' Three -n 'foo${tab}bar'" printf "%s\n" "${parsed_str[0]}" printf "%s\n" "${parsed_str[1]}" printf "%s\n" "${parsed_str[2]}" printf "%s\n" "${parsed_str[3]}" printf "%s\n" "${parsed_str[4]}" 

La solución a este problema es usar xargs (eval free).
Conserva cadenas de comillas dobles juntas:

 $ test='One "This is two" Three' $ IFS=$'\n' arr=( $(xargs -n1 <<<"$test") ) $ printf '<%s>\n' "${arr[@]}"    

Por supuesto, puede establecer los argumentos posicionales con esa matriz:

 $ set -- "${arr[@]}" $ echo "$2" This is two 

Escribí un par de funciones bash nativas para hacer esto: https://github.com/mblais/bash_ParseFields

Puede usar la función ParseFields esta manera:

 $ str='field1 field\ 2 "field 3"' $ ParseFields -d "$str" abcd $ printf "|%s|\n|%s|\n|%s|\n|%s|\n" "$a" "$b" "$c" "$d" |field1| |field 2| |field 3| || 

La opción -d para ParseFields elimina las comillas circundantes e interpreta las barras diagonales inversas de los campos analizados.

También hay una función ParseField más ParseField (utilizada por ParseFields ) que analiza un único campo en un desplazamiento específico dentro de una cadena.

Tenga en cuenta que estas funciones no pueden analizar una secuencia , solo una cadena. La variable IFS también se puede usar para especificar delimitadores de campo además de espacios en blanco.

Si necesita que los apóstrofos no escamados aparezcan en campos sin comillas , eso requerirá un cambio menor, avíseme.