Dividir cadeia por delimitador e obter N-ésimo elemento

39

Eu tenho uma string:

one_two_three_four_five

Eu preciso salvar em uma variável A value two e na variável B value four da string acima

    
por Alex 26.09.2016 / 01:00

7 respostas

49

Use cut com _ como o 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 echo e pipe em vez de Here string:

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
    
por 26.09.2016 / 01:04
13

Usando apenas construções POSIX sh, é possível usar construções de substituição de parâmetros para analisar um delimitador de cada vez. Note que este código assume que existe o número necessário de campos, caso contrário, o último campo é 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 nome com expansão de curingas desativada e IFS definido para o caractere delimitador (isso só funciona se o delimitador é um único caractere sem espaço em branco 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 elimina os parâmetros posicionais. Se você fizer isso em uma função, apenas os parâmetros posicionais da função serão afetados.

No entanto, outra abordagem é usar o read builtin.

IFS=_ read -r first second third fourth trail <<'EOF'
one_two_three_four_five
EOF
    
por 26.09.2016 / 01:21
9

Queria ver uma resposta awk , 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')
    
por 26.09.2016 / 13:14
4

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

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

Usando uma variável temporal $a em vez de $_ porque uma shell reclama.

Em um script completo:

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

Não há alteração do IFS, nem problemas com set -f (expansão do nome do caminho) Nenhuma alteração nos parâmetros posicionais ("$ @").

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

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 aqui-doc quanto o uso de <<< removerão todas as novas linhas finais.
E isso é projetado para um conteúdo variável "one liner".
Soluções para multi-liners são possíveis, mas precisam de construções mais complexas.

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

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

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

Não há equivalente para shells POSIX, já que muitos shells POSIX não possuem arrays.

Para shells que possuem arrays podem ser simples como:
(testado trabalhando em attsh, lksh, mksh, ksh e bash)

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

Mas com muita canalização 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, os arrays começam em 1 e não dividem a string por padrão.
Portanto, algumas mudanças precisam ser feitas para que isso funcione em zsh.

    
por 26.09.2016 / 18:17
2

Com zsh você pode dividir a string (em _ ) em uma matriz:

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

e depois acessar cada elemento por meio de um índice de array:

print -r ${elements[4]}

Lembre-se de que, em zsh (ao contrário de ksh / bash ) índices de matriz começam em 1 .

    
por 13.04.2017 / 14:36
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
    
por 26.09.2016 / 14:46
0

Outro exemplo de awk; 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 os seguintes trabalhos:
    A = 'echo $ {this_str} | awk -F_ '{print $ 1}' '

    B = 'echo $ {this_str} | awk -F_ '{print $ 2}' '

    C = 'echo $ {this_str} | awk -F_ '{print $ 3}' '

    ... e assim por diante ...

    
por 26.07.2018 / 09:45