Risposta compatibile
Ci sono molti modi diversi per farlo bash.
Tuttavia, è importante notare prima che bash
ha molte caratteristiche speciali (i cosiddetti bashismi ) che non funzioneranno in nessun altroconchiglia.
In particolare, gli array , array associativi e sostituzione del modello , che vengono utilizzati nelle soluzioni in questo post così come gli altri nel thread, sono bashismi e potrebbe non funzionare con altri gusci che molte persone utilizzano.
Ad esempio: sul mio Debian GNU / Linux , c'è una shell standard chiamatatrattino; Conosco molte persone a cui piace usare un'altra shell chiamataksh; e c'è anche uno strumento speciale chiamatobusybox con il suo interprete di shell (cenere).
Stringa richiesta
La stringa da dividere nella domanda precedente è:
IN="bla@some.com;john@home.com"
Userò una versione modificata di questa stringa per garantire che la mia soluzione sia robusta per le stringhe contenenti spazi bianchi, che potrebbero interrompere altre soluzioni:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
Dividi la stringa in base al delimitatore in bash (versione> = 4.2)
In puro bash
, possiamo creare un array con elementi divisi per un valore temporaneo per IFS (il separatore del campo di input ). L'IFS, tra le altre cose, dice a bash
quale carattere deve trattare come delimitatore tra gli elementi quando si definisce un array:
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
In versioni più recenti di bash
, anteponendo un comando con una definizione IFS cambia le IFS per quel comando solo e reset al valore precedente subito dopo. Ciò significa che possiamo fare quanto sopra in una sola riga:
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'
Possiamo vedere che la stringa IN
è stata memorizzata in un array chiamato fields
, diviso nei punti e virgola:
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>'
(Possiamo anche visualizzare il contenuto di queste variabili usando 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>")
Si noti che read
è il modo più rapido per eseguire la divisione perché non sono presenti fork o risorse esterne chiamate.
Una volta definito l'array, è possibile utilizzare un semplice ciclo per elaborare ciascun campo (o, piuttosto, ogni elemento dell'array che ora è stato definito):
# `"${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>]
Oppure potresti eliminare ogni campo dall'array dopo l'elaborazione usando un approccio di spostamento , che mi piace:
while [ "$fields" ] ;do
echo "> [$fields]"
# slice the array
fields=("${fields[@]:1}")
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
E se vuoi solo una semplice stampa dell'array, non hai nemmeno bisogno di passarci sopra:
printf "> [%s]\n" "${fields[@]}"
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Aggiornamento: recente bash > = 4.4
Nelle versioni più recenti di bash
, puoi anche giocare con il comando mapfile
:
mapfile -td \; fields < <(printf "%s\0" "$IN")
Questa sintassi conserva caratteri speciali, newline e campi vuoti!
Se non si desidera includere campi vuoti, è possibile effettuare le seguenti operazioni:
mapfile -td \; fields <<<"$IN"
fields=("${fields[@]%$'\n'}") # drop '\n' added by '<<<'
Con mapfile
, puoi anche saltare la dichiarazione di un array e implicitamente "loop" sugli elementi delimitati, chiamando una funzione su ciascuno:
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
(Nota: la \0
fine della stringa di formato è inutile se non ti interessano i campi vuoti alla fine della stringa o non sono presenti.)
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.
Oppure potresti usare <<<
, e nel corpo della funzione includere alcune elaborazioni per eliminare la nuova riga che aggiunge:
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.
Dividi la stringa in base al delimitatore in conchiglia
Se non puoi usare bash
, o se vuoi scrivere qualcosa che può essere usato in molte diverse shell, spesso non puoi usare i bashismi - e questo include gli array che abbiamo usato nelle soluzioni sopra.
Tuttavia, non è necessario utilizzare le matrici per eseguire il loop su "elementi" di una stringa. Esiste una sintassi utilizzata in molte shell per eliminare sottostringhe di una stringa dalla prima o dall'ultima occorrenza di un modello. Si noti che *
è un carattere jolly che sta per zero o più caratteri:
(La mancanza di questo approccio in qualsiasi soluzione pubblicata finora è il motivo principale per cui sto scrivendo questa risposta;)
${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
Come spiegato da Score_Under :
#
ed %
elimina la sottostringa corrispondente più corta possibile rispettivamente dall'inizio e dalla fine della stringa, e
##
ed %%
elimina la sottostringa corrispondente più lunga possibile.
Usando la sintassi sopra, possiamo creare un approccio in cui estraiamo "elementi" di sottostringa dalla stringa eliminando le sottostringhe fino o dopo il delimitatore.
Il blocco codice qui sotto funziona bene in bash(compresi quelli di Mac OS bash
),trattino, ksh, e busybox'S cenere:
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>]
Divertiti!