Como dividir uma string delimitada em uma matriz no awk?

169

Como dividir a string quando ela contém símbolos de pipe |. Eu quero dividi-los para estar na matriz.

eu tentei

echo "12:23:11" | awk '{split($0,a,":"); print a[3] a[2] a[1]}'

O que funciona bem. Se minha string for como, "12|23|11"então, como os divido em uma matriz?

Mohamed Saligh
fonte
3
Observe que sua saída está concatenando os elementos da matriz, sem separador. Se você preferir que eles sejam separados OFS, coloque vírgulas entre eles, fazendo com printque sejam vistos como argumentos separados.
precisa
Ou você pode usar sed:echo "12:23:11" | sed "s/.*://"
slushy
@ Slushy: seu comando não é o que o solicitante precisa. seu comando ( echo "12:23:11" | sed "s/.*://") exclui tudo até (e inclusive) o último ":", mantendo apenas o "11" ... funciona para obter o último número, mas precisaria ser modificado (de uma maneira difícil de ler) para obter o segundo número, etc. awk (e a divisão do awk) é muito mais elegante e legível.
Olivier Dulac
se você precisa de divisão em um único personagem que você pode usarcut
ccpizza

Respostas:

274

Você tentou:

echo "12|23|11" | awk '{split($0,a,"|"); print a[3],a[2],a[1]}'
Calin Paul Alexandru
fonte
2
@ Mohamed Saligh, se você usa Solaris, precisa usar / usr / xpg4 / bin / awk , dado o comprimento da string.
Dimitre Radoulov
5
'não está funcionando para mim'. especialmente com dois pontos entre os valores ecoados e a divisão configurada para dividir em '|' ??? Erro de digitação? Boa sorte a todos.
shellter 4/11/11
1
Melhor com alguma explicação de sintaxe.
Alston
2
Isso não funcionará no GNU awk, porque o terceiro argumento para splité a expressão regular e |é um símbolo especial, que precisa ser escapado. Usesplit($0, a, "\|")
WhiteWind
1
@WhiteWind: outra maneira de "garantir" que |é visto como um caractere e não como um símbolo especial é colocá-lo entre []: ou seja, split($0, a, "[|]") # eu gosto mais disso do que '\ |', em alguns casos, especialmente como uma variante do regexp ( perl vs grep vs .. outros?) podem ter "|" interpretado literalmente e "\ |" visto como separador de expressões regulares, em vez do oposto ... ymmv
Olivier Dulac
119

Para dividir uma string em uma matriz awk, usamos a função split():

 awk '{split($0, a, ":")}'
 #           ^^  ^  ^^^
 #            |  |   |
 #       string  |   delimiter
 #               |
 #               array to store the pieces

Se nenhum separador for fornecido, ele usará o FS, que assume como padrão o espaço:

$ awk '{split($0, a); print a[2]}' <<< "a:b c:d e"
c:d

Podemos dar um separador, por exemplo ::

$ awk '{split($0, a, ":"); print a[2]}' <<< "a:b c:d e"
b c

O que equivale a defini-lo através do FS:

$ awk -F: '{split($0, a); print a[1]}' <<< "a:b c:d e"
b c

No gawk, você também pode fornecer o separador como um regexp:

$ awk '{split($0, a, ":*"); print a[2]}' <<< "a:::b c::d e" #note multiple :
b c

E até veja o que o delimitador estava em cada etapa usando seu quarto parâmetro:

$ awk '{split($0, a, ":*", sep); print a[2]; print sep[1]}' <<< "a:::b c::d e"
b c
:::

Vamos citar a página de manual do GNU awk :

split (string, array [, fieldsep [, seps]])

Divida a sequência em partes separadas por fieldsep e armazene as partes na matriz e as seqüências separadoras na matriz seps . A primeira peça é armazenada array[1], a segunda peça array[2]e assim por diante. O valor da string do terceiro argumento, fieldsep , é um regexp que descreve onde dividir a string (da mesma forma que o FS pode ser um regexp que descreve onde dividir os registros de entrada). Se fieldsep for omitido, o valor de FS é usado. split()retorna o número de elementos criados. seps é uma gawkextensão, seps[i]sendo a string separadora entrearray[i]e array[i+1]. Se fieldsep for um espaço único, qualquer espaço em branco à esquerda entrará seps[0]e qualquer espaço em branco à direita entrará seps[n], onde n é o valor de retorno de split()(ou seja, o número de elementos na matriz).

