Verwenden der Parametersubstitution in einem Bash-Array

8

Ich habe file.txt, die ich in ein Bash-Array einlesen muss. Dann muss ich Leerzeichen, doppelte Anführungszeichen und alle bis auf das erste Komma in jedem Eintrag entfernen . So weit bin ich gekommen:

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

Was bis auf die Kommasituation gut funktioniert. Ich bin mir bewusst, dass es mehrere Möglichkeiten gibt, diese Katze zu häuten, aber aufgrund des größeren Skripts, zu dem dies gehört, würde ich wirklich gerne die Parametersubstitution verwenden, um hierher zu gelangen:

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

Ist dies durch Parametersubstitution möglich?

Jon Red
quelle
3
Gibt es einen Grund, warum Sie den Text in einem Array behalten müssen, und warum können Sie z. B. die Verarbeitung der Daten nicht zulassen awkoder durchführen sed?
Kusalananda
@ Jeff - Das Durchlaufen des Arrays ist ein Albtraum, der in dem größeren Skript implementiert werden muss, an dem ich arbeite.
Jon Red
3
@JonRed Ich weiß nicht, was Sie tun, daher ist es durchaus möglich, dass Sie in dieser Angelegenheit keine Wahl haben, aber im Allgemeinen ist dies ein sehr guter Hinweis darauf, dass Sie eine so komplexe String-Akrobatik in der Shell ausführen sollte eine tatsächliche Programmiersprache verwenden. Die Shell ist nicht als Programmiersprache konzipiert, und obwohl sie als eine verwendet werden kann, ist sie für komplexere Dinge keine gute Idee. Ich empfehle Ihnen dringend, in Betracht zu ziehen, zu Perl, Python oder einer anderen Skriptsprache zu wechseln.
Terdon
@terdon Es ist lustig, ich habe gerade fast genau dasselbe zu meinem Kollegen gesagt, bevor ich diesen Beitrag gelesen habe. Ich sagte im Grunde, dass dies die endgültige Version dieses Skripts ist und dass alle weiteren Anforderungen ein erneutes Schreiben in Perl erfordern. Also ja, ich stimme definitiv zu
Jon Red

Antworten:

9

Ich würde entfernen, was Sie mit entfernen müssen, sed bevor Sie in das Array laden (beachten Sie auch die Variablennamen in Kleinbuchstaben, im Allgemeinen ist es am besten, großgeschriebene Variablen in Shell-Skripten zu vermeiden):

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

Dies erzeugt die folgende Ausgabe in Ihrer Beispieldatei:

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

Wenn Sie wirklich die Parametersubstitution verwenden müssen, versuchen Sie Folgendes:

#!/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
quelle
1
@ JonRed Ich habe eine Version mit Parametersubstitution hinzugefügt, die jedoch komplex, umständlich und hässlich ist. So etwas in der Shell zu tun, ist sehr selten eine gute Idee.
Terdon
1
Beachten Sie, dass diese Zeichen anstelle von Ihren Zeichen verwendet werden können, wenn Sie sowohl Leerzeichen als auch doppelte Anführungszeichen entfernt haben RANDOMTEXTTHATWILLNEVERBEINTHEFILE.
Kusalananda
1
@Kusalananda Ja, ich habe gerade deine Antwort gelesen. Hätte daran denken sollen! Danke :)
terdon
Beantwortet direkt die Frage, zeigt, warum meine bevorzugte Lösung nicht ideal ist, und bietet die praktikabelste Alternative. Sie gewinnen, beste Antwort.
Jon Red
10

Soweit ich sehen kann, muss es nicht in ein bashArray eingelesen werden , um diese Ausgabe zu erstellen:

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

Der sedAusdruck löscht Leerzeichen und doppelte Anführungszeichen, ersetzt das erste Komma durch ein Leerzeichen (zu diesem Zeitpunkt befinden sich keine weiteren Leerzeichen in der Zeichenfolge), löscht alle anderen Kommas, stellt das erste Komma wieder her und stellt die zusätzlichen Daten voran und hängt sie an.

Alternativ mit GNU sed:

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

(Standard sedunterstützt nicht die Kombination von 2und gals Flags für den sBefehl).

Kusalananda
quelle
1
Mit GNU sed können Sie 's/,//2gKommas entfernen, beginnend mit dem 2.
Glenn Jackman
2
Und die letzten 2 s /// Befehle können sein, s/.*/|ELEMENT|&|/aber das kann mehr Aufwand für sed sein.
Glenn Jackman
1
@glennjackman Möglicherweise, aber es sieht ziemlich ordentlich aus.
Kusalananda
Ja, das ist Teil eines größeren Skripts. Das Array ist nicht nur für die Ausgabe erforderlich. Daher mein Interesse an der Parametersubstitution. Ich könnte das Array damit durchlaufen, aber das wird ein Albtraum, den es zu implementieren gilt. Terndon hat mit sed eine schleifenfreie Lösung bereitgestellt, auf die ich wahrscheinlich zurückgreifen werde, wenn die Parametersubstitution nicht möglich ist.
Jon Red
Wenn ich jedoch nicht an die Verwendung eines Arrays gebunden wäre, wäre dies die beste Lösung.
Jon Red
9
ELEMENT='50,n,e,e,d,2'
IFS=, read -r first rest <<<"$ELEMENT"
printf "%s,%s\n" "$first" "${rest//,/}"
50,need2

Machen Sie es sich nicht mehr zur Gewohnheit, ALLCAPS-Variablennamen zu verwenden. Sie werden schließlich mit einer entscheidenden "System" -Variablen wie PATH kollidieren und Ihren Code brechen.

Glenn Jackman
quelle
Keine Parametersubstitution. ABER ich wusste nicht, dass ALLCAPS-Variablennamen in Bash eine schlechte Angewohnheit waren. Sie machen einen guten Punkt, den ein flüchtiges Googeln definitiv bestätigt. Danke, dass du meinen Stil verbessert hast! :)
Jon Red
1
Ich habe Fragen beantwortet, in denen die Person geschrieben hat, PATH=something; ls $PATHund mich dann über den ls: command not foundFehler gewundert .
Glenn Jackman
1
Es gibt fast hundert integrierte Variablen, die in Großbuchstaben benannt sind (klicken Sie durch diesen Manpage-Link ), um zu sehen ...
Jeff Schaller
8

[Dies ist im Wesentlichen eine vollständigere Version von Glenn Jacksons Antwort ]

Erstellen eines assoziativen Arrays aus dem entfernten Schlüssel und Wert unter Verwendung des ersten Kommas als Trennzeichen:

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|
Steeldriver
quelle
6

Sie können das Array durchlaufen und eine Zwischenvariable verwenden:

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

Dies wird restdem Teil nach dem ersten Komma zugewiesen; Wir verketten dann drei Teile wieder in die ursprüngliche Variable:

  • der Teil vor dem ersten Komma
  • ein Komma
  • das Ersetzen restjedes Kommas durch nichts
Jeff Schaller
quelle
Dies war mein erster Gedanke und ist für das Beispiel einfach genug, aber dies ist Teil eines größeren Skripts, bei dem das Array riesig ist und es bereits Schleifen gibt und es eine ganze Sache wäre. Dies würde definitiv funktionieren, wäre aber in dem größeren Projekt, an dem ich arbeite, sehr umständlich zu implementieren.
Jon Red
1
Meinetwegen; Ich habe nur versucht, innerhalb der Grenzen zu antworten (nur Parametererweiterung).
Jeff Schaller