Editar: Es tut mir leid, ich hatte irgendwo auf SO gelesen, dass perl
wird von POSIX gefordert, also dachte ich, es wäre legitim, es zu benutzen. Aber auf unix.stackexchange.com erklären mehrere Benutzer perl
ist NICHT Teil der POSIX-Spezifikation.
Meine Lösung: Eine Funktion, die mit perl
's split
um die Arbeit zu erledigen.
Mit ausführlichen Kommentaren:
#!/bin/bash
# This function is a wrapper for Perl's split.\
# \
# Since we cannot return an array like in Perl,
# it takes the name of the resulting array as last
# argument.\
# \
# See https://perldoc.perl.org/functions/split for usage info
# and examples.\
# \
# If you provide a Perl regexp that contains e. g. an escaped token like \b,
# space(s) and/or capture group(s), it must be quoted, and e. g. /\b/ must
# be single-quoted.\
# Thus, it's best to generally single-quote a Perl regexp.
function split # Args: <Element separator regexp> <string> <array name>
{
(($# != 3)) && echo "${FUNCNAME[0]}: Wrong number of arguments, returning." && return 1
local elementSepRE=$1
local string=$2
local -n array=$3
local element i=0
# Attention! read does Word Splitting on each line!
# I must admit I didn't know that so far.
# This removes leading and trailing spaces, exactly
# what we don't want.
# Thus, we set IFS locally to newline only.
local IFS=$'\n'
while read element; do
# As opposed to array+=($element),
# this preserves leading and trailing spaces.
array[i++]=$element
done <<<$(_perl_split)
}
# This function calls Perl's split function and prints the elements of the
# resulting array on separate lines.\
# It uses the caller's $elementSepRE and $string.
function _perl_split
{
# A heredoc is a great way of embedding a Perl script.
# N.B.: - Shell variables get expanded.
# - Thus:
# - They must be quoted.
# - Perl scalar variables must be escaped.
# - The backslash of \n must be escaped to protect it.
# - Instead of redirecting a single heredoc to perl, we may
# use multiple heredocs with cat within a command group and
# pipe the result to perl.
# This enables us to conditionally add certain lines of code.
{
cat <<-END
my \$elementSepRE=q($elementSepRE);
END
# If $elementSepRE is a literal Perl regexp, qr must be applied
# to it in order to use it.
# N.B.: We cannot write this condition in Perl because when perl
# compiles the script, all statements are checked for validity,
# no matter if they will actually be executed or not.
# And if $elementSepRE was e. g. == ', the line below – although
# not to be executed – would give an error because of an unterminated
# single-quoted string.
[[ $elementSepRE =~ ^m?/ && $elementSepRE =~ /[msixpodualn]*$ ]] && cat <<-END
\$elementSepRE=qr$elementSepRE;
END
cat <<-END
my @array=split(\$elementSepRE, q($string));
print(\$_ . "\\n") for (@array);
END
} | perl
}
Und das Gleiche ohne Kommentare für diejenigen, die auf einen Blick sehen, was vor sich geht ;)
#!/bin/bash
# This function is a wrapper for Perl's split.\
# \
# Since we cannot return an array like in Perl,
# it takes the name of the resulting array as last
# argument.\
# \
# See https://perldoc.perl.org/functions/split for usage info
# and examples.\
# \
# If you provide a Perl regexp that contains e. g. an escaped token like \b,
# space(s) and/or capture group(s), it must be quoted, and e. g. /\b/ must
# be single-quoted.\
# Thus, it's best to generally single-quote a Perl regexp.
function split # Args: <Element separator regexp> <string> <array name>
{
(($# != 3)) && echo "${FUNCNAME[0]}: Wrong number of arguments, returning." && return 1
local elementSepRE=$1
local string=$2
local -n array=$3
local element i=0
local IFS=$'\n'
while read element; do
array[i++]=$element
done <<<$(_perl_split)
}
function _perl_split
{
{
cat <<-END
my \$elementSepRE=q($elementSepRE);
END
[[ $elementSepRE =~ ^m?/ && $elementSepRE =~ /[msixpodualn]*$ ]] && cat <<-END
\$elementSepRE=qr$elementSepRE;
END
cat <<-END
my @array=split(\$elementSepRE, q($string));
print(\$_ . "\\n") for (@array);
END
} | perl
}
30 Stimmen
In Bezug auf Ihr "Edit2": Sie können einfach "IFS aufheben" und der Standardzustand wird wiederhergestellt. Es besteht keine Notwendigkeit, ihn explizit zu speichern und wiederherzustellen, es sei denn, Sie haben Grund zu der Annahme, dass er bereits auf einen anderen Wert als den Standardwert gesetzt wurde. Wenn Sie dies innerhalb einer Funktion tun (und wenn Sie es nicht tun, warum nicht?), können Sie IFS als lokale Variable setzen, die nach dem Verlassen der Funktion auf ihren vorherigen Wert zurückgesetzt wird.
33 Stimmen
@BrooksMoses: (a) +1 für die Verwendung
local IFS=...
wenn möglich; (b) -1 fürunset IFS
wird IFS nicht genau auf seinen Standardwert zurückgesetzt, obwohl ich glaube, dass sich ein nicht gesetzter IFS genauso verhält wie der Standardwert von IFS ($' \t\n '), aber es scheint eine schlechte Praxis zu sein, blind davon auszugehen, dass Ihr Code niemals mit einem benutzerdefinierten IFS-Wert aufgerufen werden wird; (c) eine andere Idee ist, eine Subshell aufzurufen:(IFS=$custom; ...)
Wenn die Subshell beendet wird, kehrt IFS zu dem ursprünglichen Zustand zurück.0 Stimmen
Ich möchte nur einen kurzen Blick auf die Pfade werfen, um zu entscheiden, wohin ich eine ausführbare Datei werfen soll, also habe ich auf die Ausführung von
ruby -e "puts ENV.fetch('PATH').split(':')"
. Wenn Sie reine Bash bleiben wollen, hilft es nicht, wenn Sie eine beliebige Skriptsprache die eine eingebaute Aufteilung hat, ist einfacher.0 Stimmen
Dies ist eine Art Kommentar im Vorbeifahren, aber da der OP E-Mail-Adressen als Beispiel verwendet hat, hat sich jemand die Mühe gemacht, die Frage so zu beantworten, dass sie vollständig RFC 5322-konform ist, d. h., dass jede Zeichenkette in Anführungszeichen vor dem @ erscheinen kann, was bedeutet, dass Sie reguläre Ausdrücke oder eine andere Art von Parser benötigen, anstatt die naive Verwendung von IFS oder anderen simplen Splitterfunktionen.
13 Stimmen
for x in $(IFS=';';echo $IN); do echo "> [$x]"; done
3 Stimmen
Um es als Array zu speichern, musste ich einen weiteren Satz von Klammern setzen und die
\n
für nur ein Leerzeichen. Die letzte Zeile lautet alsomails=($(echo $IN | tr ";" " "))
. Jetzt kann ich also die Elemente vonmails
durch Verwendung der Array-Notationmails[index]
oder einfach in einer Schleife iterieren0 Stimmen
Was auch immer es wert ist, die
tr
Lösung funktioniert in zsh nicht auf die gleiche Weise.0 Stimmen
Für
$IFS
siehe Was ist die genaue Bedeutung vonIFS=$'\n'
0 Stimmen
Sie brauchen weder IFS zu speichern/wiederherzustellen, noch müssen Sie "local" verwenden. ``` declare -a OUT; IFS=';' OUT=( $( echo "$IN") ) ``` ist alles, was Sie brauchen (wie von @user2037659 angedeutet). Der Schlüssel ist, dass Sie Umgebungsvariablen lokal für einen einzelnen Befehl setzen können, indem Sie die Zuweisung(en) vor den Befehl setzen. (Ersetzen Sie das erste ';' durch einen Zeilenumbruch, um die Lesbarkeit zu verbessern). IFS= ist ein besonders nützlicher Fall.