ubuntuusers.de

Was ich an readarray nicht verstehe ...

Status: Gelöst | Ubuntu-Version: Server 23.10 (Mantic Minotaur)
Antworten |

wxpte

Anmeldungsdatum:
20. Januar 2007

Beiträge: 1388

angeregt durch den Nachbarthread habe ich ein wenig experimentiert, und versucht, das Problem mit einem Array zu lösen. Die Methode, mit der ich so etwas bisher immer gemacht habe, funktioniert auch einwandfrei:

1
2
3
4
5
6
while read zeile; do
   readarray -d' ' zeile <<< $zeile
   echo ${zeile[0]}
   echo ${zeile[1]}
   echo
done < zweispalter.txt
eins
zwei

halli
hallo

huhu
haha

mama
papa

Wenn ich aber nun versuche, den Array direkt einzulesen:

1
2
3
4
5
while readarray -d' ' zeile; do
   echo ${zeile[0]}
   echo ${zeile[1]}
   echo
done < zweispalter.txt

dann läuft das Skript in eine Endlosschleife. Warum ist das so?

Außerdem:

  • Option -t: Welcher "nachfolgende Trenner" wird denn da entfernt (vor allem, wenn ich mit -d einen anderen Trenner festlege)? Bisher habe ich noch keinen Unterschied zwischen der Verwendung von -t und dessen Weglassen feststellen können. Im Gegenteil: in einer csv-Datei muss ich sogar am Zeilenende noch ein Semikolon hinzufügen, damit das Einlesen der Zeilen als Array überhaupt richtig funktioniert.

  • Optionen -C und -c: Was genau ist ein "Callback"? Was macht man damit? Ich hatte vermutet, dass man damit die Anzahl der Array-Felder festlegen kann, aber das scheint nicht der Fall zu sein.

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13174

Solche Sachen kann man recht einfach ausprobieren.

wxpte schrieb:

Wenn ich aber nun versuche, den Array direkt einzulesen:

1
2
3
4
5
while readarray -d' ' zeile; do
   echo ${zeile[0]}
   echo ${zeile[1]}
   echo
done < zweispalter.txt

dann läuft das Skript in eine Endlosschleife. Warum ist das so?

1
2
$ readarray -t foo </dev/null; echo $?
0

bash Manpage:

              mapfile returns successfully unless an invalid option or option argument is supplied, array is  invalid
              or unassignable, or if array is not an indexed array.
  • Option -t: Welcher "nachfolgende Trenner" wird denn da entfernt (vor allem, wenn ich mit -d einen anderen Trenner festlege)? Bisher habe ich noch keinen Unterschied zwischen der Verwendung von -t und dessen Weglassen feststellen können.

1
2
3
4
5
6
7
8
9
$ cat x
11 22
33 44 55
$ readarray foo <x; echo "${#foo[@]} - |${foo[*]}|"
2 - |11 22
 33 44 55
|
$ readarray -t foo <x; echo "${#foo[@]} - |${foo[*]}|"
2 - |11 22 33 44 55|
  • Optionen -C und -c: Was genau ist ein "Callback"?

https://de.wikipedia.org/wiki/Callback-Funktion

Was macht man damit? Ich hatte vermutet, dass man damit die Anzahl der Array-Felder festlegen kann, aber das scheint nicht der Fall zu sein.

1
2
3
4
5
$ readarray -t -c 1 -C 'echo X' foo <x
X 0 11 22
X 1 33 44 55
$ echo "${#foo[@]} - |${foo[*]}|"
2 - |11 22 33 44 55|

wxpte

(Themenstarter)

Anmeldungsdatum:
20. Januar 2007

Beiträge: 1388

rklm schrieb:

Solche Sachen kann man recht einfach ausprobieren.

"Einfach" ist wohl relativ. Für einen ausgebildeten Informatiker ist es einfach, das glaube ich dir gerne. 😆

1
2
$ readarray -t foo </dev/null; echo $?
0

bash Manpage:

              mapfile returns successfully unless an invalid option or option argument is supplied, array is  invalid
              or unassignable, or if array is not an indexed array.

OK, verstanden: read braucht einen Inhalt (sonst ist der Exitstatus 1), readarray dagegen nicht unbedingt.

1
2
3
4
5
6
7
8
9
$ cat x
11 22
33 44 55
$ readarray foo <x; echo "${#foo[@]} - |${foo[*]}|"
2 - |11 22
 33 44 55
