it-swarm-es.tech

¿Cuál es la mejor manera de transformar una cadena en una matriz asociativa?

En aras del ejercicio, he escrito las pocas líneas a continuación en un script bash para transformar una entrada de publicación HTTP en una matriz asociativa. Continuando con mi aprendizaje, me preguntaba si hay formas diferentes, quizás más elegantes, de hacer esto. Quiero decir, sé que hay diferentes maneras. Me pregunto cuáles existen y cuáles son las ventajas y desventajas de cada método.

Nota: estoy usando la publicación como entrada aquí. Sin embargo, el ejercicio es construir una matriz a partir de una cadena de pares de nombre/valor múltiple. La entrada podría provenir de cualquier archivo u otra fuente de entrada.

Nota 2: la cadena con la que estoy tratando podría tener este aspecto: name=myName&age=myAge. Entonces hay 2 delimitadores. Uno separa pares de nombre/valor (&) y el otro separa el valor de su nombre (=).

#!/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;

PD No estoy dispuesto a comenzar una guerra religiosa. Simplemente me pregunto cómo harías esto y por qué elegirías tu propuesta sobre la mía.

1
Pat

Haría esto simplemente usando bash en sí mismo:

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

Esto es lo que obtengo en la salida:

Enter the string: name=Foo Bar&age=40
NAME: Foo Bar, AGE: 40
  • La cadena de entrada se divide en & usando IFS variable de entorno

  • Los valores de nombre y edad se analizan utilizando el patrón de expansión de parámetros ${var##*=}

  • Puede obtener todas las claves con ${!myArr[@]} y todos los valores con ${!myArr[@]}

En la práctica, no creo que solo hagas una matriz asociativa de un elemento. Si tiene varios elementos, reemplace la última línea printf con una construcción simple for para recorrer las teclas:

for key in "${!myArr[@]}"; do
    printf 'NAME: %s, AGE: %s\n' "$key" "${myArr["$key"]}"
done
2
heemayl

Lo que tienes no se ve mal, pero ni Bash ni sed juegan demasiado bien con la división en múltiples delimitadores; personalmente usaría AWK y simplificaría un poco el script (usar AWK cambiando IFS se vuelve redundante; tenga en cuenta que no necesita punto y coma al final de las declaraciones en Bash):

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

El comando AWK:

  • Lee $post;
  • Divide registros en secuencias de amperstands/newlines (dividir en nuevas líneas es un truco para evitar que el bucle while falle en el registro vacío final);
  • Divide campos en secuencias de signos iguales;
  • Imprime los campos separados por el separador predeterminado, un espacio.
% cat script.sh 
#!/bin/bash
read post
declare -A myArr
while read name value; do
    myArr[$name]=$value
done < <(<<<"$post" awk -F= '{print $1,$2}' RS='&|\n')
printf '%s %s\n' "${myArr[name]}" "${myArr[age]}"
% bash script.sh 
name=myName&age=myAge
myName myAge
% 
3
kos

Otra forma más, usando solo 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 la línea entrante en palabras en todos caracteres en IFS, por lo que puede usar tanto & como = en IFS dividir en ambos. Dado que la entrada POST siempre tiene un valor para una clave, esto funcionará.

Sin embargo, este método no puede verificar si hay una alternancia estricta entre & y =. Entonces, por ejemplo, age&myAge=name=myName se analizará de manera que age=myAge y name=myName.


Una nota sobre IFS

Ha realizado una copia de seguridad IFS y la ha restaurado. Pero solo necesita IFS para read, así que aplique IFS solo para READ:

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

Restaurar IFS es complicado, ya que un IFS y un IFS vacío afectan al Shell de manera diferente, pero son los mismos cuando se toma el valor de IFS. Es decir:

IFS=
IFS_BAK="$IFS"

dará el mismo valor para IFS_BAK que:

unset IFS
IFS_BAK="$IFS"

Un IFS vacío es exactamente eso: una cadena vacía. Sin embargo, un nset IFS hace que el Shell se comporte como si se usara el IFS (espacio, tabulación, nueva línea) predeterminado:

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

Entonces, si te encontraste con un IFS no configurado, y luego intentaste el viejo truco de respaldar y restaurar IFS, podrías obtener resultados sorprendentes. Es mejor restringir los cambios a IFS para que sean solo para aquellos comandos que lo necesitan.

2
muru

Has dicho:

Entonces hay 2 delimitadores. Un par separador de nombre/valor (&) y el otro separando el valor forman su nombre (=).

Bueno, podemos dividir pares nombre/valor usando & como IFS en variables, y usar la eliminación de sufijos/prefijos para liberar los valores reales de nombre y edad.

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

También has dicho:

Sin embargo, el ejercicio es construir una matriz a partir de una cadena de pares de nombre/valor múltiple. La entrada podría provenir de cualquier archivo u otra fuente de entrada.

Si queremos almacenar múltiples pares clave-valor, podemos leer la entrada línea por línea (así que no es necesario usar sed para deshacernos de \n allí), y aplicar el mismo concepto que mostré arriba:

#!/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

El siguiente ejemplo de ejecución utiliza el documento aquí, pero podría ser cualquier cosa, incluso una tubería. El punto es que el comando de lectura no le importa dónde recibe la entrada.

$> ./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
1