In einem Bash-Skript möchte ich eine Zeile in Teile teilen und sie in einem Array speichern.
Die Linie:
Paris, France, Europe
Ich möchte sie in einem Array wie diesem haben:
array[0] = Paris
array[1] = France
array[2] = Europe
Ich möchte einfachen Code verwenden, die Geschwindigkeit des Befehls spielt keine Rolle. Wie kann ich es tun?
,
(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/…cut
ist dies ein nützlicher Bash-Befehl, den Sie ebenfalls berücksichtigen sollten. Separator ist definierbar en.wikibooks.org/wiki/Cut Sie können auch Daten aus einer Datensatzstruktur mit fester Breite extrahieren. en.wikipedia.org/wiki/Cut_(Unix) computerhope.com/unix/ucut.htmAntworten:
Beachten Sie, dass die Zeichen in
$IFS
einzeln als Trennzeichen behandelt werden, sodass in diesem Fall Felder entweder durch ein Komma oder ein Leerzeichen getrennt werden können und nicht durch die Reihenfolge der beiden Zeichen. Interessanterweise werden leere Felder nicht erstellt, wenn in der Eingabe ein Komma-Leerzeichen angezeigt wird, da das Leerzeichen speziell behandelt wird.So greifen Sie auf ein einzelnes Element zu:
So durchlaufen Sie die Elemente:
So erhalten Sie sowohl den Index als auch den Wert:
Das letzte Beispiel ist nützlich, da Bash-Arrays spärlich sind. Mit anderen Worten, Sie können ein Element löschen oder ein Element hinzufügen, und dann sind die Indizes nicht zusammenhängend.
So ermitteln Sie die Anzahl der Elemente in einem Array:
Wie oben erwähnt, können Arrays spärlich sein, daher sollten Sie die Länge nicht verwenden, um das letzte Element zu erhalten. So können Sie in Bash 4.2 und höher vorgehen:
in jeder Version von Bash (von irgendwo nach 2.05b):
Größere negative Offsets werden weiter vom Ende des Arrays entfernt ausgewählt. Beachten Sie das Leerzeichen vor dem Minuszeichen in der älteren Form. Es ist notwendig.
quelle
IFS=', '
Sie einfach , dann müssen Sie die Leerzeichen nicht separat entfernen. Test:IFS=', ' read -a array <<< "Paris, France, Europe"; echo "${array[@]}"
declare -p array
übrigens gerne für Testausgaben.France, Europe, "Congo, The Democratic Republic of the"
dies nach Kongo aufgeteilt.str="Paris, France, Europe, Los Angeles"; IFS=', ' read -r -a array <<< "$str"
wirdarray=([0]="Paris" [1]="France" [2]="Europe" [3]="Los" [4]="Angeles")
als Notiz aufgeteilt. Dies funktioniert also nur mit Feldern ohne Leerzeichen, daIFS=', '
es sich um eine Reihe einzelner Zeichen handelt - nicht um einen Zeichenfolgenbegrenzer.Alle Antworten auf diese Frage sind auf die eine oder andere Weise falsch.
Falsche Antwort # 1
1: Dies ist ein Missbrauch von
$IFS
. Der Wert der$IFS
Variablen wird nicht als einzelnes Zeichenfolgentrennzeichen mit variabler Länge verwendet, sondern als Satz von Zeichenfolgen-Trennzeichen mit einem Zeichen , wobei jedes Feld,read
das 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
$IFS
etwas mehr involviert. Aus dem Bash-Handbuch :Grundsätzlich
$IFS
kö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$IFS
zusammen 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'
?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
$string
Variable irgendwelche LFs enthalten passiert, dannread
werden Beenden Sie die Verarbeitung, sobald der erste LF gefunden wird. Dasread
eingebaute verarbeitet nur eine Zeile pro Aufruf. Dies gilt auch dann, wenn Sie Eingaben nur an dieread
Anweisung 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 dasread
eingebaute 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.
read
Dies 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 integriertenread
Systems nicht angemessen, und wir sollten dies vermeiden.3: Ein nicht offensichtliches potenzielles Problem bei dieser Lösung besteht darin, dass
read
das nachfolgende Feld immer gelöscht wird, wenn es leer ist, obwohl ansonsten leere Felder erhalten bleiben. Hier ist eine Demo: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
Ähnliche Idee:
(Hinweis: Ich habe die fehlenden Klammern um die Befehlsersetzung hinzugefügt, die der Antwortende anscheinend weggelassen hat.)
Ähnliche Idee:
Diese Lösungen nutzen die Wortaufteilung in einer Array-Zuweisung, um die Zeichenfolge in Felder aufzuteilen. Lustigerweise
read
verwendet die allgemeine Wortaufteilung ebenso wie die allgemeine$IFS
Variable 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$IFS
Zeichen 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, fallsextglob
gesetzt, 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 erset -f
zuvor 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 (tr
odersed
), da die Parametererweiterung eine rein shellinterne Operation ist. (Außerdem sollte für dietr
undsed
-Lösungen die Eingabevariable innerhalb der Befehlssubstitution in doppelte Anführungszeichen gesetzt werden. Andernfalls würde die Wortaufteilung imecho
Befehl 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
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
$IFS
wird 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 -f
und dann vorübergehend deaktiviert wirdset +f
.Auch hier gehen alle leeren Felder verloren, was je nach Anwendung ein Problem sein kann oder nicht.
Falsche Antwort # 4
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 -f
und eingeschlossen wirdset +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 -f
und einschließen.set +f
Dann 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, zIFS=$'\n';
.Falsche Antwort # 5
Ähnliche Idee:
Diese Lösung ist effektiv eine Kreuzung zwischen # 1 (indem sie
$IFS
auf 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
eval
Aufruf völlig unnötig zu sein, da sein Argument ein Zeichenfolgenliteral in einfachen Anführungszeichen ist und daher statisch bekannt ist. Die Verwendungeval
auf 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: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:
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:
Relevantes Zitat aus dem Bash-Handbuch :
Es ist möglich, diese Funktion der Variablenzuweisung zu nutzen, um Änderungen
$IFS
nur vorübergehend vorzunehmen, wodurch wir das gesamte Spiel zum Speichern und Wiederherstellen vermeiden können, wie es bei der$OIFS
Variablen 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$IFS
Zuweisung 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$IFS
Zuweisung vorübergehend zu machen ? Dies funktioniert nicht, da die$array
Zuweisung dann auch vorübergehend wäre :Wir befinden uns also effektiv in einer Sackgasse, ein bisschen wie ein Catch-22. Wenn
eval
der Code ausgeführt wird, wird er in der Shell-Umgebung ausgeführt, als wäre es normaler statischer Quellcode. Daher können wir die$array
Zuweisung innerhalb deseval
Arguments ausführen, damit sie in der Shell-Umgebung wirksam wird, während die$IFS
Präfixzuweisung dies tut wird demeval
Befehl vorangestellt, überlebt deneval
Befehl nicht. Dies ist genau der Trick, der in der zweiten Variante dieser Lösung verwendet wird: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
Ä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
$IFS
Variable die gesamte Bash-Analyse in allen Kontexten beeinflusst, was nicht der Fall ist. Aus dem Bash-Handbuch:Die
$IFS
spezielle 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 dasread
eingebaute.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
$IFS
Variablen, 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: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:
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 verschiedenencompatxx
Shell-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
Dies ist eine der besten Lösungen. Beachten Sie, dass wir wieder verwenden
read
. Habe ich nicht früher gesagt, dass diesread
unangemessen ist, weil es zwei Aufteilungsebenen durchführt, wenn wir nur eine brauchen? Der Trick dabei ist, dass Sie so aufrufen könnenread
, 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 angeben
read
, werden führende und nachfolgende Leerzeichen in jedem Feld, das von der Eingabezeichenfolge getrennt ist, automatisch ignoriert. Dies tritt auf, unabhängig davon, ob$IFS
der 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 vonread
besteht darin, null NAME- Argumente zu übergeben. In diesem Fallread
wird 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,read
die ich in meiner Karriere als Shell-Programmierer häufig ausgenutzt habe. Hier ist eine Demonstration des Unterschieds im Verhalten: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
-d
Option angeben, aber schauen Sie, was passiert: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
read
ein 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
-d
Option 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:Dort ist das Problem gelöst. Eine andere Lösung besteht darin, die while-Schleife nur zu unterbrechen, wenn sowohl (1) einen
read
Fehler zurückgegeben hat als auch (2)$REPLY
leer ist, was bedeutetread
, dass vor dem Erreichen des Dateiende keine Zeichen gelesen werden konnten. Demo: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
(Dies ist tatsächlich aus demselben Beitrag wie # 7 ; der Antwortende hat zwei Lösungen in demselben Beitrag bereitgestellt.)
Das
readarray
eingebaute Synonym fürmapfile
ist 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-O
nicht 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
read
beim Parsen von Feldernreadarray
das 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
-d
Option angeben:Wir sehen, dass das Ergebnis mit dem Ergebnis identisch ist, das wir aus dem
read
in # 7 diskutierten doppelt bedingten Ansatz der Schleifenlösung erhalten haben . Wir können dies fast mit dem manuellen Dummy-Terminator-Trick lösen:Das Problem hierbei ist, dass
readarray
das 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: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 :
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 callback
Option von demonstrierenreadarray
. 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.quelle
-d
Option zumreadarray
ersten Mal in Bash 4.4 angezeigt wird.awk '{ gsub(/,[ ]+|$/,"\0"); print }'
und diese Verkettung des Finales beseitigen, müssen", "
Sie nicht die Gymnastik durchlaufen, um den endgültigen Rekord zu eliminieren. Also:readarray -td '' a < <(awk '{ gsub(/,[ ]+/,"\0"); print; }' <<<"$string")
auf Bash das unterstütztreadarray
. Hinweis Ihre Methode ist Bash 4.4+ Ich denke , wegen der-d
inreadarray
readarray
. In diesem Fall können Sie die zweitbeste Lösung verwenden, auf der aufgebaut istread
. Ich beziehe mich darauf:a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string,";
(mit derawk
Substitution, wenn Sie Unterstützung für Trennzeichen mit mehreren Zeichen benötigen). Lassen Sie mich wissen, wenn Sie auf Probleme stoßen. Ich bin mir ziemlich sicher, dass diese Lösung auf ziemlich alten Versionen von Bash funktionieren sollte, zurück zu Version 2 - etwas, das wie vor zwei Jahrzehnten veröffentlicht wurde.Hier ist ein Weg ohne IFS einzustellen:
Die Idee ist das Ersetzen von Zeichenfolgen:
So ersetzen Sie alle Übereinstimmungen von $ substring durch Leerzeichen und verwenden dann die ersetzte Zeichenfolge, um ein Array zu initialisieren:
Hinweis: Diese Antwort verwendet den Operator split + glob . Um die Erweiterung einiger Zeichen (z. B.
*
) zu verhindern , empfiehlt es sich, das Globbing für dieses Skript anzuhalten.quelle
${string//:/ }
verhindert Shell-Erweiterungarray=(${string//:/ })
Druckt drei
quelle
a=($(echo $t | tr ',' "\n"))
. Gleiches Ergebnis mita=($(echo $t | tr ',' ' '))
.VERSION="16.04.2 LTS (Xenial Xerus)"
in einerbash
Shell ausprobiert und der letzteecho
druckt nur eine leere Zeile. Welche Linux-Version und welche Shell verwenden Sie? Terminalsitzung kann leider nicht in einem Kommentar angezeigt werden.Manchmal ist mir passiert, dass die in der akzeptierten Antwort beschriebene Methode nicht funktioniert hat, insbesondere wenn es sich bei dem Trennzeichen um einen Wagenrücklauf handelt.
In diesen Fällen habe ich folgendermaßen gelöst:
quelle
read -a arr <<< "$strings"
funktionierte nicht mitIFS=$'\n'
.Die akzeptierte Antwort funktioniert für Werte in einer Zeile.
Wenn die Variable mehrere Zeilen hat:
Wir brauchen einen ganz anderen Befehl, um alle Zeilen zu erhalten:
while read -r line; do lines+=("$line"); done <<<"$string"
Oder das viel einfachere Bash- Readarray :
Das Drucken aller Zeilen ist sehr einfach und nutzt die Funktion printf:
quelle
Dies ähnelt dem Ansatz von Jmoney38 , verwendet jedoch sed:
Druckt 1
quelle
Der Schlüssel zum Aufteilen Ihrer Zeichenfolge in ein Array ist das Trennzeichen für mehrere Zeichen von
", "
. Jede Lösung, dieIFS
für Trennzeichen mit mehreren Zeichen verwendet wird, ist von Natur aus falsch, da IFS eine Menge dieser Zeichen und keine Zeichenfolge ist.Wenn Sie zuweisen,
IFS=", "
wird die Zeichenfolge entweder bei","
ODER" "
oder einer beliebigen Kombination davon unterbrochen , was keine genaue Darstellung des Zwei-Zeichen-Trennzeichens von ist", "
.Sie können die Zeichenfolge mit
awk
odersed
durch Teilung des Prozesses verwenden oder teilen:Es ist effizienter, einen regulären Ausdruck direkt in Bash zu verwenden:
Bei der zweiten Form gibt es keine Unterschale und diese ist von Natur aus schneller.
Bearbeiten von bgoldst: Hier sind einige Benchmarks, die meine
readarray
Lösung mit der Regex-Lösung von dawg vergleichen , und ich habe auch dieread
Lösung zum Teufel aufgenommen (Hinweis: Ich habe die Regex-Lösung leicht modifiziert, um eine bessere Harmonie mit meiner Lösung zu erzielen ) (siehe auch meine Kommentare unter Post):quelle
$BASH_REMATCH
. Es funktioniert und vermeidet in der Tat das Laichen von Unterschalen. +1 von mir. Aus Kritikgründen ist der reguläre Ausdruck selbst jedoch nicht ideal, da Sie anscheinend gezwungen waren, einen Teil des Trennzeichens (insbesondere das Komma) zu duplizieren, um die mangelnde Unterstützung für nicht gierige Multiplikatoren zu umgehen (auch Lookarounds) in ERE ("erweitertes" Regex-Aroma in Bash eingebaut). Dies macht es etwas weniger generisch und robust.\n
begrenzte Textzeilen) geben, die diese Felder enthält, so dass die katastrophale Verlangsamung wahrscheinlich nicht auftreten würde. Wenn Sie eine Zeichenfolge mit 100.000 Feldern haben - vielleicht ist Bash nicht ideal ;-) Danke für den Benchmark. Ich habe ein oder zwei Dinge gelernt.Reine Bash-Lösung mit mehreren Zeichen.
Wie andere in diesem Thread ausgeführt haben, gab die Frage des OP ein Beispiel für eine durch Kommas getrennte Zeichenfolge, die in ein Array analysiert werden soll, gab jedoch nicht an, ob er / sie nur an Komma-Trennzeichen, Einzelzeichen-Trennzeichen oder Mehrzeichen interessiert war Trennzeichen.
Da Google diese Antwort in der Regel an oder nahe der Spitze der Suchergebnisse platziert, wollte ich den Lesern eine eindeutige Antwort auf die Frage nach Trennzeichen für mehrere Zeichen geben, da dies auch in mindestens einer Antwort erwähnt wird.
Wenn Sie auf der Suche nach einer Lösung für ein Problem mit Trennzeichen für mehrere Zeichen sind, empfehle ich, den Beitrag von Mallikarjun M zu lesen , insbesondere die Antwort von gniourf_gniourf , der diese elegante reine BASH-Lösung mithilfe der Parametererweiterung bereitstellt:
Link zum zitierten Kommentar / referenzierten Beitrag
Link zur zitierten Frage: Wie teilt man eine Zeichenfolge in einem mehrstelligen Trennzeichen in Bash?
quelle
Dies funktioniert für mich unter OSX:
Wenn Ihre Zeichenfolge ein anderes Trennzeichen hat, ersetzen Sie diese zunächst durch Leerzeichen:
Einfach :-)
quelle
Eine andere Möglichkeit, dies zu tun, ohne IFS zu ändern:
Anstatt IFS so zu ändern, dass es mit unserem gewünschten Trennzeichen übereinstimmt, können wir alle Vorkommen unseres gewünschten Trennzeichens
", "
durch Inhalte von$IFS
via ersetzen"${string//, /$IFS}"
.Vielleicht ist dies für sehr große Saiten langsam?
Dies basiert auf Dennis Williamsons Antwort.
quelle
Ich bin auf diesen Beitrag gestoßen, als ich versucht habe, eine Eingabe wie die folgenden zu analysieren: word1, word2, ...
Keiner der oben genannten hat mir geholfen. löste es mit awk. Wenn es jemandem hilft:
quelle
Versuche dies
Es ist einfach. Wenn Sie möchten, können Sie auch eine Deklaration hinzufügen (und auch die Kommas entfernen):
Das IFS wird hinzugefügt, um das oben Gesagte rückgängig zu machen, funktioniert jedoch ohne es in einer neuen Bash-Instanz
quelle
Wir können den Befehl tr verwenden, um einen String in das Array-Objekt aufzuteilen. Es funktioniert sowohl unter MacOS als auch unter Linux
Eine andere Option ist der IFS-Befehl
quelle
Benutze das:
quelle
array=( $string )
, das Aufteilen eines Strings in ein Array, wie es ein (leider sehr häufiges) Antimuster ist: Das Aufteilen von Wörtern erfolgt :string='Prague, Czech Republic, Europe'
; Die Erweiterung des Pfadnamens tritt auf: schlägtstring='foo[abcd],bar[efgh]'
fehl, wenn Sie eine Datei mit dem Namen z. B.food
oderbarf
in Ihrem Verzeichnis haben. Die einzig gültige Verwendung eines solchen Konstrukts ist, wennstring
es sich um einen Glob handelt.UPDATE: Tun Sie dies nicht, da es Probleme mit der Bewertung gibt.
Mit etwas weniger Zeremonie:
z.B
quelle
$
in Ihre Variableeval
Hier ist mein Hack!
Das Teilen von Strings durch Strings ist eine ziemlich langweilige Sache mit Bash. Was passiert ist, dass wir begrenzte Ansätze haben, die nur in wenigen Fällen funktionieren (geteilt durch ";", "/", "." Usw.) oder wir haben eine Vielzahl von Nebenwirkungen in den Ausgaben.
Der folgende Ansatz hat eine Reihe von Manövern erfordert, aber ich glaube, dass er für die meisten unserer Bedürfnisse funktionieren wird!
quelle
Warum nicht so etwas für mehrzeilige Elemente?
quelle
Ein anderer Weg wäre:
Jetzt werden Ihre Elemente im Array "arr" gespeichert. So durchlaufen Sie die Elemente:
quelle
eval
Trick interessiert sein ). Ihre Lösung wird nachträglich$IFS
auf den Komma-Leerzeichen-Wert gesetzt.Da es so viele Möglichkeiten gibt, dies zu lösen, definieren wir zunächst, was wir in unserer Lösung sehen möchten.
readarray
zu diesem Zweck eine integrierte Funktion. Lass es uns benutzen.IFS
, Schleifen, Verwendeneval
oder Hinzufügen eines zusätzlichen Elements und das anschließende Entfernen.Der
readarray
Befehl ist am einfachsten mit Zeilenumbrüchen als Trennzeichen zu verwenden. Bei anderen Trennzeichen kann dem Array ein zusätzliches Element hinzugefügt werden. Der sauberste Ansatz besteht darin, unsere Eingaben zunächst in ein Formular zu integrieren, mitreadarray
dem sie gut funktionieren, bevor sie weitergegeben werden.Die Eingabe in diesem Beispiel hat kein Trennzeichen für mehrere Zeichen. Wenn wir ein wenig gesunden Menschenverstand anwenden, wird dies am besten als durch Kommas getrennte Eingabe verstanden, für die möglicherweise jedes Element zugeschnitten werden muss. Meine Lösung besteht darin, die Eingabe durch Komma in mehrere Zeilen aufzuteilen, jedes Element zu kürzen und alles an zu übergeben
readarray
.quelle
Ein anderer Ansatz kann sein:
Danach ist 'arr' ein Array mit vier Strings. Dies erfordert keinen Umgang mit IFS oder Lesen oder anderen speziellen Dingen, daher viel einfacher und direkter.
quelle