Utilisation de la substitution de paramètres sur un tableau Bash

8

J'ai file.txt que je dois lire dans un tableau Bash. Ensuite, je dois supprimer les espaces, les guillemets doubles et tout sauf la première virgule dans chaque entrée . Voici jusqu'où je suis allé:

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

Ce qui fonctionne très bien, sauf pour la virgule. Je suis conscient qu'il existe plusieurs façons d'habiller ce chat, mais en raison du script plus important dont il fait partie, j'aimerais vraiment utiliser la substitution de paramètres pour arriver ici:

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

Est-ce possible via la substitution de paramètres?

Jon Red
la source
3
Y a-t-il une raison pour laquelle vous devez conserver le texte dans un tableau, et pourquoi vous ne pouvez pas laisser par exemple awkou sedfaire le traitement des données?
Kusalananda
@Jeff - La boucle sur le tableau sera un cauchemar à implémenter dans le plus gros script sur lequel je travaille.
Jon Red
3
@JonRed Je ne sais pas ce que vous faites, il est donc tout à fait possible que vous n'ayez pas le choix en la matière, mais généralement, lorsque vous vous retrouvez à faire des acrobaties de cordes aussi complexes dans le shell, c'est une très bonne indication que vous devrait utiliser un véritable langage de programmation. Le shell n'est pas conçu comme un langage de programmation, et bien qu'il puisse être utilisé comme tel, ce n'est vraiment pas une bonne idée pour des choses plus complexes. Je vous invite fortement à envisager de passer à perl ou python ou à tout autre langage de script.
terdon
@terdon C'est drôle, je viens de finir de dire presque exactement la même chose à mon collègue avant de lire ce post. J'ai essentiellement dit qu'il s'agissait de la version finale de ce script et que toute autre exigence nécessiterait une réécriture en Perl. Alors oui, je suis définitivement d'accord
Jon Red

Réponses:

9

Je supprimerais ce que vous devez supprimer en utilisant sed avant de charger dans le tableau (notez également les noms de variables en minuscules, en général, il est préférable d'éviter les variables en majuscules dans les scripts 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

Cela produit la sortie suivante sur votre fichier d'exemple:

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

Si vous devez vraiment utiliser la substitution de paramètres, essayez quelque chose comme ceci:

#!/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
terdon
la source
1
@JonRed J'ai ajouté une version avec substitution de paramètres mais elle est complexe, lourde et laide. Faire ce genre de choses dans le shell est très rarement une bonne idée.
terdon
1
Notez que si vous avez supprimé à la fois des espaces et des guillemets, ces caractères deviennent disponibles à la place des vôtres RANDOMTEXTTHATWILLNEVERBEINTHEFILE.
Kusalananda
1
@Kusalananda ouais, je viens de lire ta réponse. J'aurais dû y penser! Merci :)
terdon
Répond directement à la question, illustre pourquoi ma solution préférée n'est pas idéale et fournit l'alternative la plus viable. Vous gagnez, meilleure réponse.
Jon Red
10

Pour autant que je puisse voir, il n'est pas nécessaire de le lire dans un bashtableau pour créer cette sortie:

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

L' sedexpression supprime les espaces et les guillemets doubles, remplace la première virgule par un espace (il n'y a pas d'autres espaces dans la chaîne à ce stade), supprime toutes les autres virgules, restaure la première virgule, et ajoute et ajoute les données supplémentaires.

Alternativement, avec GNU sed:

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

(la norme sedne prend pas en charge la combinaison de 2et en gtant que drapeaux de la scommande).

Kusalananda
la source
1
avec GNU sed, vous pouvez utiliser 's/,//2gpour supprimer les virgules, en commençant par le 2ème
glenn jackman
2
Et, les 2 dernières commandes s / / peuvent l'être s/.*/|ELEMENT|&|/mais cela peut être plus d'effort pour sed.
glenn jackman
1
@glennjackman Peut-être, mais ça a l'air plutôt soigné.
Kusalananda
Oui, cela fait partie d'un script plus large. Le tableau est nécessaire, pas seulement pour la sortie. D'où mon intérêt pour la substitution de paramètres. Je pourrais faire une boucle sur le tableau avec cela, mais ce sera un cauchemar à mettre en œuvre. Terndon a fourni une solution sans boucle utilisant sed sur laquelle je vais probablement me rabattre si la substitution de paramètres est un no-go.
Jon Red
Si je n'étais pas lié à l'utilisation d'un tableau, cependant, ce serait la meilleure solution.
Jon Red
9
ELEMENT='50,n,e,e,d,2'
IFS=, read -r first rest <<<"$ELEMENT"
printf "%s,%s\n" "$first" "${rest//,/}"
50,need2

Sortez de l'habitude d'utiliser des noms de variables ALLCAPS. Vous finirez par entrer en collision avec une variable "système" cruciale comme PATH et casser votre code.

glenn jackman
la source
Pas de substitution de paramètres. MAIS, je ne savais pas que les noms de variables ALLCAPS étaient une mauvaise habitude dans Bash. Vous soulevez un bon point, celui qu'une recherche rapide sur Google confirme définitivement. Merci d'avoir amélioré mon style! :)
Jon Red
1
J'ai répondu aux questions où la personne a écrit PATH=something; ls $PATH, puis je me suis interrogée sur l' ls: command not founderreur.
glenn jackman
1
Il y a près d'une centaine de variables intégrées qui sont nommées dans toutes les majuscules (cliquez sur ce lien de page de manuel ) pour voir ...
Jeff Schaller
8

[Il s'agit essentiellement d'une version plus développée de la réponse de glenn jackmann ]

Construire un tableau associatif à partir de la clé et de la valeur supprimées, en utilisant la première virgule comme séparateur:

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|
tournevis
la source
6

Vous pouvez faire une boucle sur le tableau et utiliser une variable intermédiaire:

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

Cela affecte à restla partie après la première virgule; nous concaténons ensuite trois morceaux dans la variable d'origine:

  • la portion avant la première virgule
  • une virgule
  • le remplacement restde chaque virgule sans rien
Jeff Schaller
la source
C'était ma première pensée et c'est assez simple pour l'exemple mais cela fait partie d'un script plus large où le tableau est massif et il y a déjà des boucles et ce serait tout. Cela fonctionnerait certainement mais serait très lourd à mettre en œuvre dans le projet plus vaste sur lequel je travaille.
Jon Red
1
C'est suffisant; J'ai juste essayé de répondre dans les limites (expansion des paramètres uniquement).
Jeff Schaller