|
$ readarray -t foo <x; echo "${#foo[@]} - |${foo[*]}|"
2 - |11 22 33 44 55|

Anhand deines Beispiels wurde mir noch nicht ersichtlich, was da eigentlich passiert. Aber wenn man den Trenner ändert, dann wird es deutlich:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
________
18:42 ~$ cat x
11;22
33;44;55
________
18:42 ~$ readarray -d';' foo <x; echo "${#foo[@]} - |${foo[*]}|"
4 - |11; 22
33; 44; 55
|
________
18:42 ~$ readarray -td';' foo <x; echo "${#foo[@]} - |${foo[*]}|"
4 - |11 22
33 44 55
|
________
18:43 ~$

Bei den an das Array übergebenen Argumenten ändert sich also nichts, lediglich die Anzeige der Trenner wird bei der Ausgabe des gesamten Arrays unterdrückt. Dass newline bei Linux ein ganz normales Zeichen (wie jedes andere auch) ist, wird mich wohl ewig verwirren. 🥴

  • Optionen -C und -c: Was genau ist ein "Callback"?

https://de.wikipedia.org/wiki/Callback-Funktion

Was macht man damit? Ich hatte vermutet, dass man damit die Anzahl der Array-Felder festlegen kann, aber das scheint nicht der Fall zu sein.

1
2
3
4
5
$ readarray -t -c 1 -C 'echo X' foo <x
X 0 11 22
X 1 33 44 55
$ echo "${#foo[@]} - |${foo[*]}|"
2 - |11 22 33 44 55|

Da werde ich wohl noch eine Weile brauchen, um das wirklich zu durchdringen. Anhand des Wikipedia-Artikels habe ich bis jetzt nur eine vage Vorstellung von dem Funktionsumfang dieser beiden Optionen.

Aber danke für die Informationen, so ein wenig sehe ich da schon klarer. Jetzt raucht mir erst einmal der Schädel. 🤣

shiro Team-Icon

Supporter

Anmeldungsdatum:
20. Juli 2020

Beiträge: 1246

Aber danke für die Informationen, so ein wenig sehe ich da schon klarer. Jetzt raucht mir erst einmal der Schädel.

Da du schon dabei bist, hier noch ein wenig weiteres Feuer für den Schädel 😉

$ unset fx fy
$ while read -a foo ; do echo "${#foo[@]}-|${foo[*]}|"; fx+=( ${foo[*]} ); fy+=( "${foo[*]}" ); done <x
2-|11 22|
3-|33 44 55|
$ echo "${#foo[@]}-|${foo[*]}|"
0-||
$ echo "fx:${#fx[@]}-|${fx[*]}|"
fx:5-|11 22 33 44 55|
$ echo "fy:${#fy[@]}-|${fy[*]}|"
fy:2-|11 22 33 44 55|
$ 

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13174

wxpte schrieb:

rklm schrieb:

Solche Sachen kann man recht einfach ausprobieren.

"Einfach" ist wohl relativ. Für einen ausgebildeten Informatiker ist es einfach, das glaube ich dir gerne. 😆

Jede, die Skripte schreiben will, sollte dazu in der Lage sein. Man muss seinen Code ja auch testen.

Bei den an das Array übergebenen Argumenten ändert sich also nichts, lediglich die Anzeige der Trenner wird bei der Ausgabe des gesamten Arrays unterdrückt.

Das ist falsch. Option -t bewirkt, dass die Trennzeichen nicht im Array gespeichert werden. Sie werden also nicht "bei der Ausgabe des gesamten Arrays unterdrückt". Das wäre auch sehr unlogisch, wenn eine Option von readarray Auswirkungen auf irgendein folgendes echo hätte.

Da werde ich wohl noch eine Weile brauchen, um das wirklich zu durchdringen. Anhand des Wikipedia-Artikels habe ich bis jetzt nur eine vage Vorstellung von dem Funktionsumfang dieser beiden Optionen.

Man kann z.B. den Ladefortschritt bei großen Dateien anzeigen lassen, indem man z.B. alle 1000 Zeilen eine Ausgabe macht.

Aber danke für die Informationen, so ein wenig sehe ich da schon klarer. Jetzt raucht mir erst einmal der Schädel. 🤣

Gerne. 😉

wxpte

(Themenstarter)

