Usar la sustitución de parámetros en una matriz Bash


8

Tengo file.txt que necesito leer en una matriz Bash. Luego necesito eliminar espacios, comillas dobles y todo menos la primera coma en cada entrada . Aquí está lo lejos que he llegado:

$ cat file.txt
10,this
2 0 , i s
30,"all"
40,I
50,n,e,e,d,2
60",s e,e"

$ cat script.sh
#!/bin/bash
readarray -t ARRAY<$1
ARRAY=( "${ARRAY[@]// /}" )
ARRAY=( "${ARRAY[@]//\"/}" )
for ELEMENT in "${ARRAY[@]}";do
    echo "|ELEMENT|$ELEMENT|"
done

$ ./script.sh file.txt
|ELEMENT|10,this|
|ELEMENT|20,is|
|ELEMENT|30,all|
|ELEMENT|40,I|
|ELEMENT|50,n,e,e,d,2|
|ELEMENT|60,se,e|

Lo que funciona muy bien, excepto por la situación de coma. Soy consciente de que hay varias formas de desollar este gato, pero debido a la secuencia de comandos más grande de la que forma parte, realmente me gustaría usar la sustitución de parámetros para llegar aquí:

|ELEMENT|10,this|
|ELEMENT|20,is|
|ELEMENT|30,all|
|ELEMENT|40,I|
|ELEMENT|50,need2|
|ELEMENT|60,see|

¿Es esto posible mediante la sustitución de parámetros?


3
¿Hay alguna razón por la que necesite mantener el texto en una matriz y por qué no puede permitir, por ejemplo, awko sedhacer el procesamiento de los datos?
Kusalananda

@Jeff: recorrer la matriz será una pesadilla para implementar en el script más grande en el que estoy trabajando.
Jon Red

3
@JonRed No sé lo que está haciendo, por lo que es muy posible que no tenga otra opción al respecto, pero en general, cuando se encuentra haciendo acrobacias de cuerda tan complejas en el caparazón, es una muy buena indicación de que debería estar usando un lenguaje de programación real. El shell no está diseñado como un lenguaje de programación, y aunque puede usarse como uno, realmente no es una buena idea para cosas más complejas. Le recomiendo encarecidamente que considere cambiar a Perl o Python o cualquier otro lenguaje de secuencias de comandos.
terdon

@terdon Es divertido, acabo de terminar de decir casi exactamente lo mismo a mi colega antes de leer esta publicación. Básicamente dije que esta es la versión final de este script y que cualquier requerimiento adicional requerirá reescribir en Perl. Así que sí, definitivamente estoy de acuerdo
Jon Red

Respuestas:


9

Quitaría lo que necesita eliminar usando sed antes de cargar en la matriz (también tenga en cuenta los nombres de las variables en minúsculas, en general es mejor evitar las variables en mayúsculas en los scripts de shell):

#!/bin/bash
readarray -t array< <(sed 's/"//g; s/  *//g; s/,/"/; s/,//g; s/"/,/' "$1")
for element in "${array[@]}";do
    echo "|ELEMENT|$element|"
done

Esto produce el siguiente resultado en su archivo de ejemplo:

$ foo.sh file 
|ELEMENT|10,this|
|ELEMENT|20,is|
|ELEMENT|30,all|
|ELEMENT|40,I|
|ELEMENT|50,need2|
|ELEMENT|60,see|

Si realmente debe usar la sustitución de parámetros, intente algo como esto:

#!/bin/bash
readarray -t array< "$1"
array=( "${array[@]// /}" )
array=( "${array[@]//\"/}" )
array=( "${array[@]/,/\"}" )
array=( "${array[@]//,/}" )
array=( "${array[@]/\"/,}" )

for element in "${array[@]}"; do
    echo "|ELEMENT|$element|"
done

1
@JonRed Agregué una versión con sustitución de parámetros, pero es compleja, engorrosa y fea. Hacer este tipo de cosas en el shell rara vez es una buena idea.
terdon

1
Tenga en cuenta que si ha eliminado los espacios y las comillas dobles, estos caracteres estarán disponibles para usar en lugar de los suyos RANDOMTEXTTHATWILLNEVERBEINTHEFILE.
Kusalananda

1
@Kusalananda, sí, acabo de leer tu respuesta. Debería haber pensado en eso! Gracias :)
terdon

