Dado o parâmetro bash
foo='ab "cd" "e f" x="1 2" '
Desejo produzir um array equivalente a
foo_transformed=( ab '"cd"' '"e f"' 'x="1 2"' )
de forma portátil, ou seja, usar recursos internos do bash (v3 +) ou programas disponíveis para a maioria dos sistemas operacionais (Linux, Unix, Cygwin) por padrão. Simplicidade e segurança, dada a sequência de entrada (quase) arbitrária, são desejáveis.
Você pode assumir que a entrada não contém aspas simples '
ou barras invertidas \
, mas pode conter um número arbitrário de caracteres de espaço em branco, tanto onde desejo que delimitem a string quanto onde não o fazem (quando entre aspas duplas).
Se tentarmos:
foo_transformed=( $foo )
então as aspas internas de foo
não são respeitadas ( for k in "${foo_transformed[@]}"; do echo "- $k"; done
):
- ab
- "cd"
- "e
- f"
- x="1
- 2"
Se tentarmos:
eval foo_transformed=( $foo )
então as aspas são perdidas:
- ab
- cd
- e f
- x=1 2
O regex permite
foo
conter novas linhas incorporadas entre aspas duplas. Para lidar corretamente com outras novas linhas, substitua ambas as instâncias de\t
por\t\n
ou talvez[:space:]
.(O Bash regex não aceita,
\t
então eu uso$'...\t...'
a sintaxe para converter em guias/novas linhas literais.)Conforme observado nos comentários, isso dependerá de entradas malformadas.
Para aceitar entradas malformadas, este regex modificado poderia ser usado:
Alternativamente, uma pré-verificação poderia ser feita:
Isso pode ser o que você deseja:
Forneci
foo
alguns valores extras, incluindo caracteres globbing, aspas simples, possíveis referências de variáveis, injeções de comando de estilo antigo e novo, novas linhas dentro e fora das strings entre aspas e strings contendo várias substrings entre aspas para que pudesse testar o script de forma mais completa.O uso do
fpat
regexp acima é inspirado no GNU awk,FPAT
que é usado para identificar campos na entrada. Veja, por exemplo, como ele é usado em CSVs em Qual é a maneira mais robusta de analisar CSV com eficiência usando o awk? . Corresponde:(^[[:space:]]*)
- uma sequência inicial opcional de espaços (que são descartados) seguida pela(...)
qual corresponde à string que realmente queremos capturar:[^[:space:]]+
- uma série de não-espaços|
- ou([^[:space:]"]*"([^"]|"")*"[^[:space:]"]*)+
- uma sequência repetida de substrings entre aspas duplas contendo quaisquer caracteres ou nenhum caractere, opcionalmente cercada por substrings que não contêm espaços ou aspas duplas.Você poderia usar uma substituição de string dentro de uma
declare
instrução que faça exatamente o que você deseja de uma só vez:Explicação detalhada:
${foo//\"/\"\\\"}
substitui cada aspa dupla"
por ,foo
portanto"\"
permanece entre aspas enquanto contém a própria aspa a ser retida.declare -a foo_transformed="(${foo//\"/\"\\\"})"
, usa a string transformada em uma declaração de arraydeclare -a variable="(string)"
.EDITAR
Adicionado escape de todas as expansões