Die einfache Antwort lautet: Reduzieren Sie alle Trennzeichen auf eins (das erste).
Das erfordert eine Schleife (die weniger als log(N)
mal läuft ):
var=':a bc::d ef:#$%_+$$% ^%&*(*&*^
$#,.::ghi::*::' # a long test string.
d=':@!#$%^&*()_+,.' # delimiter set
f=${d:0:1} # first delimiter
v=${var//["$d"]/"$f"}; # convert all delimiters to
: # the first of the delimiter set.
tmp=$v # temporal variable (v).
while
tmp=${tmp//["$f"]["$f"]/"$f"}; # collapse each two delimiters to one
[[ "$tmp" != "$v" ]]; # If there was a change
do
v=$tmp; # actualize the value of the string.
done
Sie müssen nur noch die Zeichenfolge in einem Trennzeichen korrekt aufteilen und ausdrucken:
readarray -td "$f" arr < <(printf '%s%s' "$v"'' "$f")
printf '<%s>' "${arr[@]}" ; echo
set -f
IFS muss nicht geändert werden.
Getestet mit Leerzeichen, Zeilenumbrüchen und Glob-Zeichen. Alle Arbeit. Ziemlich langsam (wie eine Shell-Schleife zu erwarten ist).
Aber nur für Bash (Bash 4.4+ wegen der Option -d
zum Readarray).
Sch
Eine Shell-Version kann kein Array verwenden. Das einzige verfügbare Array sind die Positionsparameter.
Die Verwendung tr -s
ist nur eine Zeile (IFS ändert sich im Skript nicht):
set -f; IFS=$f command eval set -- '$(echo "$var" | tr -s "$d" "[$f*]" )""'
Und drucken Sie es aus:
printf '<%s>' "$@" ; echo
Immer noch langsam, aber nicht viel mehr.
Der Befehl command
ist in Bourne ungültig.
Ruft in zsh command
nur externe Befehle auf und lässt eval fehlschlagen, wenn command
es verwendet wird.
In ksh wird auch mit command
der Wert von IFS im globalen Bereich geändert.
Und command
die Aufteilung schlägt in mksh-bezogenen Shells (mksh, lksh, posh) fehl. Durch Entfernen des Befehls command
wird der Code auf mehreren Shells ausgeführt. Aber: Durch das Entfernen command
behält IFS seinen Wert in den meisten Shells (eval ist ein spezielles integriertes Element), außer in bash (ohne Posix-Modus) und zsh im Standardmodus (keine Emulation). Dieses Konzept kann weder mit noch ohne Standard-zsh verwendet werden command
.
IFS mit mehreren Zeichen
Ja, IFS kann aus mehreren Zeichen bestehen, aber jedes Zeichen generiert ein Argument:
set -f; IFS="$d" command eval set -- '$(echo "$var" )""'
printf '<%s>' "$@" ; echo
Wird ausgegeben:
<><a bc><><d ef><><><><><><><><>< ><><><><><><><><><
><><><><><><ghi><><><><><>
Mit bash können Sie das command
Wort weglassen, wenn Sie nicht in der sh / POSIX-Emulation sind. Der Befehl schlägt in ksh93 fehl (IFS behält den geänderten Wert bei). In zsh lässt der Befehl command
zsh versuchen, eval
einen externen Befehl zu finden (den er nicht findet), und schlägt fehl.
Was passiert ist, dass die einzigen IFS-Zeichen, die automatisch auf ein Trennzeichen reduziert werden, IFS-Leerzeichen sind.
Ein Leerzeichen in IFS reduziert alle aufeinander folgenden Leerzeichen zu einem. Eine Registerkarte reduziert alle Registerkarten. Ein Leerzeichen und eine Registerkarte reduzieren die Anzahl der Leerzeichen und / oder Registerkarten auf ein Trennzeichen. Wiederholen Sie die Idee mit Newline.
Um mehrere Trennzeichen zu kollabieren, ist ein wenig Jonglieren erforderlich.
Angenommen, ASCII 3 (0x03) wird in der Eingabe nicht verwendet var
:
var=${var// /$'\3'} # protect spaces
var=${var//["$d"]/ } # convert all delimiters to spaces
set -f; # avoid expanding globs.
IFS=" " command eval set -- '""$var""' # split on spaces.
set -- "${@//$'\3'/ }" # convert spaces back.
Die meisten Kommentare zu ksh, zsh und bash (about command
und IFS) gelten hier noch.
Ein Wert von $'\0'
wäre bei der Texteingabe weniger wahrscheinlich, aber Bash-Variablen können keine NULs ( 0x00
) enthalten.
In sh gibt es keine internen Befehle, um dieselben Zeichenfolgenoperationen auszuführen. Daher ist tr die einzige Lösung für sh-Skripte.