Alle Antworten auf diese Frage sind auf die eine oder andere Weise falsch.
Falsche Antwort # 1
IFS=', ' read -r -a array <<< "$string"
1: Dies ist ein Missbrauch von $IFS. Der Wert der $IFSVariablen wird nicht als einzelnes Zeichenfolgentrennzeichen mit variabler Länge verwendet, sondern als Satz von Zeichenfolgen-Trennzeichen mit einem Zeichen , wobei jedes Feld, readdas von der Eingabezeile abgespalten wird, durch ein beliebiges Zeichen im Satz abgeschlossen werden kann (Komma oder Leerzeichen in diesem Beispiel).
Tatsächlich ist für die echten Stickler da draußen die volle Bedeutung von $IFSetwas mehr involviert. Aus dem Bash-Handbuch :
Die Shell behandelt jedes Zeichen von IFS als Trennzeichen und teilt die Ergebnisse der anderen Erweiterungen in Wörter auf, wobei diese Zeichen als Feldterminatoren verwendet werden. Wenn IFS nicht gesetzt ist oder sein Wert genau <Leerzeichen> <Tab> <Newline> ist , die Standardeinstellung, dann die Sequenzen von <Leerzeichen> , <Tab> und <Newline> am Anfang und Ende der Ergebnisse der vorherigen Erweiterungen werden ignoriert, und jede Folge von IFS- Zeichen, die nicht am Anfang oder Ende stehen, dient zur Abgrenzung von Wörtern. Wenn IFS einen anderen Wert als den Standardwert hat, werden Sequenzen der Leerzeichen <Leerzeichen> , <Tab> und <verwendetwerden am Anfang und Ende des Wortes ignoriert, solange das Leerzeichen den Wert von IFS (ein IFS- Leerzeichen) hat. Jedes Zeichen in IFS , das kein IFS- Leerzeichen ist, sowie alle benachbarten IFS- Leerzeichen begrenzen ein Feld. Eine Folge von IFS- Leerzeichen wird ebenfalls als Trennzeichen behandelt. Wenn der Wert von IFS null ist, erfolgt keine Wortaufteilung.
Grundsätzlich $IFSkönnen Felder für nicht standardmäßige Nicht-Null-Werte von entweder mit (1) einer Folge von einem oder mehreren Zeichen getrennt werden, die alle aus dem Satz von "IFS-Leerzeichen" stammen (d. H. Welcher von <Leerzeichen>) . <tab> und <newline> ("newline" bedeutet Zeilenvorschub (LF) ) sind überall in $IFS) oder (2) alle Nicht-"IFS-Leerzeichen", die $IFSzusammen mit den "IFS-Leerzeichen" vorhanden sind in der Eingabezeile.
Für das OP ist es möglich, dass der zweite Trennungsmodus, den ich im vorherigen Absatz beschrieben habe, genau das ist, was er für seine Eingabezeichenfolge wünscht, aber wir können ziemlich sicher sein, dass der erste Trennungsmodus, den ich beschrieben habe, überhaupt nicht korrekt ist. Was wäre zum Beispiel, wenn seine Eingabezeichenfolge wäre 'Los Angeles, United States, North America'?
IFS=', ' read -ra a <<<'Los Angeles, United States, North America'; declare -p a;
## declare -a a=([0]="Los" [1]="Angeles" [2]="United" [3]="States" [4]="North" [5]="America")
2: Auch wenn Sie waren diese Lösung mit einem Einzel-Zeichen - Separator (wie ein Komma von selbst, daß ohne folgende Leerzeichen oder anderem Gepäck ist) zu verwenden, wenn der Wert der $stringVariable irgendwelche LFs enthalten passiert, dann readwerden Beenden Sie die Verarbeitung, sobald der erste LF gefunden wird. Das readeingebaute verarbeitet nur eine Zeile pro Aufruf. Dies gilt auch dann, wenn Sie Eingaben nur an die readAnweisung weiterleiten oder umleiten , wie wir es in diesem Beispiel mit dem Here-String- Mechanismus tun , und somit garantiert, dass unverarbeitete Eingaben verloren gehen. Der Code, der das readeingebaute System antreibt, kennt den Datenfluss in seiner enthaltenen Befehlsstruktur nicht.
Sie könnten argumentieren, dass dies wahrscheinlich kein Problem verursacht, aber dennoch eine subtile Gefahr darstellt, die nach Möglichkeit vermieden werden sollte. readDies wird durch die Tatsache verursacht, dass das integrierte Gerät tatsächlich zwei Ebenen der Eingabeaufteilung durchführt: zuerst in Zeilen, dann in Felder. Da das OP nur eine Aufteilungsebene wünscht, ist diese Verwendung des integrierten readSystems nicht angemessen, und wir sollten dies vermeiden.
3: Ein nicht offensichtliches potenzielles Problem bei dieser Lösung besteht darin, dass readdas nachfolgende Feld immer gelöscht wird, wenn es leer ist, obwohl ansonsten leere Felder erhalten bleiben. Hier ist eine Demo:
string=', , a, , b, c, , , '; IFS=', ' read -ra a <<<"$string"; declare -p a;
## declare -a a=([0]="" [1]="" [2]="a" [3]="" [4]="b" [5]="c" [6]="" [7]="")
Vielleicht würde sich das OP nicht darum kümmern, aber es ist immer noch eine Einschränkung, über die es sich zu wissen lohnt. Dies verringert die Robustheit und Allgemeingültigkeit der Lösung.
Dieses Problem kann gelöst werden, indem unmittelbar vor dem Einspeisen ein Dummy-Trennzeichen an die Eingabezeichenfolge angehängt wird read, wie ich später zeigen werde.
Falsche Antwort # 2
string="1:2:3:4:5"
set -f # avoid globbing (expansion of *).
array=(${string//:/ })
Ähnliche Idee:
t="one,two,three"
a=($(echo $t | tr ',' "\n"))
(Hinweis: Ich habe die fehlenden Klammern um die Befehlsersetzung hinzugefügt, die der Antwortende anscheinend weggelassen hat.)
Ähnliche Idee:
string="1,2,3,4"
array=(`echo $string | sed 's/,/\n/g'`)
Diese Lösungen nutzen die Wortaufteilung in einer Array-Zuweisung, um die Zeichenfolge in Felder aufzuteilen. Lustigerweise readverwendet die allgemeine Wortaufteilung ebenso wie die allgemeine $IFSVariable die spezielle Variable, obwohl in diesem Fall impliziert wird, dass sie auf den Standardwert <Leerzeichen> <Tab> <Newline> und damit auf eine beliebige Folge eines oder mehrerer IFS gesetzt ist Zeichen (die jetzt alle Leerzeichen sind) werden als Feldtrennzeichen betrachtet.
Dies löst das Problem von zwei Aufteilungsebenen, die von begangen werden read, da die Wortaufteilung an sich nur eine Aufteilungsebene darstellt. Das Problem besteht jedoch nach wie vor darin, dass die einzelnen Felder in der Eingabezeichenfolge bereits $IFSZeichen enthalten können und daher während des Wortaufteilungsvorgangs nicht ordnungsgemäß aufgeteilt werden. Dies ist bei keiner der von diesen Antwortenden bereitgestellten Beispiel-Eingabezeichenfolgen der Fall (wie praktisch ...), aber das ändert natürlich nichts an der Tatsache, dass eine Codebasis, die diese Redewendung verwendet, dann das Risiko eingehen würde Sprengung, wenn diese Annahme jemals irgendwann auf der ganzen Linie verletzt wurde. Betrachten Sie noch einmal mein Gegenbeispiel von 'Los Angeles, United States, North America'(oder 'Los Angeles:United States:North America').
Auch wird das Wort Aufspalten normalerweise durch gefolgt Dateierweiterung ( aka Pfadnamenerweiterung aka Globbing), die, wenn sie durchgeführt würden potentiell beschädigte Worte die Zeichen enthalten *, ?oder [gefolgt von ](und, falls extglobgesetzt, geklammert Fragmenten mit vorangestellter ?, *, +, @, oder !) indem Sie sie mit Dateisystemobjekten abgleichen und die Wörter ("Globs") entsprechend erweitern. Der erste dieser drei Antwortenden hat dieses Problem geschickt unterboten, indem er set -fzuvor ausgeführt wurde, um das Globbing zu deaktivieren. Technisch funktioniert das (obwohl Sie wahrscheinlich hinzufügen solltenset +f Danach können Sie das Globbing für nachfolgenden Code wieder aktivieren, der möglicherweise davon abhängt. Es ist jedoch unerwünscht, sich mit den globalen Shell-Einstellungen herumschlagen zu müssen, um eine grundlegende Analyseoperation von String zu Array im lokalen Code zu hacken.
Ein weiteres Problem bei dieser Antwort ist, dass alle leeren Felder verloren gehen. Dies kann je nach Anwendung ein Problem sein oder auch nicht.
Hinweis: Wenn Sie diese Lösung verwenden möchten, ist es besser, die ${string//:/ }Form der Parametererweiterung "Mustersubstitution" zu verwenden , als sich die Mühe zu machen, eine Befehlssubstitution (die die Shell teilt) aufzurufen, eine Pipeline zu starten und Ausführen einer externen ausführbaren Datei ( troder sed), da die Parametererweiterung eine rein shellinterne Operation ist. (Außerdem sollte für die trund sed-Lösungen die Eingabevariable innerhalb der Befehlssubstitution in doppelte Anführungszeichen gesetzt werden. Andernfalls würde die Wortaufteilung im echoBefehl wirksam und möglicherweise die Feldwerte beeinträchtigen. Außerdem ist die $(...)Form der Befehlssubstitution der alten vorzuziehen`...` Form, da es das Verschachteln von Befehlsersetzungen vereinfacht und eine bessere Syntaxhervorhebung durch Texteditoren ermöglicht.)
Falsche Antwort # 3
str="a, b, c, d" # assuming there is a space after ',' as in Q
arr=(${str//,/}) # delete all occurrences of ','
Diese Antwort ist fast die gleiche wie # 2 . Der Unterschied besteht darin, dass der Antwortende davon ausgegangen ist, dass die Felder durch zwei Zeichen begrenzt sind, von denen eines in der Standardeinstellung dargestellt $IFSwird und das andere nicht. Er hat diesen ziemlich spezifischen Fall gelöst, indem er das nicht IFS-dargestellte Zeichen unter Verwendung einer Mustersubstitutionserweiterung entfernt und dann die Felder auf dem überlebenden IFS-dargestellten Trennzeichen durch Wortaufteilung aufteilt.
Dies ist keine sehr generische Lösung. Darüber hinaus kann argumentiert werden, dass das Komma hier wirklich das "primäre" Trennzeichen ist und dass das Entfernen und dann abhängig vom Leerzeichen für die Feldaufteilung einfach falsch ist. Betrachten Sie noch einmal mein Gegenbeispiel : 'Los Angeles, United States, North America'.
Auch hier könnte die Dateinamenerweiterung die erweiterten Wörter beschädigen. Dies kann jedoch verhindert werden, indem das Globbing für die Zuweisung mit set -fund dann vorübergehend deaktiviert wird set +f.
Auch hier gehen alle leeren Felder verloren, was je nach Anwendung ein Problem sein kann oder nicht.
Falsche Antwort # 4
string='first line
second line
third line'
oldIFS="$IFS"
IFS='
'
IFS=${IFS:0:1} # this is useful to format your code with tabs
lines=( $string )
IFS="$oldIFS"
Dies ist insofern ähnlich zu # 2 und # 3 , als es die Wortaufteilung verwendet, um die Arbeit zu erledigen, nur dass der Code jetzt explizit so festgelegt wird $IFS, dass er nur das in der Eingabezeichenfolge vorhandene Einzelzeichen-Feldtrennzeichen enthält. Es sollte wiederholt werden, dass dies für Feldtrennzeichen mit mehreren Zeichen wie das Komma-Raum-Trennzeichen des OP nicht funktionieren kann. Für ein Einzelzeichen-Trennzeichen wie das in diesem Beispiel verwendete LF ist es jedoch nahezu perfekt. Die Felder können nicht unbeabsichtigt in der Mitte aufgeteilt werden, wie wir bei früheren falschen Antworten gesehen haben, und es gibt je nach Bedarf nur eine Aufteilungsebene.
Ein Problem besteht darin, dass die Dateinamenerweiterung betroffene Wörter wie zuvor beschrieben beschädigt. Dies kann jedoch erneut gelöst werden, indem die kritische Anweisung in set -fund eingeschlossen wird set +f.
Ein weiteres potenzielles Problem besteht darin, dass, da LF wie zuvor definiert als "IFS-Leerzeichen" qualifiziert ist, alle leeren Felder verloren gehen, genau wie in # 2 und # 3 . Dies wäre natürlich kein Problem, wenn das Trennzeichen ein Nicht-IFS-Leerzeichen ist, und je nach Anwendung spielt es möglicherweise keine Rolle, beeinträchtigt jedoch die Allgemeingültigkeit der Lösung.
Zusammenfassend lässt sich sagen, dass Sie ein Ein-Zeichen-Trennzeichen haben und es sich entweder nicht um ein "IFS-Leerzeichen" handelt oder dass Sie sich nicht für leere Felder interessieren und die kritische Anweisung in set -fund einschließen. set +fDann funktioniert diese Lösung , aber sonst nicht.
(Zur Information kann das Zuweisen eines LF zu einer Variablen in bash auch einfacher mit der $'...'Syntax erfolgen, z IFS=$'\n';.
Falsche Antwort # 5
countries='Paris, France, Europe'
OIFS="$IFS"
IFS=', ' array=($countries)
IFS="$OIFS"
Ähnliche Idee:
IFS=', ' eval 'array=($string)'
Diese Lösung ist effektiv eine Kreuzung zwischen # 1 (indem sie $IFSauf Komma setzt ) und # 2-4 (indem sie die Wortaufteilung verwendet, um die Zeichenfolge in Felder aufzuteilen). Aus diesem Grund leidet es unter den meisten Problemen, die alle oben genannten falschen Antworten betreffen, ähnlich wie die schlimmste aller Welten.
Auch in Bezug auf die zweite Variante scheint der evalAufruf völlig unnötig zu sein, da sein Argument ein Zeichenfolgenliteral in einfachen Anführungszeichen ist und daher statisch bekannt ist. Die Verwendung evalauf diese Weise bietet jedoch einen nicht offensichtlichen Vorteil . Normalerweise, wenn Sie einen einfachen Befehl ausführen , die aus einer variablen Zuordnung besteht nur , ohne einen tatsächlichen Befehl Wort und bedeutet es folgende erfolgt die Zuordnung Wirkung in der Shell - Umgebung:
IFS=', '; ## changes $IFS in the shell environment
Dies gilt auch dann, wenn der einfache Befehl mehrere Variablenzuweisungen umfasst . Auch hier wirken sich alle Variablenzuweisungen auf die Shell-Umgebung aus, solange kein Befehlswort vorhanden ist:
IFS=', ' array=($countries); ## changes both $IFS and $array in the shell environment
Wenn die Variablenzuweisung jedoch an einen Befehlsnamen angehängt ist (ich nenne dies gerne eine "Präfixzuweisung"), wirkt sich dies nicht auf die Shell-Umgebung aus, sondern nur auf die Umgebung des ausgeführten Befehls, unabhängig davon, ob es sich um einen integrierten Befehl handelt oder extern:
IFS=', ' :; ## : is a builtin command, the $IFS assignment does not outlive it
IFS=', ' env; ## env is an external command, the $IFS assignment does not outlive it
Relevantes Zitat aus dem Bash-Handbuch :
Wenn kein Befehlsname angezeigt wird, wirken sich die Variablenzuweisungen auf die aktuelle Shell-Umgebung aus. Andernfalls werden die Variablen zur Umgebung des ausgeführten Befehls hinzugefügt und wirken sich nicht auf die aktuelle Shell-Umgebung aus.
Es ist möglich, diese Funktion der Variablenzuweisung zu nutzen, um Änderungen $IFSnur vorübergehend vorzunehmen, wodurch wir das gesamte Spiel zum Speichern und Wiederherstellen vermeiden können, wie es bei der $OIFSVariablen in der ersten Variante der Fall ist. Die Herausforderung, der wir uns hier gegenübersehen, besteht darin, dass der Befehl, den wir ausführen müssen, selbst eine bloße Variablenzuweisung ist und daher kein Befehlswort enthält, um die $IFSZuweisung vorübergehend zu machen . Sie könnten sich denken, warum fügen Sie der Anweisung nicht einfach ein No-Op-Befehlswort hinzu : builtin, um die $IFSZuweisung vorübergehend zu machen ? Dies funktioniert nicht, da die $arrayZuweisung dann auch vorübergehend wäre :
IFS=', ' array=($countries) :; ## fails; new $array value never escapes the : command
Wir befinden uns also effektiv in einer Sackgasse, ein bisschen wie ein Catch-22. Wenn evalder Code ausgeführt wird, wird er in der Shell-Umgebung ausgeführt, als wäre es normaler statischer Quellcode. Daher können wir die $arrayZuweisung innerhalb des evalArguments ausführen, damit sie in der Shell-Umgebung wirksam wird, während die $IFSPräfixzuweisung dies tut wird dem evalBefehl vorangestellt, überlebt den evalBefehl nicht. Dies ist genau der Trick, der in der zweiten Variante dieser Lösung verwendet wird:
IFS=', ' eval 'array=($string)'; ## $IFS does not outlive the eval command, but $array does
Wie Sie sehen, handelt es sich also tatsächlich um einen ziemlich cleveren Trick, der genau das erreicht, was erforderlich ist (zumindest in Bezug auf die Zuweisungseffekte), und zwar auf eine nicht offensichtliche Weise. Ich bin eigentlich nicht gegen diesen Trick im Allgemeinen, trotz der Beteiligung von eval; Achten Sie nur darauf, die Argumentzeichenfolge in einfache Anführungszeichen zu setzen, um sich vor Sicherheitsbedrohungen zu schützen.
Aber auch hier ist dies aufgrund der "schlimmsten aller Welten" Agglomeration von Problemen immer noch eine falsche Antwort auf die Forderung des OP.
Falsche Antwort # 6
IFS=', '; array=(Paris, France, Europe)
IFS=' ';declare -a array=(Paris France Europe)
Ähm ... was? Das OP verfügt über eine Zeichenfolgenvariable, die in ein Array analysiert werden muss. Diese "Antwort" beginnt mit dem wörtlichen Inhalt der Eingabezeichenfolge, die in ein Array-Literal eingefügt wird. Ich denke, das ist eine Möglichkeit, es zu tun.
Es sieht so aus, als hätte der Antwortende angenommen, dass die $IFSVariable die gesamte Bash-Analyse in allen Kontexten beeinflusst, was nicht der Fall ist. Aus dem Bash-Handbuch:
IFS Der interne Feldtrenner, der zum Teilen von Wörtern nach der Erweiterung und zum Teilen von Zeilen in Wörter mit dem Befehl read builtin verwendet wird. Der Standardwert ist <Leerzeichen> <Tab> <Neue Zeile> .
Die $IFSspezielle Variable wird also eigentlich nur in zwei Kontexten verwendet: (1) Wortaufteilung, die nach der Erweiterung durchgeführt wird (dh nicht beim Parsen des Bash-Quellcodes) und (2) zum Aufteilen von Eingabezeilen in Wörter durch das readeingebaute.
Lassen Sie mich versuchen, dies klarer zu machen. Ich denke, es könnte gut sein, zwischen Parsen und Ausführen zu unterscheiden . Bash muss zuerst den Quellcode analysieren , was offensichtlich ein Parsing- Ereignis ist, und später den Code ausführen , wenn die Erweiterung ins Bild kommt. Expansion ist wirklich ein Ausführungsereignis . Außerdem habe ich Probleme mit der Beschreibung der $IFSVariablen, die ich gerade zitiert habe. Anstatt zu sagen, dass die Wortaufteilung nach der Erweiterung durchgeführt wird , würde ich sagen, dass die Wortaufteilung während der Erweiterung durchgeführt wird, oder, vielleicht noch genauer, die Wortaufteilung ist ein Teil davonder Expansionsprozess. Der Ausdruck "Wortaufteilung" bezieht sich nur auf diesen Expansionsschritt; Es sollte niemals verwendet werden, um auf das Parsen von Bash-Quellcode zu verweisen, obwohl die Dokumente leider die Wörter "split" und "words" häufig herumwerfen. Hier ist ein relevanter Auszug aus der linux.die.net-Version des Bash-Handbuchs:
Die Erweiterung wird in der Befehlszeile ausgeführt, nachdem sie in Wörter aufgeteilt wurde. Es werden sieben Arten der Erweiterung durchgeführt: Klammererweiterung , Tilde-Erweiterung , Parameter- und Variablenerweiterung , Befehlssubstitution , arithmetische Erweiterung , Wortteilung und Pfadnamenerweiterung .
Die Reihenfolge der Erweiterungen lautet: Klammererweiterung; Tilde-Erweiterung, Parameter- und Variablenerweiterung, arithmetische Erweiterung und Befehlssubstitution (von links nach rechts); Wortaufteilung; und Pfadnamenerweiterung.
Sie könnten argumentieren, dass die GNU-Version des Handbuchs etwas besser abschneidet, da sie im ersten Satz des Erweiterungsabschnitts das Wort "Token" anstelle von "Wörtern" verwendet:
Die Erweiterung wird in der Befehlszeile ausgeführt, nachdem sie in Token aufgeteilt wurde.
Der wichtige Punkt ist, $IFSändert nichts an der Art und Weise, wie Bash den Quellcode analysiert. Das Parsen von Bash-Quellcode ist ein sehr komplexer Prozess, bei dem die verschiedenen Elemente der Shell-Grammatik erkannt werden, z. B. Befehlssequenzen, Befehlslisten, Pipelines, Parametererweiterungen, arithmetische Ersetzungen und Befehlsersetzungen. Zum größten Teil kann der Bash-Parsing-Prozess nicht durch Aktionen auf Benutzerebene wie Variablenzuweisungen geändert werden (tatsächlich gibt es einige geringfügige Ausnahmen von dieser Regel; siehe beispielsweise die verschiedenen compatxxShell-Einstellungen, die bestimmte Aspekte des Analyseverhaltens im laufenden Betrieb ändern können). Die vorgelagerten "Wörter" / "Token", die sich aus diesem komplexen Analyseprozess ergeben, werden dann gemäß dem allgemeinen Prozess der "Erweiterung" erweitert, wie in den obigen Dokumentationsausschnitten beschrieben, wobei die Wortaufteilung des erweiterten (expandierenden?) Textes in den nachgelagerten Text erfolgt Worte sind einfach ein Schritt dieses Prozesses. Das Teilen von Wörtern berührt nur Text, der aus einem vorhergehenden Erweiterungsschritt ausgespuckt wurde. Literaltext, der direkt aus dem Quell-Bytestream analysiert wurde, ist davon nicht betroffen.
Falsche Antwort # 7
string='first line
second line
third line'
while read -r line; do lines+=("$line"); done <<<"$string"
Dies ist eine der besten Lösungen. Beachten Sie, dass wir wieder verwenden read. Habe ich nicht früher gesagt, dass dies readunangemessen ist, weil es zwei Aufteilungsebenen durchführt, wenn wir nur eine brauchen? Der Trick dabei ist, dass Sie so aufrufen können read, dass effektiv nur eine Aufteilungsebene ausgeführt wird, insbesondere indem nur ein Feld pro Aufruf abgespalten wird, was die Kosten für den wiederholten Aufruf in einer Schleife erforderlich macht. Es ist ein bisschen ein Kinderspiel, aber es funktioniert.
Aber es gibt Probleme. Erstens: Wenn Sie mindestens ein NAME- Argument angebenread , werden führende und nachfolgende Leerzeichen in jedem Feld, das von der Eingabezeichenfolge getrennt ist, automatisch ignoriert. Dies tritt auf, unabhängig davon, ob $IFSder Standardwert festgelegt ist oder nicht, wie weiter oben in diesem Beitrag beschrieben. Nun, das OP kümmert sich möglicherweise nicht darum für seinen spezifischen Anwendungsfall, und tatsächlich kann es ein wünschenswertes Merkmal des Analyseverhaltens sein. Aber nicht jeder, der einen String in Felder analysieren möchte, wird dies wollen. Es gibt jedoch eine Lösung: Eine etwas nicht offensichtliche Verwendung von readbesteht darin, null NAME- Argumente zu übergeben. In diesem Fall readwird die gesamte Eingabezeile, die vom Eingabestream abgerufen wird, in einer Variablen mit dem Namen gespeichert $REPLY, was als Bonus nicht der Fall istEntfernen Sie führende und nachfolgende Leerzeichen vom Wert. Dies ist eine sehr robuste Verwendung, readdie ich in meiner Karriere als Shell-Programmierer häufig ausgenutzt habe. Hier ist eine Demonstration des Unterschieds im Verhalten:
string=$' a b \n c d \n e f '; ## input string
a=(); while read -r line; do a+=("$line"); done <<<"$string"; declare -p a;
## declare -a a=([0]="a b" [1]="c d" [2]="e f") ## read trimmed surrounding whitespace
a=(); while read -r; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]=" a b " [1]=" c d " [2]=" e f ") ## no trimming
Das zweite Problem bei dieser Lösung besteht darin, dass der Fall eines benutzerdefinierten Feldtrennzeichens, wie z. B. des Komma-Bereichs des OP, nicht behandelt wird. Nach wie vor werden Multicharakter-Separatoren nicht unterstützt, was eine unglückliche Einschränkung dieser Lösung darstellt. Wir könnten versuchen, zumindest durch Komma zu teilen, indem wir das Trennzeichen für die -dOption angeben, aber schauen Sie, was passiert:
string='Paris, France, Europe';
a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France")
Vorhersehbarerweise wurde das nicht berücksichtigte umgebende Leerzeichen in die Feldwerte gezogen, und daher müsste dies anschließend durch Trimmvorgänge korrigiert werden (dies könnte auch direkt in der while-Schleife erfolgen). Aber es gibt noch einen weiteren offensichtlichen Fehler: Europa fehlt! Was ist damit passiert? Die Antwort lautet, dass readein fehlerhafter Rückkehrcode zurückgegeben wird, wenn er das Dateiende erreicht (in diesem Fall können wir ihn als Ende der Zeichenfolge bezeichnen), ohne dass ein endgültiger Feldabschluss im letzten Feld auftritt. Dies führt dazu, dass die while-Schleife vorzeitig unterbrochen wird und wir das letzte Feld verlieren.
Technisch gesehen betraf derselbe Fehler auch die vorherigen Beispiele. Der Unterschied besteht darin, dass das Feldtrennzeichen als LF angenommen wurde. Dies ist die Standardeinstellung, wenn Sie die -dOption nicht angeben , und der <<<Mechanismus ("hier-Zeichenfolge") hängt automatisch eine LF an die Zeichenfolge an, bevor sie als eingegeben wird Eingabe in den Befehl. Daher haben wir in diesen Fällen das Problem eines abgelegten Endfelds versehentlich gelöst, indem wir unabsichtlich einen zusätzlichen Dummy-Terminator an die Eingabe angehängt haben. Nennen wir diese Lösung die "Dummy-Terminator" -Lösung. Wir können die Dummy-Terminator-Lösung manuell für jedes benutzerdefinierte Trennzeichen anwenden, indem wir sie selbst mit der Eingabezeichenfolge verketten, wenn wir sie in der Here-Zeichenfolge instanziieren:
a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string,"; declare -p a;
declare -a a=([0]="Paris" [1]=" France" [2]=" Europe")
Dort ist das Problem gelöst. Eine andere Lösung besteht darin, die while-Schleife nur zu unterbrechen, wenn sowohl (1) einen readFehler zurückgegeben hat als auch (2) $REPLYleer ist, was bedeutet read, dass vor dem Erreichen des Dateiende keine Zeichen gelesen werden konnten. Demo:
a=(); while read -rd,|| [[ -n "$REPLY" ]]; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=$' Europe\n')
Dieser Ansatz enthüllt auch den geheimen LF, der vom <<<Umleitungsoperator automatisch an die Here-Zeichenfolge angehängt wird . Es könnte natürlich durch einen expliziten Trimmvorgang, wie vor einem Moment beschrieben, separat entfernt werden, aber offensichtlich löst der manuelle Dummy-Terminator-Ansatz es direkt, also könnten wir einfach damit weitermachen. Die manuelle Dummy-Terminator-Lösung ist insofern recht praktisch, als sie diese beiden Probleme (das Problem mit dem abgelegten Endfeld und das Problem mit dem angehängten LF) auf einmal löst.
Insgesamt ist dies also eine ziemlich leistungsstarke Lösung. Die einzige verbleibende Schwäche ist die mangelnde Unterstützung für Multicharakter-Trennzeichen, auf die ich später noch eingehen werde.
Falsche Antwort # 8
string='first line
second line
third line'
readarray -t lines <<<"$string"
(Dies ist tatsächlich aus demselben Beitrag wie # 7 ; der Antwortende hat zwei Lösungen in demselben Beitrag bereitgestellt.)
Das readarrayeingebaute Synonym für mapfileist ideal. Es ist ein eingebauter Befehl, der einen Bytestream auf einmal in eine Array-Variable analysiert. Kein Durcheinander mit Schleifen, Bedingungen, Ersetzungen oder irgendetwas anderem. Und es entfernt nicht heimlich Leerzeichen von der Eingabezeichenfolge. Und (falls -Onicht angegeben) löscht es bequem das Zielarray, bevor es zugewiesen wird. Aber es ist immer noch nicht perfekt, daher meine Kritik daran als "falsche Antwort".
Um dies aus dem Weg zu räumen, beachten Sie zunächst, dass genau wie beim Verhalten readbeim Parsen von Feldern readarraydas nachfolgende Feld gelöscht wird, wenn es leer ist. Auch dies ist wahrscheinlich kein Problem für das OP, könnte aber für einige Anwendungsfälle sein. Ich werde gleich darauf zurückkommen.
Zweitens werden nach wie vor keine Multicharakter-Begrenzer unterstützt. Ich werde auch gleich eine Lösung dafür finden.
Drittens analysiert die geschriebene Lösung nicht die Eingabezeichenfolge des OP, und tatsächlich kann sie nicht so verwendet werden, wie sie ist, um sie zu analysieren. Ich werde auch kurz darauf eingehen.
Aus den oben genannten Gründen halte ich dies immer noch für eine "falsche Antwort" auf die Frage des OP. Im Folgenden werde ich das geben, was ich für die richtige Antwort halte.
Richtige Antwort
Hier ist ein naiver Versuch, # 8 zum Laufen zu bringen, indem Sie einfach die -dOption angeben:
string='Paris, France, Europe';
readarray -td, a <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=$' Europe\n')
Wir sehen, dass das Ergebnis mit dem Ergebnis identisch ist, das wir aus dem readin # 7 diskutierten doppelt bedingten Ansatz der Schleifenlösung erhalten haben . Wir können dies fast mit dem manuellen Dummy-Terminator-Trick lösen:
readarray -td, a <<<"$string,"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=" Europe" [3]=$'\n')
Das Problem hierbei ist, dass readarraydas nachfolgende Feld beibehalten wurde, da der <<<Umleitungsoperator den LF an die Eingabezeichenfolge angehängt hat und das nachfolgende Feld daher nicht leer war (andernfalls wäre es gelöscht worden). Wir können uns darum kümmern, indem wir das endgültige Array-Element explizit nachträglich deaktivieren:
readarray -td, a <<<"$string,"; unset 'a[-1]'; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=" Europe")
Die einzigen zwei verbleibenden Probleme, die tatsächlich zusammenhängen, sind (1) das überflüssige Leerzeichen, das gekürzt werden muss, und (2) die mangelnde Unterstützung für Multicharakter-Begrenzer.
Das Leerzeichen kann natürlich später gekürzt werden (siehe z. B. Trimmen von Leerzeichen aus einer Bash-Variablen? ). Aber wenn wir ein Trennzeichen für mehrere Zeichen hacken können, würde dies beide Probleme auf einmal lösen.
Leider gibt es keinen direkten Weg, um ein Trennzeichen für mehrere Zeichen zum Laufen zu bringen. Die beste Lösung, an die ich gedacht habe, besteht darin, die Eingabezeichenfolge vorzuverarbeiten, um das Mehrzeichen-Trennzeichen durch ein Einzelzeichen-Trennzeichen zu ersetzen, das garantiert nicht mit dem Inhalt der Eingabezeichenfolge kollidiert. Das einzige Zeichen, das diese Garantie hat, ist das NUL-Byte . Dies liegt daran, dass Variablen in bash (übrigens nicht in zsh) das NUL-Byte nicht enthalten können. Dieser Vorverarbeitungsschritt kann inline in einer Prozesssubstitution durchgeführt werden. So geht's mit awk :
readarray -td '' a < <(awk '{ gsub(/, /,"\0"); print; }' <<<"$string, "); unset 'a[-1]';
declare -p a;
## declare -a a=([0]="Paris" [1]="France" [2]="Europe")
Endlich da! Diese Lösung teilt keine Felder fälschlicherweise in der Mitte auf, schneidet nicht vorzeitig aus, löscht keine leeren Felder, beschädigt sich nicht bei Dateinamenerweiterungen, entfernt nicht automatisch führende und nachfolgende Leerzeichen und hinterlässt am Ende keinen blinden LF. erfordert keine Schleifen und gibt sich nicht mit einem Einzelzeichen-Trennzeichen zufrieden.
Trimmlösung
Zuletzt wollte ich meine eigene ziemlich komplizierte Trimmlösung mit der obskuren -C callbackOption von demonstrieren readarray. Leider habe ich gegen Stack Overflows drakonisches Post-Limit von 30.000 Zeichen keinen Platz mehr, daher kann ich es nicht erklären. Ich werde das als Übung für den Leser belassen.
function mfcb { local val="$4"; "$1"; eval "$2[$3]=\$val;"; };
function val_ltrim { if [[ "$val" =~ ^[[:space:]]+ ]]; then val="${val:${#BASH_REMATCH[0]}}"; fi; };
function val_rtrim { if [[ "$val" =~ [[:space:]]+$ ]]; then val="${val:0:${#val}-${#BASH_REMATCH[0]}}"; fi; };
function val_trim { val_ltrim; val_rtrim; };
readarray -c1 -C 'mfcb val_trim a' -td, <<<"$string,"; unset 'a[-1]'; declare -p a;
## declare -a a=([0]="Paris" [1]="France" [2]="Europe")
,(Komma-Leerzeichen) und nicht nach einem einzelnen Zeichen wie Komma fragt . Wenn Sie nur an letzterem interessiert sind, sind die Antworten hier einfacher zu folgen: stackoverflow.com/questions/918886/…