Resposta compatível
Existem várias maneiras diferentes de fazer isso em bater.
No entanto, é importante notar que existembash
muitos recursos especiais (os chamados basismos ) que não funcionarão em nenhum outroConcha.
Em particular, matrizes , matrizes associativas e substituição padrão , que são utilizados nas soluções neste post, bem como outros no segmento, são bashisms e não podem trabalhar sob outras conchas que muitas pessoas usam.
Por exemplo: no meu Debian GNU / Linux , existe um padrão shell chamadotraço; Conheço muitas pessoas que gostam de usar outro shell chamadoksh; e também há uma ferramenta especial chamadabusybox com seu próprio interpretador de shell (cinza)
Sequência solicitada
A string a ser dividida na pergunta acima é:
IN="bla@some.com;john@home.com"
Usarei uma versão modificada dessa cadeia para garantir que minha solução seja robusta para cadeias que contenham espaço em branco, o que poderia interromper outras soluções:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
Dividir string com base no delimitador em bater (versão> = 4.2)
Em termos puros bash
, podemos criar uma matriz com elementos divididos por um valor temporário para o IFS (o separador de campos de entrada ). O IFS, entre outras coisas, informa bash
quais caracteres deve ser tratado como um delimitador entre os elementos ao definir uma matriz:
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
Em versões mais recentes do bash
, prefixando um comando com uma definição IFS altera as IFS para esse comando única e redefine para o valor anterior imediatamente depois. Isso significa que podemos fazer o acima em apenas uma linha:
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'
Podemos ver que a string IN
foi armazenada em uma matriz chamada fields
, dividida em ponto e vírgula:
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>'
(Também podemos exibir o conteúdo dessas variáveis 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>")
Observe que read
é a maneira mais rápida de fazer a divisão, porque não há garfos ou recursos externos chamados.
Depois que a matriz é definida, você pode usar um loop simples para processar cada campo (ou melhor, cada elemento da matriz que você definiu agora):
# `"${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>]
Ou você pode soltar cada campo da matriz após o processamento usando uma abordagem de deslocamento , que eu gosto:
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 você quer apenas uma impressão simples da matriz, não precisa nem fazer um loop sobre ela:
printf "> [%s]\n" "${fields[@]}"
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Atualização: recente bater > = 4.4
Nas versões mais recentes do bash
, você também pode jogar com o comando mapfile
:
mapfile -td \; fields < <(printf "%s\0" "$IN")
Essa sintaxe preserva caracteres especiais, novas linhas e campos vazios!
Se você não quiser incluir campos vazios, faça o seguinte:
mapfile -td \; fields <<<"$IN"
fields=("${fields[@]%$'\n'}") # drop '\n' added by '<<<'
Com mapfile
, você também pode pular a declaração de uma matriz e implicitamente "fazer um loop" sobre os elementos delimitados, chamando uma função em cada:
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: a \0
sequência no final da sequência de formatação é inútil se você não se importa com campos vazios no final da sequência ou eles não estiverem presentes.)
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.
Ou você pode usar <<<
e, no corpo da função, incluir algum processamento para descartar a nova linha que ele adiciona:
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.
Dividir string com base no delimitador em Concha
Se você não pode usar bash
, ou se deseja escrever algo que possa ser usado em muitas conchas diferentes, geralmente não pode usar basismos - e isso inclui as matrizes que usamos nas soluções acima.
No entanto, não precisamos usar matrizes para fazer um loop sobre os "elementos" de uma string. Há uma sintaxe usada em muitos shells para excluir substrings de uma string da primeira ou da última ocorrência de um padrão. Observe que *
é um curinga que representa zero ou mais caracteres:
(A falta dessa abordagem em qualquer solução postada até agora é o principal motivo pelo qual estou escrevendo esta resposta;)
${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
Conforme explicado por Score_Under :
#
e %
exclua a substring correspondente mais curta possível do início e do final da string, respectivamente, e
##
e %%
exclua a substring correspondente mais longa possível.
Usando a sintaxe acima, podemos criar uma abordagem na qual extraímos "elementos" de substring da string excluindo as substrings até ou após o delimitador.
O código de bloqueio abaixo funciona bem em bater (incluindo Mac OS's bash
),traço, kshe busyboxé cinza:
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>]
Diverta-se!