Utilizzo della sostituzione dei parametri su un array Bash


8

Ho file.txt che devo leggere in un array Bash. Quindi ho bisogno di rimuovere spazi, virgolette doppie e tutto tranne la prima virgola in ogni voce . Ecco quanto sono arrivato lontano:

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

Che funziona benissimo tranne per la situazione virgola. Sono consapevole che ci sono diversi modi per abbellire questo gatto, ma a causa dello script più grande di cui fa parte, mi piacerebbe davvero usare la sostituzione dei parametri per arrivare a qui:

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

È possibile tramite la sostituzione dei parametri?


3
C'è qualche motivo per cui è necessario conservare il testo in un array e perché non è possibile consentire ad esempio awko di sedelaborare i dati?
Kusalananda

@Jeff - Il loop sull'array sarà un incubo da implementare nello script più grande su cui sto lavorando.
Jon Red,

3
@JonRed Non so cosa stai facendo, quindi è del tutto possibile che tu non abbia una scelta in merito, ma in generale, quando ti ritrovi a fare acrobazie a stringa così complesse nella shell, questa è un'ottima indicazione che tu dovrebbe usare un vero linguaggio di programmazione. La shell non è progettata come un linguaggio di programmazione e, sebbene possa essere usata come un linguaggio, non è una buona idea per cose più complesse. Vi esorto caldamente a prendere in considerazione il passaggio a perl o python o qualsiasi altro linguaggio di scripting.
terdon

@terdon È divertente, ho appena finito di dire quasi esattamente la stessa cosa al mio collega prima di leggere questo post. Fondamentalmente ho detto che questa è la versione finale di questo script e che qualsiasi ulteriore requisito richiederà la riscrittura in Perl. Quindi sì, sono assolutamente d'accordo
Jon Red,

Risposte:


9

Vorrei rimuovere ciò che è necessario rimuovere utilizzando sed prima di caricare nell'array (notare anche i nomi delle variabili minuscole, in generale è meglio evitare le variabili maiuscole negli script di 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

Questo produce il seguente output sul tuo file di esempio:

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

Se devi davvero utilizzare la sostituzione dei parametri, prova qualcosa del genere:

#!/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 Ho aggiunto una versione con sostituzione dei parametri ma è complessa, ingombrante e brutta. Fare questo genere di cose nella shell è molto raramente una buona idea.
terdon

1
Nota che se hai rimosso entrambi gli spazi e le doppie virgolette, questi caratteri diventano disponibili per l'uso al posto del tuo RANDOMTEXTTHATWILLNEVERBEINTHEFILE.
Kusalananda

1
@Kusalananda sì, ho appena letto la tua risposta. Avrei dovuto pensarci! Grazie :)
terdon

Risponde direttamente alla domanda, illustra perché la mia soluzione preferita non è l'ideale e offre l'alternativa più praticabile. Hai vinto, la migliore risposta.
Jon Red

10

Per quanto posso vedere, non è necessario leggerlo in un basharray per creare quell'output:

$ 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' sedespressione cancella gli spazi e le doppie virgolette, sostituisce la prima virgola con uno spazio (non ci sono altri spazi nella stringa a questo punto), cancella tutte le altre virgole, ripristina la prima virgola e antepone e aggiunge i dati extra.

In alternativa, con GNU sed:

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

(lo standard sednon supporta la combinazione di 2e gcome flag per il scomando).


1
con GNU sed, puoi usare 's/,//2gper rimuovere le virgole, a partire dal 2
glenn jackman,

2
E, gli ultimi 2 s /// comandi possono essere s/.*/|ELEMENT|&|/ma questo potrebbe essere uno sforzo maggiore per sed.
Glenn Jackman,

1
@glennjackman Forse, ma sembra piuttosto pulito.
Kusalananda

Sì, questo fa parte di una sceneggiatura più ampia. L'array è necessario, non solo per l'output. Da qui il mio interesse per la sostituzione dei parametri. Potrei fare un giro sull'array con questo, ma sarà un incubo da implementare. Terndon ha fornito una soluzione priva di loop utilizzando sed su cui probabilmente ripiegherò se la sostituzione dei parametri è un no-go.
Jon Red,

Se non fossi legato all'utilizzo di un array, tuttavia, questa sarebbe la soluzione migliore.
Jon Red

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

Prendi l'abitudine di usare i nomi delle variabili ALLCAPS. Alla fine ti scontrerai con una variabile "di sistema" cruciale come PATH e romperai il tuo codice.


Non sostituzione di parametri. MA, non ero a conoscenza del fatto che i nomi delle variabili ALLCAPS fossero una cattiva abitudine in Bash. Hai un buon punto, quello che sicuramente conferma un gocciolatore. Grazie per aver migliorato il mio stile! :)
Jon Red

1
Ho risposto alle domande in cui la persona ha scritto PATH=something; ls $PATHe poi mi sono chiesto ls: command not founddell'errore.
Glenn Jackman,

1
Ci sono quasi un centinaio di variabili integrate che sono nominate in maiuscolo (fare clic su questo link della pagina man ) per vedere ...
Jeff Schaller

8

[Questa è essenzialmente una versione più completamente sviluppata della risposta di Glenn Jackmann ]

Costruire un array associativo dalla chiave e dal valore eliminati, usando la prima virgola come separatore:

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

È possibile eseguire il ciclo sull'array e utilizzare una variabile intermedia:

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

Questo assegna alla restporzione dopo la prima virgola; concateniamo quindi tre pezzi nella variabile originale:

  • la parte prima della prima virgola
  • una virgola
  • la sostituzione restdi ogni virgola con nulla

Questo è stato il mio primo pensiero ed è abbastanza semplice per l'esempio, ma fa parte di uno script più ampio in cui l'array è enorme e ci sono già dei loop e sarebbe tutto. Funzionerebbe sicuramente, ma sarebbe molto complicato da implementare nel progetto più ampio a cui sto lavorando.
Jon Red

1
Giusto; Ho appena provato a rispondere entro i limiti (solo espansione dei parametri).
Jeff Schaller
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.