Durch das Parsen eines Arrays mithilfe von IFS mit nicht weißen Leerzeichenwerten werden leere Elemente erstellt.
Selbst das tr -s
Verkleinern mehrerer Delims auf ein einziges Delim reicht nicht aus.
Ein Beispiel kann das Problem klarer erklären.
Gibt es eine Möglichkeit, "normale" Ergebnisse durch eine Optimierung von IFS zu erzielen (gibt es eine zugehörige Einstellung, um das Verhalten von IFS zu ändern? .... dh das gleiche Verhalten wie das Standard-Leerzeichen) IFS.
var=" abc def ghi "
echo "============== IFS=<default>"
arr=($var)
for x in ${!arr[*]} ; do
echo "# arr[$x] \"${arr[x]}\""
done
#
sfi="$IFS" ; IFS=':'
set -f # Disable file name generation (globbing)
# (This data won't "glob", but unless globbing
# is actually needed, turn if off, because
# unusual/unexpected combinations of data can glob!
# and they can do it in the most obscure ways...
# With IFS, "you're not in Kansas any more! :)
var=":abc::def:::ghi::::"
echo "============== IFS=$IFS"
arr=($var)
for x in ${!arr[*]} ; do
echo "# arr[$x] \"${arr[x]}\""
done
echo "============== IFS=$IFS and tr"
arr=($(echo -n "$var"|tr -s "$IFS"))
for x in ${!arr[*]} ; do
echo "# arr[$x] \"${arr[x]}\""
done
set +f # enable globbing
IFS="$sfi" # re-instate original IFS val
echo "============== IFS=<default>"
Hier ist die Ausgabe
============== IFS=<default>
# arr[0] "abc"
# arr[1] "def"
# arr[2] "ghi"
============== IFS=:
# arr[0] ""
# arr[1] "abc"
# arr[2] ""
# arr[3] "def"
# arr[4] ""
# arr[5] ""
# arr[6] "ghi"
# arr[7] ""
# arr[8] ""
# arr[9] ""
============== IFS=: and tr
# arr[0] ""
# arr[1] "abc"
# arr[2] "def"
# arr[3] "ghi"
============== IFS=<default>
Antworten:
Um mehrere (nicht durch Leerzeichen) aufeinanderfolgende Trennzeichen zu entfernen, können zwei (Zeichenfolge / Array) Parametererweiterungen verwendet werden. Der Trick besteht darin, die
IFS
Variable für die Array-Parametererweiterung auf die leere Zeichenfolge zu setzen .Dies ist
man bash
unter Wortaufteilung dokumentiert :quelle
IFS=' '
verhält sich die Einstellung (dh ein Leerzeichen) genauso. Ich finde das weniger verwirrend als ein explizites Nullargument ("" oder '') vonIFS
.Von der
bash
Manpage:Dies bedeutet, dass IFS-Leerzeichen (Leerzeichen, Tabulatoren und Zeilenumbrüche) nicht wie die anderen Trennzeichen behandelt werden. Wenn Sie mit einem alternativen Trennzeichen genau das gleiche Verhalten erzielen möchten, können Sie mit Hilfe von
tr
odersed
: Trennzeichen austauschen.Das
%#%#%#%#%
Ding ist ein magischer Wert, um die möglichen Leerzeichen innerhalb der Felder zu ersetzen. Es wird erwartet, dass es "einzigartig" (oder sehr unzusammenhängend) ist. Wenn Sie sicher sind, dass auf den Feldern niemals Platz sein wird, lassen Sie diesen Teil einfach fallen.quelle
tr
Beispiele beabsichtigt , um das Problem zu zeigen ... Ich möchte einen Systemaufruf vermeiden, also werde ich mir eine Bash-Option ansehen, die über die${var##:}
in meinem Kommentar zu Glen's Ansewer erwähnte hinausgeht. Ich werde eineIFS
ist in allen Bourne-Shells gleich, sie ist in POSIX angegeben .IFS
Zeichen als Trennzeichenfolge zu erstellen . Meine Frage wurde am besten von beantwortetjon_d
, aber die Antwort von @ nazad zeigt eine raffinierte Möglichkeit, sieIFS
ohne Schleifen und ohne Dienstprogramm-Apps zu verwenden.Da bash IFS keine interne Möglichkeit bietet, aufeinanderfolgende Trennzeichen als ein einziges Trennzeichen zu behandeln (für Nicht-Leerzeichen-Trennzeichen), habe ich eine All-Bash-Version zusammengestellt (im Gegensatz zur Verwendung eines externen Aufrufs, z. B. tr, awk, sed )
Es kann IFS mit mehreren Zeichen verarbeiten.
Hier sind die Ergebnisse der Ausführungszeit zusammen mit ähnlichen Tests für die
tr
undawk
die auf dieser Q / A-Seite gezeigten Optionen ... Die Tests basieren auf 10000 Iterationen, bei denen nur das Arrray erstellt wurde (ohne E / A) ...Hier ist die Ausgabe
Hier ist das Skript
quelle
Du kannst es auch mit Gawk machen, aber es ist nicht schön:
Ausgänge
quelle
$var
in${var##:}
... Ich war wirklich auf der Suche nach einer Möglichkeit, IFS selbst zu optimieren. Ich möchte Um dies ohne einen externen Anruf zu tun (ich habe das Gefühl, dass Bash dies effizienter kann als jeder externe), also werde ich auf diesem Weg bleiben. Ihre Methode funktioniert (+1). Soweit Wenn ich die Eingabe ändere, würde ich es lieber mit Bash versuchen als mit awk oder tr (es würde einen Systemaufruf vermeiden), aber ich bin wirklich auf eine IFS-Optimierung aus ...bash 1.276s
...call (awk) 0m32.210s
,,,call (tr) 0m32.178s
... Tun Sie das ein paar Mal und Sie könnten denken, Bash ist langsam! ... ist awk in diesem Fall einfacher? ... nicht wenn du das Snippet schon hast :) ... ich werde es später posten; muss jetzt gehen.var="The \"X\" factor:::A single '\"' crashes:::\"One Two\""
Die einfache Antwort lautet: Reduzieren Sie alle Trennzeichen auf eins (das erste).
Das erfordert eine Schleife (die weniger als
log(N)
mal läuft ):Sie müssen nur noch die Zeichenfolge in einem Trennzeichen korrekt aufteilen und ausdrucken:
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):Und drucken Sie es aus:
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, wenncommand
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 Befehlscommand
wird der Code auf mehreren Shells ausgeführt. Aber: Durch das Entfernencommand
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 werdencommand
.IFS mit mehreren Zeichen
Ja, IFS kann aus mehreren Zeichen bestehen, aber jedes Zeichen generiert ein Argument:
Wird ausgegeben:
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 Befehlcommand
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
: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.
quelle
command eval
IIRC von Gilles