Bash não analisa citações ao converter uma string em argumentos

Esse é meu problema. Na festa 3:

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

Como obter bash para entender as aspas e retornar $ 2 como This is two e não "This ? Infelizmente não posso alterar a construção da variável chamada test neste exemplo.

A razão pela qual isso acontece é por causa da ordem em que o shell analisa a linha de comando: ele analisa (e remove) aspas e escapa e, em seguida, substitui os valores das variables. No momento em que $test é substituído por One "This is two" Three , é tarde demais para que as cotações tenham o efeito pretendido.

A maneira simples (mas perigosa) de fazer isso é adicionar outro nível de análise com eval :

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

(Observe que as aspas no comando echo não são necessárias, mas são uma boa prática geral.)

A razão pela qual eu digo que isso é perigoso é que ele não apenas retorna e repara por strings citadas, ele volta e repara tudo , talvez incluindo coisas que você não queria interpretadas como substituições de comandos. Suponha que você tenha definido

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

… o eval irá executar o comando rm . Portanto, se você não puder contar com o conteúdo de $test para ser “seguro”, não use essa construção .

BTW, o caminho certo para fazer esse tipo de coisa é com uma matriz:

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

Infelizmente, isso requer controle de como a variável é criada.

Agora temos o bash 4, onde é possível fazer algo assim:

 #!/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]}" 

Saídas, como esperado:

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

Na verdade, eu não tenho certeza, mas é provavelmente possível fazer isso em um bash mais antigo assim:

 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]}" 

A solução para esse problema é usar xargs (eval free).
Ele mantém cadeias duplas entre aspas:

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

Claro, você pode definir os argumentos posicionais com essa matriz:

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

Eu escrevi algumas funções bash nativas para fazer isso: https://github.com/mblais/bash_ParseFields

Você pode usar a function ParseFields assim:

 $ 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| || 

A opção -d para ParseFields remove as aspas vizinhas e interpreta barras invertidas dos campos analisados.

Há também uma function ParseField mais simples (usada por ParseFields ) que analisa um único campo em um deslocamento específico dentro de uma string.

Observe que essas funções não podem analisar um stream , apenas uma cadeia de caracteres. A variável IFS também pode ser usada para especificar delimitadores de campo além do espaço em branco.

Se você precisar que os apóstrofos sem escape possam aparecer em campos sem aspas , isso exigiria uma pequena alteração – avise-me.