Qual é a melhor maneira de transformar uma string em um array associativo?

1

Para o exercício, escrevi as poucas linhas abaixo em um script bash para transformar uma entrada de postagem HTTP em uma matriz associativa. Na sequência do meu aprendizado, eu estava me perguntando, se existem maneiras diferentes, talvez mais elegantes, de fazer isso. Quer dizer, eu sei que existem maneiras diferentes. Eu estou querendo saber quais existem e quais são as vantagens e desvantagens de cada método.

Observação: estou usando a postagem como entrada aqui. O exercício, no entanto, é construir uma matriz a partir de uma cadeia de pares de vários nomes / valores. A entrada pode ser proveniente de qualquer arquivo ou outra fonte de entrada.

Nota 2: a string com a qual estou lidando pode ter esta aparência: name=myName&age=myAge . Portanto, existem dois delimitadores. Um separando pares de nome / valor ( & ) e o outro separando o valor de seu nome ( = ).

#!/bin/bash
read post;
declare -A myArr;
IFS_bck=$IFS;
IFS="=";
while read name value; do
  myArr[$name]=$value;
done < <(sed -rn -e 's/&/\n/g p' <<<"$post");
IFS=$IFS_bck;

P.S. Não estou disposto a começar uma guerra religiosa. Estou simplesmente imaginando como você faria isso e por que escolheria sua proposta sobre a minha.

    
por Pat 09.03.2016 / 13:18

4 respostas

2

Eu faria isso usando apenas o bash :

#!/bin/bash
read -e -p 'Enter the string: ' post
declare -A myArr

while IFS='&' read name age; do
    myArr["${name##*=}"]="${age##*=}"
done <<<"$post"

printf 'NAME: %s, AGE: %s\n' "${!myArr[@]}" "${myArr[@]}"

Aqui está o que recebo na saída:

