Dividir string por delimitador e obter o N-ésimo elemento

75

Eu tenho uma string:

one_two_three_four_five

Preciso salvar em um Avalor variável twoe em Bvalor variável fourda string acima

Alex
fonte

Respostas:

106

Use cutcom _como delimitador de campo e obtenha os campos desejados:

A="$(cut -d'_' -f2 <<<'one_two_three_four_five')"
B="$(cut -d'_' -f4 <<<'one_two_three_four_five')"

Você também pode usar echoe canalizar em vez da cadeia Here:

A="$(echo 'one_two_three_four_five' | cut -d'_' -f2)"
B="$(echo 'one_two_three_four_five' | cut -d'_' -f4)"

Exemplo:

$ s='one_two_three_four_five'

$ A="$(cut -d'_' -f2 <<<"$s")"
$ echo "$A"
two

$ B="$(cut -d'_' -f4 <<<"$s")"
$ echo "$B"
four
heemail
fonte
Existe alguma alternativa? Estou usando o ksh (não o bsh) e ele retorna ksh: erro de sintaxe: `<'inesperado
Alex
@ Alex Verifique minhas edições.
heemayl
Boa resposta, tenho uma pequena pergunta: o que acontecerá se sua variável "$ s" for uma pasta de caminho. Quando tento cortar uma pasta de caminho, gosto do seguinte: `$ FILE = my_user / my_folder / [file] *` $ echo $FILE my_user/my_folder/file.csv $ A="$(cut -d'/' -f2 <<<"$FILE")" $ echo $A [file]* Você sabe o que está acontecendo aqui?
Henry Navarro
1
E se você quiser apenas o último campo, usando apenas os shell embutidos - sem precisar especificar sua posição ou quando não souber o número de campos:echo "${s##*_}"
Amit Naidu
19

Usando apenas construções sh POSIX, é possível usar construções de substituição de parâmetro para analisar um delimitador por vez. Observe que esse código pressupõe que haja o número necessário de campos, caso contrário, o último campo será repetido.

string='one_two_three_four_five'
remainder="$string"
first="${remainder%%_*}"; remainder="${remainder#*_}"
second="${remainder%%_*}"; remainder="${remainder#*_}"
third="${remainder%%_*}"; remainder="${remainder#*_}"
fourth="${remainder%%_*}"; remainder="${remainder#*_}"

Como alternativa, você pode usar uma substituição de parâmetro sem aspas com a expansão de curinga desativada e IFSconfigurada para o caractere delimitador (isso só funciona se o delimitador for um caractere não-espaço em branco único ou se qualquer sequência de espaço em branco for um delimitador).

string='one_two_three_four_five'
set -f; IFS='_'
set -- $string
second=$2; fourth=$4
set +f; unset IFS

Isso derruba os parâmetros posicionais. Se você fizer isso em uma função, apenas os parâmetros posicionais da função serão afetados.

Ainda outra abordagem é usar o readbuiltin.

IFS=_ read -r first second third fourth trail <<'EOF'
one_two_three_four_five
EOF
Gilles
fonte
O uso de unset IFSnão retorna IFSao padrão. Se depois disso alguém OldIFS="$IFS"tiver um valor nulo no OldIFS. Além disso, supõe-se que o valor anterior do IFS seja o padrão, o que é muito possível (e útil) não ser. A única solução correta é armazenar old="$IFS"e restaurar posteriormente com IFS = "$ old". Ou ... use um sub-shell (...). Ou, melhor ainda, leia minha resposta.
sorontar 26/09/16
@sorontar unset IFSnão restaura IFSo valor padrão, mas retorna a divisão do campo para o efeito padrão. Sim, é uma limitação, mas geralmente aceitável na prática. O problema com um subshell é que precisamos extrair dados dele. Eu mostro uma solução que não muda o estado no final, com read. (Ele funciona em shells POSIX, mas o IIRC não no shell Bourne porque executaria o readsubshell devido ao documento aqui.) Usar <<<como na resposta é uma variante que funciona apenas em ksh / bash / zsh.
Gilles
Não vejo nenhum problema, mesmo com o shell att ou heirloom sobre um subshell. Todas as cascas testadas (incluindo a bourne antiga) fornecem o valor correto na casca principal.
sorontar 26/09/16
O que acontecerá se meu caminho for algo assim user/my_folder/[this_is_my_file]*? O que eu obtenho quando sigo essas etapas é[this_is_my_file]*
Henry Navarro
@HenryNavarro Esta saída não corresponde a nenhum trecho de código na minha resposta. Nenhum deles faz algo especial /.
Gilles
17

Queria ver uma awkresposta, então aqui está uma:

A=$(awk -F_ '{print $2}' <<< 'one_two_three_four_five')
B=$(awk -F_ '{print $4}' <<< 'one_two_three_four_five')
Paul Evans
fonte
1
E se você quiser a última peça - sem precisar especificar sua posição ou quando não souber o número de campos:awk -F_ '{print $NF}' <<< 'one_two_3_4_five'
Amit Naidu
8

A maneira mais simples (para cascas com <<<) é:

 IFS='_' read -r a second a fourth a <<<"$string"

Usando uma variável temporal em $avez de $_porque um shell reclama.

Em um script completo:

 string='one_two_three_four_five'
 IFS='_' read -r a second a fourth a <<<"$string"
 echo "$second $fourth"

Nenhuma alteração do IFS, nenhum problema com set -f(expansão do nome do caminho) Nenhuma alteração nos parâmetros posicionais ("$ @").


Para uma solução portátil para todos os shells (sim, todos os POSIX incluídos) sem alterar o IFS ou set -f, use o (um pouco mais complexo) equivalente heredoc:

string='one_two_three_four_five'

IFS='_' read -r a second a fourth a <<-_EOF_
$string
_EOF_

echo "$second $fourth"

Entenda que essas soluções (tanto o documento em questão quanto o uso de <<<removerão todas as novas linhas finais.
E isso foi desenvolvido para um conteúdo variável de "uma linha". As
soluções para várias linhas são possíveis, mas precisam de construções mais complexas.


Uma solução muito simples é possível na versão 4.4 do bash

readarray -d _ -t arr <<<"$string"

echo "array ${arr[1]} ${arr[3]}"   # array numbers are zero based.

Não há equivalente para shells POSIX, pois muitos shells POSIX não possuem matrizes.

Para shells com matrizes pode ser tão simples quanto:
(testado trabalhando em attsh, lksh, mksh, ksh e bash)

set -f; IFS=_; arr=($string)

Mas com muito encanamento adicional para manter e redefinir variáveis ​​e opções:

string='one_* *_three_four_five'

case $- in
    *f*) noglobset=true; ;;
    *) noglobset=false;;
esac

oldIFS="$IFS"

set -f; IFS=_; arr=($string)

if $noglobset; then set -f; else set +f; fi

echo "two=${arr[1]} four=${arr[3]}"

No zsh, as matrizes começam em 1 e não dividem a string por padrão.
Portanto, algumas alterações precisam ser feitas para que isso funcione no zsh.

sorontar
fonte
soluções que usam read são simples, desde que OP não quer extrair a 76 e os elementos 127 a partir de uma longa seqüência ...
don_crissti
@don_crissti Bem, sim, é claro, mas uma construção semelhante: readarraypoderia ser mais fácil de usar para essa situação.
sorontar 26/09/16
@don_crissti Também adicionei uma solução de matriz para shells que possuem matrizes. Para shells POSIX, bem, não tendo matrizes, parâmetros posicionais com até 127 elementos não são uma solução "simples" em nenhuma medida.
sorontar 26/09/16
2

Com zshvocê pode dividir a string (ativada _) em uma matriz:

elements=(${(s:_:)string})

e acesse cada elemento / qualquer via índice de matriz:

print -r ${elements[4]}

Tenha em mente que em zsh(ao contrário ksh/ bash) índices de matriz começam em 1 .

don_crissti
fonte
Lembre-se de adicionar um set -faviso à primeira solução. ... asteriscos, *talvez?
sorontar 26/09/16
@sorontar - por que você acha que eu preciso set -f? Eu não estou usando read/ IFS. Tente minhas soluções com uma string como *_*_*ou o que quer ...
don_crissti
Não é para o zsh, mas o usuário pediu uma solução ksh; portanto, ele pode tentar usá-lo nesse shell. Um aviso o ajudará a evitar o problema.
sorontar 26/09/16
1

É permitida uma solução python?

# python -c "import sys; print sys.argv[1].split('_')[1]" one_two_three_four_five
two

# python -c "import sys; print sys.argv[1].split('_')[3]" one_two_three_four_five
four
fhgd
fonte
Não. Má resposta ruim
Raj Kumar
0

Outro exemplo estranho; mais simples de entender.

A=\`echo one_two_three_four_five | awk -F_ '{print $1}'\`  
B=\`echo one_two_three_four_five | awk -F_ '{print $2}'\`  
C=\`echo one_two_three_four_five | awk -F_ '{print $3}'\`  
... and so on...  

Também pode ser usado com variáveis.
Suponha:
this_str = "one_two_three_four_five"
Então o seguinte funciona:
A = `echo $ {this_str} | awk -F_ '{print $ 1}' '
B = `eco $ {this_str} | awk -F_ '{print $ 2}' '
C = `eco $ {this_str} | awk -F_ '{print $ 3}' '
... e assim por diante ...

user274900
fonte