Gibt es so etwas wie "split ()" von JavaScript in der Shell?

18

split()In JavaScript ist es sehr einfach, einen String in ein Array zu unterteilen.

Was ist mit Shell-Skript?

Angenommen, ich möchte Folgendes tun:

$ script.sh var1_var2_var3

Wenn der Benutzer var1_var2_var3der script.sh einen solchen String gibt, konvertiert er den String innerhalb des Skripts in ein Array wie

array=( var1 var2 var3 )
for name in ${array[@]}; do
    # some code
done
AGamePlayer
quelle
1
Was verwenden shellSie, mit dem bashSie tun könnenIFS='_' read -a array <<< "${string}"
gwillie
perlkann das auch. Es ist keine "reine" Hülle, aber es ist ziemlich verbreitet.
Sobrique
@Sobrique Mir ist auch die technische Definition der "reinen" Shell nicht bekannt, aber es gibt node.js.
Emory
Ich neige dazu, daran zu arbeiten, "
ob

Antworten:

24

Bourne / POSIX-ähnliche Shells haben einen split + glob-Operator und werden jedes Mal aufgerufen, wenn Sie eine Parametererweiterung ( $var, $-...), eine Befehlssubstitution ( $(...)) oder eine arithmetische Erweiterung ( $((...))) im Listenkontext nicht in Anführungszeichen setzen.

Eigentlich aufgerufen Sie es aus Versehen , wenn Sie tat for name in ${array[@]}statt for name in "${array[@]}". (Beachten Sie, dass das versehentliche Aufrufen dieses Operators viele Fehler und Sicherheitslücken verursacht. )

Dieser Operator wird mit dem $IFSspeziellen Parameter (um anzugeben, auf welche Zeichen aufgeteilt werden soll (wobei darauf zu achten ist, dass Leerzeichen, Tabulator und Zeilenvorschub dort eine besondere Behandlung erfahren)) und der -fOption zum Deaktivieren ( set -f) oder Aktivieren ( set +f) des globTeils konfiguriert .

Beachten Sie auch, dass das Sin $IFSursprünglich (in der Bourne-Shell, von der es $IFSstammt) für Separator war, in POSIX-Shells die Zeichen in $IFSeher als Begrenzer oder Abschlusszeichen zu sehen sind (siehe unten für ein Beispiel).

Also aufteilen _:

string='var1_var2_var3'
IFS=_ # delimit on _
set -f # disable the glob part
array=($string) # invoke the split+glob operator

for i in "${array[@]}"; do # loop over the array elements.

Um die Unterscheidung zwischen Trennzeichen und Trennzeichen zu sehen , probieren Sie Folgendes aus:

string='var1_var2_'

Das wird spaltete es in var1und var2nur (kein zusätzliches leeres Element).

Um es ähnlich wie JavaScript zu machen split(), benötigen Sie einen zusätzlichen Schritt:

string='var1_var2_var3'
IFS=_ # delimit on _
set -f # disable the glob part
temp=${string}_ # add an extra delimiter
array=($temp) # invoke the split+glob operator

(Beachten Sie, dass ein leeres Element wie bei JavaScript $stringin 1 (nicht in 0split() ) aufgeteilt wird.)

Um zu sehen, welche Sonderbehandlungen Tab, Space und Newline erhalten, vergleichen Sie:

IFS=' '; string=' var1  var2  '

(wo bekommt man var1und var2) mit

IFS='_'; string='_var1__var2__'

wo Sie bekommen: '', var1, '', var2, ''.

Beachten Sie, dass die zshShell diesen split + glob-Operator nur implizit aufruft, wenn er in shoder kshemuliert ist. Dort müssen Sie es explizit aufrufen. $=stringfür den aufgeteilten Teil, $~stringfür den Glob-Teil ( $=~stringfür beide), und es hat auch einen aufgeteilten Operator, in dem Sie das Trennzeichen angeben können:

array=(${(s:_:)string})

oder um die leeren Elemente zu erhalten:

array=("${(@s:_:)string}")

Beachten Sie, dass es szum Teilen , nicht zum Abgrenzen (auch bei $IFSeiner bekannten POSIX-Abweichung von zsh) gibt. Es unterscheidet sich von JavaScript split()darin, dass eine leere Zeichenfolge in ein 0-Element (nicht in ein 1-Element) aufgeteilt wird.

Ein bemerkenswerter Unterschied zu $IFS-splitting besteht darin, dass ${(s:abc:)string}sich die abcZeichenfolge aufteilt , während sich mit IFS=abc, das aufteilen würde a, boder c.

Mit zshund ksh93kann die Sonderbehandlung, die Leerzeichen, Tabulatoren oder Zeilenumbrüche erhalten, durch Verdoppeln entfernt werden $IFS.

Als historische Notiz hat die Bourne-Shell (die Vorgänger- oder die moderne POSIX-Shell) immer die leeren Elemente entfernt. Es hatte auch eine Reihe von Fehlern im Zusammenhang mit der Aufteilung und Erweiterung von $ @ mit Nicht-Standardwerten von $IFS. Zum Beispiel IFS=_; set -f; set -- $@wäre nicht gleichbedeutend mit IFS=_; set -f; set -- $1 $2 $3....

Aufteilen auf reguläre Ausdrücke

Wenn Sie sich etwas näher mit JavaScript befassen möchten split(), das sich in reguläre Ausdrücke aufteilen lässt, müssen Sie sich auf externe Dienstprogramme verlassen.