Responde directamente a la pregunta, ilustra por qué mi solución preferida no es ideal y proporciona la alternativa más viable. Tú ganas, la mejor respuesta.
Jon Red

10

Por lo que puedo ver, no hay necesidad de leerlo en una bashmatriz para crear esa salida:

$ sed 's/[ "]//g; s/,/ /; s/,//g; s/ /,/; s/.*/|ELEMENT|&|/' <file
|ELEMENT|10,this|
|ELEMENT|20,is|
|ELEMENT|30,all|
|ELEMENT|40,I|
|ELEMENT|50,need2|
|ELEMENT|60,see|

La sedexpresión elimina espacios y comillas dobles, reemplaza la primera coma con un espacio (no hay otros espacios en la cadena en este punto), elimina todas las demás comas, restaura la primera coma, y ​​antepone y agrega los datos adicionales.

Alternativamente, con GNU sed:

sed 's/[ "]//g; s/,//2g; s/.*/|ELEMENT|&|/' <file

(el estándar sedno admite la combinación de 2y gcomo indicadores del scomando).


1
con GNU sed, puedes usarlo 's/,//2gpara eliminar comas, comenzando con el segundo
glenn jackman

2
Y, los últimos 2 s /// comandos pueden ser s/.*/|ELEMENT|&|/pero eso puede ser más esfuerzo para sed.
Glenn Jackman

1
@glennjackman Posiblemente, pero se ve bastante ordenado.
Kusalananda

Sí, esto es parte de un guión más grande. La matriz es necesaria, no solo para la salida. De ahí mi interés en la sustitución de parámetros. Podría recorrer el conjunto con esto, pero será una pesadilla implementarlo. Terndon proporcionó una solución sin bucles usando sed que probablemente recurriré si la sustitución de parámetros no es posible.
Jon Red

Sin embargo, si no estuviera atado a usar una matriz, esta sería la mejor solución.
Jon Red

9
ELEMENT='50,n,e,e,d,2'
IFS=, read -r first rest <<<"$ELEMENT"
printf "%s,%s\n" "$first" "${rest//,/}"
50,need2

Salga del hábito de usar nombres de variables ALLCAPS. Eventualmente colisionará con una variable crucial del "sistema" como PATH y romperá su código.


No sustitución de parámetros. PERO, no sabía que los nombres de variables ALLCAPS eran un mal hábito en Bash. Haces un buen punto, uno que un google superficial confirma definitivamente. ¡Gracias por mejorar mi estilo! :)
Jon Red

1
Respondí preguntas donde la persona escribió PATH=something; ls $PATHy luego me pregunté sobre el ls: command not founderror.
Glenn Jackman

1
Hay casi un centenar de variables incorporadas que se nombran en mayúsculas (haga clic en el enlace de esta página de manual ) para ver ...
Jeff Schaller

8

[Esta es esencialmente una versión más desarrollada de la respuesta de Glenn Jackmann ]

Construir una matriz asociativa a partir de la clave y el valor despojados, utilizando la primera coma como separador:

declare -A arr
while IFS=, read -r k v; do arr["${k//[ \"]}"]="${v//[ ,\"]}"; done < file.txt
for k in "${!arr[@]}"; do 
  printf '|ELEMENT|%s,%s|\n' "$k" "${arr[$k]}"
done
|ELEMENT|20,is|
|ELEMENT|10,this|
|ELEMENT|50,need2|
|ELEMENT|40,I|
|ELEMENT|60,see|
|ELEMENT|30,all|

6

Podría recorrer la matriz y usar una variable intermedia:

for((i=0; i < "${#ARRAY[@]}"; i++))
do
  rest="${ARRAY[i]#*,}"
  ARRAY[i]="${ARRAY[i]%%,*}","${rest//,/}"
done

Esto se asigna a restla porción después de la primera coma; Luego concatenamos tres piezas nuevamente en la variable original:

  • la porción antes de la primera coma
  • una coma
  • el reemplazo restde cada coma con nada

Este fue mi primer pensamiento y es lo suficientemente simple como para el ejemplo, pero esto es parte de un script más grande donde la matriz es masiva y ya hay bucles y sería todo. Esto definitivamente funcionaría, pero sería muy engorroso implementarlo en el proyecto más grande en el que estoy trabajando.
Jon Red

1
Lo suficientemente justo; Solo traté de responder dentro de las limitaciones (solo expansión de parámetros).
Jeff Schaller
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.