Enter the string: name=Foo Bar&age=40
NAME: Foo Bar, AGE: 40
  • A string de entrada é spitted em & usando IFS variável ambiental

  • Os valores de nome e idade são analisados usando o padrão de expansão de parâmetro ${var##*=}

  • Você pode obter todas as chaves usando ${!myArr[@]} e todos os valores usando ${!myArr[@]}

Na prática, não acho que você faria apenas uma matriz associativa de um elemento. Se você tiver vários elementos, substitua a última printf line por uma simples construção for para fazer um loop pelas chaves:

for key in "${!myArr[@]}"; do
    printf 'NAME: %s, AGE: %s\n' "$key" "${myArr["$key"]}"
done
    
por heemayl 09.03.2016 / 15:13
3

O que você tem não parece ruim, mas nem Bash nem sed jogam muito bem com a divisão em vários delimitadores; pessoalmente eu usaria o AWK e simplificaria o script um pouco (usando o AWK mudando IFS se torna redundante; observe que você não precisa de ponto-e-vírgula no final das instruções no Bash):

#!/bin/bash
read post
declare -A myArr
while read name value; do
    myArr[$name]=$value
done < <(<<<"$post" awk -F= '{print ,}' RS='&|\n')

O comando AWK:

  • $post ;
  • Divide registros em seqüências de amperstands / newlines (a divisão em novas linhas é um truque para evitar que o loop while falhe no registro final vazio);
  • Divide campos em sequências de sinais de igual;
  • Imprime os campos separados pelo separador padrão, um espaço.
% cat script.sh 
#!/bin/bash
read post
declare -A myArr
while read name value; do
    myArr[$name]=$value
done < <(<<<"$post" awk -F= '{print ,}' RS='&|\n')
printf '%s %s\n' "${myArr[name]}" "${myArr[age]}"
% bash script.sh 
name=myName&age=myAge
myName myAge
% 
    
por kos 09.03.2016 / 14:03
2

Mais uma forma, usando apenas IFS :

#!/bin/bash
declare -A myArr
IFS='&=' read -a post
for ((i = 0; i < ${#post[@]}; i += 2))
do
    myArr[${post[i]}]=${post[i + 1]}
done
for key in "${!myArr[@]}"
do
    printf "%s: %s\n" "$key" "${myArr[$key]}"
done

read divide a linha de entrada em palavras em todos caracteres em IFS , para que você possa usar & e = em IFS para dividir em ambos. Dado que a entrada POST sempre tem um valor para uma chave, isso funcionará.

No entanto, esse método não pode verificar se há uma alternância estrita entre & e = . Portanto, por exemplo, age&myAge=name=myName será analisado para que age=myAge e name=myName .

Uma nota sobre IFS

Você fez o backup de IFS e a restaurou. Mas você só precisa de IFS para read , então aplique IFS apenas para READ :

IFS='...' read ... # or
while IFS='...' read ...; ...

A restauração de IFS é complicada, pois um IFS não definido e um IFS vazio afetam o shell de maneira diferente, mas são os mesmos quando você obtém o valor IFS em si. Isso é:

IFS=
IFS_BAK="$IFS"

fornecerá o mesmo valor para IFS_BAK como:

unset IFS
IFS_BAK="$IFS"

Um IFS vazio é exatamente isso: uma string vazia. No entanto, um unset IFS faz com que o shell se comporte como se o padrão IFS (espaço, tabulação, nova linha) fosse usado:

$ foo='a  b  c'
$ printf "|%s|\n" $foo
|a|
|b|
|c|
$ IFS=; printf "|%s|\n" $foo
|a  b  c|
$ unset IFS; printf "|%s|\n" $foo
|a|
|b|
|c|

Portanto, se você se encontrou com um IFS não definido e tentou o antigo truque de backup e restauração do IFS, poderá obter resultados surpreendentes. É melhor restringir as alterações a IFS para serem apenas para os comandos que precisam dele.

    
por muru 09.03.2016 / 17:35
1

Você disse:

  

Portanto, há dois delimitadores. Um separando pares de nome / valor (& amp;) e o outro separando o valor de seu nome (=).

Bem, podemos dividir pares nome / valor usando & as IFS em variáveis e usar a remoção de sufixos / prefixo para liberar os valores reais de nome e idade.

$> cat post-parse.sh                                                           
#!/bin/bash
IFS='&' read PAIR1 PAIR2
# if necessary use these as well
# name_key=${PAIR1%%=*}
# age_key=${PAIR2%%=*}
name_val=${PAIR1##*=}
age_val=${PAIR2##*=}
echo $name_val $age_val
$> ./post-parse.sh
name=Serg&age=25
Serg 25
$> 

Você também disse:

  

O exercício, no entanto, é construir uma matriz a partir de uma cadeia de pares de vários nomes / valores. A entrada pode ser proveniente de qualquer arquivo ou outra fonte de entrada.

Se quisermos armazenar vários pares de valores-chave, podemos ler linha por linha (portanto, não é necessário usar sed para eliminar \n ) e aplicar o mesmo conceito que mostrei acima:

#!/bin/bash
declare -A myArray
while read input_line ; # read input line by line
do
echo $input_line
  IFS='&' read PAIR1 PAIR2 <<< $input_line # split in two with &
  # name_key=${PAIR1%%=*}
  # age_key=${PAIR2%%=*}
  name_val=${PAIR1##*=}
  age_val=${PAIR2##*=}
  myArray[$name_val]=$age_val
done
# print out the array
for key in "${!myArray[@]}" 
do
   echo ${myArray[$key]} is $key
done

Exemplo de execução abaixo usa o documento aqui, mas pode ser qualquer coisa, até mesmo um pipe. O ponto é, o comando de leitura não se importa onde é recebido.

$> ./post-parse.sh << EOF                                                      
> name=John&age=25                                                             
> name=Jane&age=35                                                             
> EOF
name=John&age=25
name=Jane&age=35
25 is John
35 is Jane
    
por Sergiy Kolodyazhnyy 09.03.2016 / 14:55