Hat im POSIX-Werkzeugkasten awkeinen splitOperator, der auf erweiterte reguläre Ausdrücke aufgeteilt werden kann (dies ist mehr oder weniger eine Teilmenge der von JavaScript unterstützten Perl-ähnlichen regulären Ausdrücke).

split() {
  awk -v q="'" '
    function quote(s) {
      gsub(q, q "\\" q q, s)
      return q s q
    }
    BEGIN {
      n = split(ARGV[1], a, ARGV[2])
      for (i = 1; i <= n; i++) printf " %s", quote(a[i])
      exit
    }' "$@"
}
string=a__b_+c
eval "array=($(split "$string" '[_+]+'))"

Die zshShell verfügt über eine integrierte Unterstützung für Perl-kompatible reguläre Ausdrücke (in ihrem zsh/pcreModul). Die Verwendung dieser Funktion zum Teilen einer Zeichenfolge ist jedoch relativ umständlich.

Stéphane Chazelas
quelle
Gibt es einen Grund für spezielle Behandlungen mit Tab, Space und Newline?
Cuonglm
1
@cuonglm, in der Regel wollen Sie zur Trennung von Worten , wenn die Begrenzungszeichen Rohlinge sind, im Fall von nicht-leeren Trennzeichen (wie Split $PATHauf :) , im Gegenteil, mögen Sie in der Regel leere Elemente bewahren. Beachten Sie, dass in der Bourne-Shell alle Zeichen die Sonderbehandlung erhielten kshund dass nur die leeren Zeichen (nur Leerzeichen, Tabulator und Zeilenvorschub) speziell behandelt wurden.
Stéphane Chazelas
Nun, die kürzlich hinzugefügte Bourne-Shell-Note hat mich überrascht. Und wenn Sie fertig sind, sollten Sie den Hinweis für die zshBehandlung mit einer Zeichenfolge hinzufügen, die 2 oder mehr Zeichen enthält ${(s:string:)var}. Wenn hinzugefügt, kann ich meine Antwort löschen :)
Cuonglm
1
Was meinen Sie mit "Beachten Sie auch, dass das S in $ IFS für Trennzeichen und nicht für Trennzeichen steht."? Ich verstehe die Mechanik und dass sie nachfolgende Trennzeichen ignoriert, aber das Ssteht für Separator , nicht für Delimiter . Zumindest steht das in meinem Bash-Handbuch.
terdon
@terdon $IFSstammt aus der Bourne-Shell, in der es sich um ein Trennzeichen handelte . ksh änderte das Verhalten, ohne den Namen zu ändern. Ich erwähne das, um zu betonen, dass split+glob(außer in zsh oder pdksh) nicht mehr einfach geteilt wird.
Stéphane Chazelas
7

Ja, benutze IFSund setze es auf _. Verwenden Sie dann read -a, um in einem Array zu speichern ( -rdeaktiviert die Backslash-Erweiterung). Beachten Sie, dass dies spezifisch für bash ist. ksh und zsh haben ähnliche Funktionen mit leicht unterschiedlicher Syntax, und plain sh hat überhaupt keine Array-Variablen.

$ r="var1_var2_var3"
$ IFS='_' read -r -a array <<< "$r"
$ for name in "${array[@]}"; do echo "+ $name"; done
+ var1
+ var2
+ var3

Von man bash:

lesen

-a aname

Die Wörter werden sequentiellen Indizes der Array-Variablen aname zugewiesen, beginnend mit 0. aname wird deaktiviert, bevor neue Werte zugewiesen werden. Andere Namensargumente werden ignoriert.

IFS

Das interne Feldtrennzeichen, das 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 `` ''.

Beachten Sie, dass dies readbei der ersten Zeile endet. Pass -d ''auf , readum das zu vermeiden, aber in diesem Fall wird es ein extra Newline am Ende aufgrund des <<<Betreibers. Sie können es manuell entfernen:

IFS='_' read -r -d '' -a array <<< "$r"
array[$((${#array[@]}-1))]=${array[$((${#array[@]}-1))]%?}
fedorqui
quelle
$rDies setzt voraus, dass keine Zeilenumbrüche oder Backslashes enthalten sind. Beachten Sie auch, dass dies nur in neueren Versionen der bashShell funktioniert .
Stéphane Chazelas
@ StéphaneChazelas guter Punkt. Ja, dies ist der "grundlegende" Fall einer Zeichenfolge. Im Übrigen sollte sich jeder um Ihre umfassende Antwort bemühen. In Bezug auf die Versionen von bash, read -awurde in Bash 4 eingeführt, oder?
Fedorqui
1
Es tut mir leid, ich dachte, es <<<wurde erst kürzlich hinzugefügt, bashaber es scheint, dass es seit 2.05b (2002) da ist. read -aist noch älter als das. <<<kommt von zshund wird auch von ksh93(und mksh und yash) unterstützt, read -aist aber bash-spezifisch (es ist -Ain ksh93, yash und zsh).
Stéphane Chazelas
@ StéphaneChazelas gibt es einen "einfachen" Weg, um herauszufinden, wann diese Änderungen stattgefunden haben? Ich sage "einfach", um nicht in die Release-Dateien zu graben, vielleicht eine Seite, die sie alle zeigt.
Fedorqui
1
Dafür schaue ich mir Änderungsprotokolle an. zsh hat auch ein Git-Repository mit einer Historie von 3.1.5 und seine Mailingliste wird auch zum Verfolgen von Änderungen verwendet.
Stéphane Chazelas