fedorqui 'Então pare de prejudicar'
fonte
apenas mencionar que você está usando o GNU awk, não é regular awk (que não armazena separadores em seps [], e tem outras limitações)
Olivier Dulac
17

Por favor seja mais específico! O que você quer dizer com "não funciona"? Poste a saída exata (ou mensagem de erro), seu SO e versão do awk:

% awk -F\| '{
  for (i = 0; ++i <= NF;)
    print i, $i
  }' <<<'12|23|11'
1 12
2 23
3 11

Ou, usando a divisão:

% awk '{
  n = split($0, t, "|")
  for (i = 0; ++i <= n;)
    print i, t[i]
  }' <<<'12|23|11'
1 12
2 23
3 11

Edit: no Solaris, você precisará usar o POSIX awk ( / usr / xpg4 / bin / awk ) para processar 4000 campos corretamente.

Dimitre Radoulov
fonte
for(i = 0ou for(i = 1?
PiotrNycz 17/09/2015
i = 0, porque eu uso ++ i depois (não i ++).
Dimitre Radoulov 17/09/2015
3
Ok - eu não percebi isso. Eu acredito fortemente mais legível seria for (i = 1; i <= n; ++i)...
PiotrNycz
5

Não gosto da echo "..." | awk ...solução, pois ela chama chamadas desnecessárias forke de execsistema.

Eu prefiro a solução de Dimitre com um pequeno toque

awk -F\| '{print $3 $2 $1}' <<<'12|23|11'

Ou uma versão um pouco mais curta:

awk -F\| '$0=$3 $2 $1' <<<'12|23|11'

Nesse caso, o registro de saída é uma condição verdadeira, para que seja impresso.

Nesse caso específico, o stdinredirecionamento pode ser poupado com a configuração de um variável interna:

awk -v T='12|23|11' 'BEGIN{split(T,a,"|");print a[3] a[2] a[1]}'

eu usei um bom tempo, mas em isso pode ser gerenciado por manipulação interna de strings. No primeiro caso, a cadeia original é dividida pelo terminador interno. No segundo caso, supõe-se que a sequência sempre contenha pares de dígitos separados por um separador de um caractere.

T='12|23|11';echo -n ${T##*|};T=${T%|*};echo ${T#*|}${T%|*}
T='12|23|11';echo ${T:6}${T:3:2}${T:0:2}

O resultado em todos os casos é

112312
TrueY
fonte
Eu acho que o resultado final deveria ser as referências às variáveis ​​da matriz awk, independentemente do exemplo de saída de impressão fornecido. Mas você perdeu um caso realmente fácil para fornecer seu resultado final. T = '12: 23: 11 '; echo $ {T //:}
Daniel Liston
@DanielListon Você está certo! Obrigado! Eu não sabia que o arrasto / pode ser deixado nesta bashexpressão ...
TrueY
4

Na verdade, awkpossui um recurso chamado link 'Input Field Separator Variable' . Isto é como usá-lo. Não é realmente uma matriz, mas usa as variáveis ​​$ internas. Para dividir uma string simples, é mais fácil.

echo "12|23|11" | awk 'BEGIN {FS="|";} { print $1, $2, $3 }'
Sven
fonte
3
echo "12|23|11" | awk '{split($0,a,"|"); print a[3] a[2] a[1]}'

Deveria trabalhar.

codaddict
fonte
3
echo "12|23|11" | awk '{split($0,a,"|"); print a[3] a[2] a[1]}'
Schildmeijer
fonte
1

Piada? :)

E se echo "12|23|11" | awk '{split($0,a,"|"); print a[3] a[2] a[1]}'

Esta é a minha saída:

p2> echo "12|23|11" | awk '{split($0,a,"|"); print a[3] a[2] a[1]}'
112312

então eu acho que está funcionando depois de tudo ..

duedl0r
fonte
isso é devido ao comprimento da string? desde então, meu comprimento de string é 4000. alguma idéia #
Mohamed Saligh
1

Sei que essa é uma pergunta antiga, mas pensei que talvez alguém gostasse do meu truque. Especialmente porque essa solução não se limita a um número específico de itens.

# Convert to an array
_ITEMS=($(echo "12|23|11" | tr '|' '\n'))

# Output array items
for _ITEM in "${_ITEMS[@]}"; do
  echo "Item: ${_ITEM}"
done

A saída será:

Item: 12
Item: 23
Item: 11
Qorbani
fonte