¿Cómo dividir una cadena delimitada en una matriz en awk?

169

Cómo dividir la cadena cuando contiene símbolos de tubería |en ella. Quiero dividirlos para que estén en la matriz.

Lo intenté

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

Que funciona bien. Si mi cadena es así "12|23|11", ¿cómo los divido en una matriz?

Mohamed Saligh
fuente
3
Tenga en cuenta que su salida está concatenando los elementos de la matriz, sin separador. Si, en cambio, desea que se separen OFS, pegue comas entre ellos, haciéndolos printver como argumentos separados.
dubiousjim
O puede usar sed:echo "12:23:11" | sed "s/.*://"
slushy
@slushy: tu comando no es para nada lo que el autor de la pregunta necesita. su comando ( echo "12:23:11" | sed "s/.*://") elimina todo hasta (e incluye) el último ":", manteniendo solo el "11" ... funciona para obtener el último número, pero necesitaría ser modificado (de una manera difícil de leer) para obtener el segundo número, etc. awk (y la división de awk) es mucho más elegante y legible.
Olivier Dulac
si necesita dividirse en un solo personaje, puede usarlocut
ccpizza

Respuestas:

274

Has probado:

echo "12|23|11" | awk '{split($0,a,"|"); print a[3],a[2],a[1]}'
Calin Paul Alexandru
fuente
2
@ Moohamed Saligh, si está en Solaris, debe usar / usr / xpg4 / bin / awk , dada la longitud de la cadena.
Dimitre Radoulov el
55
'no está funcionando para mí'. especialmente con dos puntos entre los valores repetidos y la configuración dividida para dividir en '|' ??? ¿Error de tipografía? Buena suerte a todos.
Shellter
1
Mejor con alguna explicación de sintaxis.
Alston
2
Esto no funcionará en GNU awk, porque el tercer argumento splites expresión regular, y |es un símbolo especial, que necesita ser escapado. Usosplit($0, a, "\|")
WhiteWind
1
@WhiteWind: otra forma de "garantizar" que |se ve como un carácter y no un símbolo especial es ponerlo entre []: es decir, split($0, a, "[|]") # Me gusta más que '\ |', en algunos casos, especialmente como alguna variante de regexp ( perl vs grep vs .. otros?) puede tener "|" interpretado literalmente y "\ |" visto como separador de expresiones regulares, en lugar de lo contrario ... ymmv
Olivier Dulac
119

Para dividir una cadena en una matriz awk, utilizamos la función split():

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

Si no se proporciona un separador, utiliza el FS, que por defecto es el espacio:

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

Podemos dar un separador, por ejemplo ::

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

Lo que es equivalente a configurarlo a través de FS:

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

En gawk también puede proporcionar el separador como una expresión regular:

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

E incluso vea cuál era el delimitador en cada paso utilizando su cuarto parámetro:

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

Citemos la página de manual de GNU awk :

split (cadena, matriz [, fieldsep [, seps]])

Dividir cadena en piezas separadas por fieldsep y almacene las piezas en la matriz y las cadenas de separación en la matriz de seps . La primera pieza se almacena en array[1], la segunda pieza en array[2], y así sucesivamente. El valor de cadena del tercer argumento, fieldsep , es una expresión regular que describe dónde dividir la cadena (del mismo modo que FS puede ser una expresión regular que describe dónde dividir los registros de entrada). Si se omite fieldsep , se utiliza el valor de FS . split()Devuelve el número de elementos creados. seps es una gawkextensión, seps[i]siendo la cadena de separación entrearray[i]y array[i+1]. Si fieldsep es un espacio único, entonces cualquier espacio en blanco inicial entraseps[0]y cualquier espacio en blanco final entra seps[n], donde n es el valor de retorno de split()(es decir, el número de elementos en la matriz).

fedorqui 'así que deja de dañar'
fuente
solo mencione que está utilizando gnu awk, no awk regular (que no almacena separadores en seps [] y tiene otras limitaciones)
Olivier Dulac
17

¡Por favor sé más específico! ¿Qué quieres decir con "no funciona"? Publique la salida exacta (o mensaje de error), su sistema operativo y la versión awk:

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

O, usando split:

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

Editar: en Solaris necesitará usar POSIX awk ( / usr / xpg4 / bin / awk ) para procesar 4000 campos correctamente.

Dimitre Radoulov
fuente
for(i = 0o for(i = 1?
PiotrNycz
i = 0, porque uso ++ i after (no i ++).
Dimitre Radoulov
3
Ok, no me di cuenta de esto. Creo firmemente que sería más legible for (i = 1; i <= n; ++i)...
PiotrNycz
5

No me gusta la echo "..." | awk ...solución, ya que llama innecesarias forky execllamadas del sistema.

Prefiero una solución de Dimitre con un pequeño giro

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

O una versión un poco más corta:

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

En este caso, se crea el registro de salida, que es una condición verdadera, por lo que se imprime.

En este caso específico el stdin redirección se puede ahorrar configurando un variable interna:

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

solía bastante tiempo, pero en Esto podría ser manejado por la manipulación interna de la cadena. En el primer caso, la cadena original se divide por un terminador interno. En el segundo caso, se supone que la cadena siempre contiene pares de dígitos separados por un separador de un carácter.

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}

El resultado en todos los casos es

112312
Verdad
fuente
Creo que se suponía que el resultado final eran las referencias de variables de la matriz awk, independientemente del ejemplo de salida de impresión dado. Pero te perdiste un caso bash realmente fácil para proporcionar tu resultado final. T = '12: 23: 11 '; echo $ {T //:}
Daniel Liston el
@DanielListon ¡Tienes razón! ¡Gracias! No sabía que el final / se puede dejar en esta bashexpresión ...
Verdaderamente
4

En realidad, awktiene una función llamada enlace 'Variable del separador de campo de entrada' . Así es como se usa. No es realmente una matriz, pero usa las variables internas $. Para dividir una cadena simple es más fácil.

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

Deberia trabajar.

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

¿Broma? :)

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

Esta es mi salida:

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

así que supongo que está funcionando después de todo ...

duedl0r
fuente
¿Es por la longitud de la cuerda? desde entonces, mi longitud de cadena es de 4000. cualquier idea
Mohamed Saligh
1

Sé que esta es una pregunta vieja, pero pensé que tal vez alguien como mi truco. Especialmente porque esta solución no se limita a un número específico de elementos.

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

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

La salida será:

Item: 12
Item: 23
Item: 11
Qorbani
fuente