Kompatybilna odpowiedź
Można to zrobić na wiele różnych sposobów grzmotnąć.
Jednak ważne jest, aby najpierw zauważyć, że bash
ma wiele specjalnych funkcji (tak zwanych baszizmów ), które nie będą działać w żadnej innejmuszla.
W szczególności tablice , tablice asocjacyjne i podstawianie wzorców , które są używane w rozwiązaniach w tym poście, a także inne w wątku, są bashizmami i mogą nie działać pod innymi powłokami, z których korzysta wiele osób.
Na przykład: w moim Debian GNU / Linux jest standardowa powłoka o nazwiedziarskość; Znam wielu ludzi, którzy lubią używać innej powłoki o nazwieksh; i istnieje również specjalne narzędzie o nazwiebusybox z własnym tłumaczem powłoki (popiół).
Żądany ciąg
Ciąg do podzielenia w powyższym pytaniu to:
IN="bla@some.com;john@home.com"
Użyję zmodyfikowanej wersji tego ciągu, aby upewnić się, że moje rozwiązanie jest odporne na ciągi zawierające białe znaki, które mogłyby uszkodzić inne rozwiązania:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
Podziel ciąg na podstawie separatora w grzmotnąć (wersja> = 4.2)
W czystej postaci bash
możemy utworzyć tablicę z elementami podzielonymi przez tymczasową wartość dla IFS ( separator pola wejściowego ). IFS wskazuje między innymi, bash
które znaki należy traktować jako ogranicznik między elementami podczas definiowania tablicy:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
# save original IFS value so we can restore it later
oIFS="$IFS"
IFS=";"
declare -a fields=($IN)
IFS="$oIFS"
unset oIFS
W nowszych wersjach bash
, poprzedzając polecenie z definicji zmienia IFS IFS dla tego polecenia tylko i resetuje go do poprzedniej wartości zaraz potem. Oznacza to, że możemy to zrobić w jednym wierszu:
IFS=\; read -a fields <<<"$IN"
# after this command, the IFS resets back to its previous value (here, the default):
set | grep ^IFS=
# IFS=$' \t\n'
Widzimy, że ciąg IN
został zapisany w tablicy o nazwie fields
podzielonej na średniki:
set | grep ^fields=\\\|^IN=
# fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
# IN='bla@some.com;john@home.com;Full Name <fulnam@other.org>'
(Możemy również wyświetlić zawartość tych zmiennych za pomocą declare -p
:)
declare -p IN fields
# declare -- IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
# declare -a fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
Zauważ, że read
jest to najszybszy sposób wykonania podziału, ponieważ nie ma wywoływanych widelców ani zasobów zewnętrznych.
Po zdefiniowaniu tablicy możesz użyć prostej pętli do przetworzenia każdego pola (a raczej każdego elementu w tablicy, którą teraz zdefiniowałeś):
# `"${fields[@]}"` expands to return every element of `fields` array as a separate argument
for x in "${fields[@]}" ;do
echo "> [$x]"
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Lub możesz usunąć każde pole z tablicy po przetworzeniu przy użyciu metody przesunięcia , co lubię:
while [ "$fields" ] ;do
echo "> [$fields]"
# slice the array
fields=("${fields[@]:1}")
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
A jeśli chcesz po prostu wydrukować tablicę, nie musisz jej nawet zapętlać:
printf "> [%s]\n" "${fields[@]}"
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Aktualizacja: ostatnia grzmotnąć > = 4,4
W nowszych wersjach bash
możesz także grać za pomocą polecenia mapfile
:
mapfile -td \; fields < <(printf "%s\0" "$IN")
Ta składnia chroni specjalne znaki, znaki nowej linii i puste pola!
Jeśli nie chcesz dołączać pustych pól, możesz wykonać następujące czynności:
mapfile -td \; fields <<<"$IN"
fields=("${fields[@]%$'\n'}") # drop '\n' added by '<<<'
Za pomocą mapfile
można również pominąć deklarowanie tablicy i niejawnie „zapętlić” ograniczane elementy, wywołując funkcję na każdym:
myPubliMail() {
printf "Seq: %6d: Sending mail to '%s'..." $1 "$2"
# mail -s "This is not a spam..." "$2" </path/to/body
printf "\e[3D, done.\n"
}
mapfile < <(printf "%s\0" "$IN") -td \; -c 1 -C myPubliMail
(Uwaga: \0
na końcu łańcucha formatu jest bezużyteczne, jeśli nie obchodzi Cię puste pole na końcu łańcucha lub nie ma ich).
mapfile < <(echo -n "$IN") -td \; -c 1 -C myPubliMail
# Seq: 0: Sending mail to 'bla@some.com', done.
# Seq: 1: Sending mail to 'john@home.com', done.
# Seq: 2: Sending mail to 'Full Name <fulnam@other.org>', done.
Lub możesz użyć <<<
, a w treści funkcji włącz trochę przetwarzania, aby usunąć nowy wiersz, który dodaje:
myPubliMail() {
local seq=$1 dest="${2%$'\n'}"
printf "Seq: %6d: Sending mail to '%s'..." $seq "$dest"
# mail -s "This is not a spam..." "$dest" </path/to/body
printf "\e[3D, done.\n"
}
mapfile <<<"$IN" -td \; -c 1 -C myPubliMail
# Renders the same output:
# Seq: 0: Sending mail to 'bla@some.com', done.
# Seq: 1: Sending mail to 'john@home.com', done.
# Seq: 2: Sending mail to 'Full Name <fulnam@other.org>', done.
Podziel ciąg na podstawie separatora w muszla
Jeśli nie możesz użyć bash
lub chcesz napisać coś, co można wykorzystać w wielu różnych powłokach, często nie możesz użyć bashism - i obejmuje to tablice, których używaliśmy w powyższych rozwiązaniach.
Nie musimy jednak używać tablic do zapętlania „elementów” łańcucha. W wielu powłokach stosowana jest składnia do usuwania podciągów ciągu od pierwszego lub ostatniego wystąpienia wzorca. Pamiętaj, że *
jest to symbol wieloznaczny oznaczający zero lub więcej znaków:
(Brak tego podejścia w jakimkolwiek opublikowanym rozwiązaniu jest głównym powodem, dla którego piszę tę odpowiedź;)
${var#*SubStr} # drops substring from start of string up to first occurrence of `SubStr`
${var##*SubStr} # drops substring from start of string up to last occurrence of `SubStr`
${var%SubStr*} # drops substring from last occurrence of `SubStr` to end of string
${var%%SubStr*} # drops substring from first occurrence of `SubStr` to end of string
Jak wyjaśniono w Score_Under :
#
i %
usuń możliwie najkrótsze pasujące podciąg odpowiednio z początku i końca łańcucha, oraz
##
i %%
usuń możliwie najdłuższy pasujący podciąg.
Korzystając z powyższej składni, możemy stworzyć podejście, w którym wyodrębniamy „elementy” podłańcucha z ciągu, usuwając podciągi do separatora lub po nim.
Poniższy blok kodu działa dobrze w grzmotnąć(w tym Mac OS bash
),dziarskość, ksh, i busybox„s popiół:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
while [ "$IN" ] ;do
# extract the substring from start of string up to delimiter.
# this is the first "element" of the string.
iter=${IN%%;*}
echo "> [$iter]"
# if there's only one element left, set `IN` to an empty string.
# this causes us to exit this `while` loop.
# else, we delete the first "element" of the string from IN, and move onto the next.
[ "$IN" = "$iter" ] && \
IN='' || \
IN="${IN#*;}"
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Baw się dobrze!