Anmeldungsdatum:
20. Januar 2007

Beiträge: 1388

rklm schrieb:

wxpte schrieb:

rklm schrieb:

Solche Sachen kann man recht einfach ausprobieren.

"Einfach" ist wohl relativ. Für einen ausgebildeten Informatiker ist es einfach, das glaube ich dir gerne. 😆

Jede, die Skripte schreiben will, sollte dazu in der Lage sein. Man muss seinen Code ja auch testen.

Ob jede, die Skripte schreiben will, dazu in der Lage sein sollte, hat nichts damit zu tun, ob es für sie auch einfach ist.

wxpte

(Themenstarter)

Anmeldungsdatum:
20. Januar 2007

Beiträge: 1388

shiro schrieb:

Da du schon dabei bist, hier noch ein wenig weiteres Feuer für den Schädel 😉

$ unset fx fy
$ while read -a foo ; do echo "${#foo[@]}-|${foo[*]}|"; fx+=( ${foo[*]} ); fy+=( "${foo[*]}" ); done <x
2-|11 22|
3-|33 44 55|
$ echo "${#foo[@]}-|${foo[*]}|"
0-||
$ echo "fx:${#fx[@]}-|${fx[*]}|"
fx:5-|11 22 33 44 55|
$ echo "fy:${#fy[@]}-|${fy[*]}|"
fy:2-|11 22 33 44 55|
$ 

Da kann man mal sehen! 😮 Das bestätigt mich nur in meiner Ansicht, dass das bash-manual das unpraktischste von allen ist. Da wurden alle integrierten Befehle und Anweisungen in ein einziges Dokument gepfropft. Gerade Anweisungen wie read sind mit der SuFu von less nur mühsam zu finden, deswegen schaue ich auch nicht so gerne da rein. Wenn ich gewusst hätte, dass die Array-Funktion in read bereits integriert ist, dann wäre ich wahrscheinlich längst einen Schritt weiter.

Noch dazu sind gerade die komplexeren Features ausschließlich in englischer Sprache verfügbar. Ich bin ja schon sehr dankbar dafür, dass sich inzwischen technisch versierte und sprachbegabte Menschen dazu bereit gefunden haben, wenigstens die Manuals zu grep, sed etc. ins Deutsche zu übersetzen. Wenn ich nur durchgängig verstehen würde, wovon im Manual zu bash die Rede ist, dann würde ich mich ja selbst beim Übersetzungsprojekt zu den Manuals melden. Aber so, wie es tatsächlich ist, würde der Bock nur zum Gärtner gemacht. ☹

Recht herzlichen Dank, dann werde ich an dieser Stelle mal weiter üben.

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13174

wxpte schrieb:

Da kann man mal sehen! 😮 Das bestätigt mich nur in meiner Ansicht, dass das bash-manual das unpraktischste von allen ist. Da wurden alle integrierten Befehle und Anweisungen in ein einziges Dokument gepfropft. Gerade Anweisungen wie read sind mit der SuFu von less nur mühsam zu finden, deswegen schaue ich auch nicht so gerne da rein.

Es gibt eine Sektion "SHELL BUILTIN COMMANDS". Da finden sich alle. Du kannst die Referenz auch online lesen; da ist die Struktur vielleicht etwas einfacher zu finden.

Wenn ich gewusst hätte, dass die Array-Funktion in read bereits integriert ist, dann wäre ich wahrscheinlich längst einen Schritt weiter.

Das finde ich eine seltsame Formulierung, die m.E. nicht ganz richtig ist. read und readarray haben zwei ähnliche aber doch deutlich unterschiedliche Aufgaben: read liest eine Zeile, die in Wörter geteilt wird und die entweder auf angegebene einzelne Variablen oder ein Array (mit Option -a) verteilt wird. readarray bzw. mapfile hingegen ist dafür gedacht eine komplette Datei (oder Teile davon, jedenfalls mehrere Zeilen) einzulesen und alle gelesenen Zeilen in ein Array zu speichern.

Man kann natürlich beide Funktionen dafür benutzen eine komplette Datei zu verarbeiten, aber beide Ansätze haben deutliche Unterschiede: mit read in einer Schleife ist der Speicherbedarf geringer, aber man kann nicht so leicht zwischen Zeilen hin und her springen, wie das möglich ist, wenn man die gesamte Datei in einem Array hat. Außerdem gehört read zum POSIX-Standard und ist damit tendenziell in mehr Shells und Umgebungen verfügbar, während readarray eine bash-Ergänzung ist.

