Comment diviser une chaîne délimitée en un tableau dans awk?

169

Comment diviser la chaîne lorsqu'elle contient des symboles de tuyau |. Je veux les diviser pour être en tableau.

j'ai essayé

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

Ce qui fonctionne bien. Si ma chaîne est comme, "12|23|11"comment puis-je les diviser en un tableau?

Mohamed Saligh
la source
3
Notez que votre sortie concatène les éléments du tableau, sans séparateur. Si vous souhaitez plutôt qu'ils soient séparés par OFS, placez des virgules entre eux, pour les printvoir comme des arguments séparés.
dubiousjim
Ou vous pouvez utiliser sed:echo "12:23:11" | sed "s/.*://"
slushy
@slushy: votre commande n'est pas du tout ce dont le demandeur a besoin. votre commande ( echo "12:23:11" | sed "s/.*://") supprime tout jusqu'au (et y compris) le dernier ":", en ne gardant que le "11" ... cela fonctionne pour obtenir le dernier nombre, mais aurait besoin d'être modifié (d'une manière difficile à lire) pour obtenir le 2ème numéro, etc. awk (et le split de awk) est beaucoup plus élégant et lisible.
Olivier Dulac
si vous avez besoin de diviser sur un seul caractère, vous pouvez utilisercut
ccpizza

Réponses:

274

As-tu essayé:

echo "12|23|11" | awk '{split($0,a,"|"); print a[3],a[2],a[1]}'
Calin Paul Alexandru
la source
2
@Mohamed Saligh, si vous êtes sous Solaris, vous devez utiliser / usr / xpg4 / bin / awk , étant donné la longueur de la chaîne.
Dimitre Radoulov
5
«ne fonctionne pas pour moi». en particulier avec les deux-points entre les valeurs en écho et le fractionnement configuré pour fractionner sur '|' ??? Faute de frappe? Bonne chance à tous.
shellter
1
Mieux avec une explication de la syntaxe.
Alston
2
Cela ne fonctionnera pas dans GNU awk, car le troisième argument de splitest une expression régulière, et |est un symbole spécial, qui doit être échappé. Utilisationsplit($0, a, "\|")
WhiteWind
1
@WhiteWind: une autre façon de "s'assurer" qui |est vu comme un caractère et non comme un symbole spécial est de le mettre entre []: c'est-à-dire split($0, a, "[|]") # j'aime mieux cela que '\ |', dans certains cas, en particulier comme une variante de l'expression rationnelle ( perl vs grep vs .. autres?) peut avoir "|" interprété littéralement et "\ |" vu comme séparateur de regex, au lieu de l'inverse ... ymmv
Olivier Dulac
119

Pour diviser une chaîne en un tableau, awknous utilisons la fonction split():

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

Si aucun séparateur n'est donné, il utilise le FS, qui par défaut est l'espace:

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

On peut donner un séparateur, par exemple ::

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

Ce qui équivaut à le définir via FS:

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

Dans gawk, vous pouvez également fournir le séparateur en tant qu'expression régulière:

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

Et même voir quel était le délimiteur à chaque étape en utilisant son quatrième paramètre:

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

Citons la page de manuel de GNU awk :

split (chaîne, tableau [, fieldsep [, seps]])

Divisez la chaîne en morceaux séparés par fieldsep et stockez les morceaux dans le tableau et les chaînes de séparation dans le tableau seps . La première pièce est stockée array[1], la deuxième pièce array[2], et ainsi de suite. La valeur de chaîne du troisième argument, fieldsep , est une expression rationnelle décrivant où diviser la chaîne (tout comme FS peut être une expression rationnelle décrivant où diviser les enregistrements d'entrée). Si fieldsep est omis, la valeur de FS est utilisée. split()renvoie le nombre d'éléments créés. seps est une gawkextension, seps[i]étant la chaîne de séparation entrearray[i]et array[i+1]. Si fieldsep est un espace unique, alors tout espace blanc de début entre dans seps[0]et tout espace blanc de fin entre seps[n], où n est la valeur de retour de split()(c'est-à-dire le nombre d'éléments dans le tableau).

fedorqui 'Alors arrête de nuire
la source
mentionnez simplement que vous utilisez gnu awk, pas awk régulier (qui ne stocke pas les séparateurs dans seps [], et a d'autres limitations)
Olivier Dulac
17

Soyez plus précis! Qu'entendez-vous par «ça ne marche pas»? Publiez la sortie exacte (ou le message d'erreur), votre système d'exploitation et la version awk:

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

Ou, en utilisant Split:

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

Edit: sous Solaris, vous devrez utiliser le awk POSIX ( / usr / xpg4 / bin / awk ) afin de traiter correctement 4000 champs.

Dimitre Radoulov
la source
for(i = 0ou for(i = 1?
PiotrNycz
i = 0, car j'utilise ++ i après (pas i ++).
Dimitre Radoulov
3
Ok - je n'ai pas remarqué cela. Je crois fermement que plus lisible serait for (i = 1; i <= n; ++i)...
PiotrNycz
5

Je n'aime pas la echo "..." | awk ...solution car elle appelle des appels inutiles forket execsystème.

Je préfère une solution Dimitre avec une petite touche

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

Ou une version un peu plus courte:

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

Dans ce cas, l'enregistrement de sortie est assemblé, ce qui est une condition vraie, il est donc imprimé.

Dans ce cas précis, la stdinredirection peut être épargnée en définissant un variable interne:

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

j'ai utilisé pas mal de temps, mais dans cela pourrait être géré par une manipulation de chaîne interne. Dans le premier cas, la chaîne d'origine est divisée par un terminateur interne. Dans le second cas, on suppose que la chaîne contient toujours des paires de chiffres séparées par un séparateur à un caractère.

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}

Le résultat dans tous les cas est

112312
VraiY
la source
Je pense que le résultat final était censé être les références de variable du tableau awk, quel que soit l'exemple de sortie d'impression donné. Mais vous avez manqué un cas de bash vraiment facile pour fournir votre résultat final. T = '12: 23: 11 '; echo $ {T //:}
Daniel List le
@DanielListon Vous avez raison! Merci! Je ne savais pas que le trailing / peut être laissé dans cette bashexpression ...
TrueY
4

Dispose en awkfait d'une fonction appelée lien «Variable de séparation de champ d'entrée» . Voici comment l'utiliser. Ce n'est pas vraiment un tableau, mais il utilise les variables internes $. Pour diviser une simple chaîne, c'est plus facile.

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

devrait marcher.

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

Blague? :)

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

Voici ma sortie:

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

donc je suppose que ça marche après tout ..

duedl0r
la source
est-ce à cause de la longueur de la chaîne? depuis, ma longueur de chaîne est de 4000. toutes les idées
Mohamed Saligh
1

Je sais que c'est un peu une vieille question, mais j'ai pensé que peut-être quelqu'un comme mon truc. D'autant que cette solution ne se limite pas à un nombre spécifique d'articles.

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

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

La sortie sera:

Item: 12
Item: 23
Item: 11
Qorbani
la source