Noch dazu sind gerade die komplexeren Features ausschließlich in englischer Sprache verfügbar.

Ja, das ist in der IT wirklich ein Nachteil, wenn man es nicht so mit dem Englischen hat.

Recht herzlichen Dank, dann werde ich an dieser Stelle mal weiter üben.

👍

wxpte

(Themenstarter)

Anmeldungsdatum:
20. Januar 2007

Beiträge: 1388

rklm schrieb:

Es gibt eine Sektion "SHELL BUILTIN COMMANDS". Da finden sich alle. Du kannst die Referenz auch online lesen; da ist die Struktur vielleicht etwas einfacher zu finden.

Das Lustige ist ja, dass ich inzwischen eine Methode gefunden habe, die builtin commands recht einfach zu finden.

Das finde ich eine seltsame Formulierung, die m.E. nicht ganz richtig ist. read und readarray haben zwei ähnliche aber doch deutlich unterschiedliche Aufgaben: read liest eine Zeile, die in Wörter geteilt wird und die entweder auf angegebene einzelne Variablen oder ein Array (mit Option -a) verteilt wird. readarray bzw. mapfile hingegen ist dafür gedacht eine komplette Datei (oder Teile davon, jedenfalls mehrere Zeilen) einzulesen und alle gelesenen Zeilen in ein Array zu speichern.

Ja, auch diesen Irrtum habe ich in den nachfolgenden Experimenten festgestellt (es sah halt auf den ersten Blick so aus). Genau wie ich inzwischen den grundlegenden Irrtum, der mich überhaupt erst zu dieser Frage veranlasst hat, herausgefunden habe. Weiter oben schrieb ich ja bereits schon einmal:

wxpte schrieb:

Dass newline bei Linux ein ganz normales Zeichen (wie jedes andere auch) ist, wird mich wohl ewig verwirren. 🥴

Tatsächlich komme ich zeitweise immer wieder zu der falschen Annahme, readarray bestünde aus zwei Ebenen:

  • Eine Ebene, auf der mit Hilfe des newline mehrere Arrays voneinander getrennt werden

  • Eine Ebene, auf der mit einem anderen, frei wählbaren Trenner (default: space) die Arrayfelder voneinander getrennt werden

In Wirklichkeit ist es wohl aber so, dass readarray und read aus dem Datenstrom lesen und jeweils nur einen Trenner zur Aufteilung zur Verfügung haben.

Deswegen funktioniert das im Eingangspost zuerst gelistete Skript ja auch: read liest wegen des default-Trenners newline zeilenweise ein (wobei man interessanderweise auch hier den Trenner mit der Option -d ändern kann), und while baut aus jeder Einheit einen Schleifendurchgang. Dann wird im nächsten Schritt mit readarray die nur aus der jeweiligen Zeile bestehende Variable eingelesen, so dass am Ende ein sauberes Array herauskommt.

Wenn man also zwei Ebenen haben will, gibt es wohl keine Alternative im Shell-Skripting, als read und readarray in der Schleife zu verschachteln. Nachdem ich das erst einmal herausgefunden habe, löste sich viel von meinem illusionären Verständnis auf.

rklm schrieb:

Das ist falsch. Option -t bewirkt, dass die Trennzeichen nicht im Array gespeichert werden. Sie werden also nicht "bei der Ausgabe des gesamten Arrays unterdrückt". Das wäre auch sehr unlogisch, wenn eine Option von readarray Auswirkungen auf irgendein folgendes echo hätte.

Auch das hat sich in meinen Übungen inzwischen deutlich bestätigt. Denn wenn ich mit einer csv-Datei nach der Zerlegung in Arrays etwas anderes mache, als ein schnödes echo, dann bekomme ich jede Menge Fehlermeldungen, dass der Befehl unbekannt sei, um die Ohren gehauen. Offensichtlich interpretiert bash die in den Arrayfeldern enthaltenen Semikola dann als Trenner zwischen Befehlen. Die Verwendung der Option -t ist daher unentbehrlich.

Alles in allem hat mir dieser Durchgang nicht nur einige neue Erkenntnisse verschafft, sondern auch irgendwie Spaß gemacht. 😊

Also: nochmals vielen Dank an alle für die Unterstützung.

Antworten |