Comment traiter un fichier texte multi-colonnes pour obtenir un autre fichier texte multi-colonnes?

17

J'ai un fichier texte:

a   aa  aaa     b   bb  bbb     c   cc  ccc
d   dd  ddd     e   ee  eee     f   ff  fff
g   gg  ggg     h   hh  hhh     i   ii  iii
j   jj  jjj

Comment puis-je le traiter et obtenir un fichier à 2 colonnes comme celui-ci:

a   aa
aaa b
bb  bbb
c   cc
ccc d
dd  ddd
e   ee
eee f
ff  fff
g   gg
ggg h
hh  hhh
i   ii
iii j
jj  jjj

Ou un fichier à trois colonnes comme celui-ci:

a   aa  aaa
b   bb  bbb
c   cc  ccc
d   dd  ddd
e   ee  eee
f   ff  fff
g   gg  ggg
h   hh  hhh
i   ii  iii
j   jj  jj

Je préfère obtenir une solution awk mais d'autres solutions sont également les bienvenues.

Juste un apprenant
la source

Réponses:

1

Vous pouvez également le faire avec une seule invocation de GNU awk:

reshape.awk

# Set awk to split input at whitespace characters and
# use tab as the output field separator 
BEGIN {
  RS="[ \t\n]+"
  OFS="\t"
}

# Print using OFS or ORS based on the element index
{
  printf "%s", $1 (NR%n == 0 ? ORS : OFS)
}

# Append a missing new-line when last row is not full
END { 
  if( NR%n != 0) 
    printf "\n"
}

Exécutez-le comme ceci:

awk -f reshape.awk n=2 infile

Ou en monoplace:

awk -v n=2 'BEGIN { RS="[ \t\n]+"; OFS="\t" } { printf "%s", $1 (NR%n == 0 ? ORS : OFS) } END { if( NR%n != 0) printf "\n" }' infile

Production:

a   aa
aaa b
bb  bbb
c   cc
ccc d
dd  ddd
e   ee
eee f
ff  fff
g   gg
ggg h
hh  hhh
i   ii
iii j
jj  jjj

Ou avec n=3:

a   aa  aaa
b   bb  bbb
c   cc  ccc
d   dd  ddd
e   ee  eee
f   ff  fff
g   gg  ggg
h   hh  hhh
i   ii  iii
j   jj  jjj
Thor
la source
N'est-ce pas utilisé $1comme chaîne de formatageprintf ?
Wildcard
@Wildcard: Oui, c'est plus sûr à utiliser "%s", .... Mis à jour
Thor
Merci d'avoir confirmé. :) La même chose s'applique à la awkcommande dans votre autre réponse à cette question, soit dit en passant.
Wildcard
20

Mettez chaque champ sur une ligne et post-éditez.

Chaque champ sur une ligne

tr

tr -s ' ' '\n' < infile

grep

grep -o '[[:alnum:]]*' infile

sed

sed 's/\s\+/\n/g' infile

ou plus portable:

sed 's/\s\+/\
/g' infile

awk

awk '$1=$1' OFS='\n' infile

ou

awk -v OFS='\n' '$1=$1' infile

Columnate

pâte

Pour 2 colonnes:

... | paste - -

Pour 3 colonnes:

... | paste - - -

etc.

sed

Pour 2 colonnes:

... | sed 'N; s/\n/\t/g'

Pour 3 colonnes:

... | sed 'N; N; s/\n/\t/g'

etc.

xargs

... | xargs -n number-of-desired-columns

En tant xargsqu'utilisations /bin/echopour l'impression, sachez que les données qui ressemblent à des options echoseront interprétées comme telles.

awk

... | awk '{ printf "%s", $0 (NR%n==0?ORS:OFS) }' n=number-of-desired-columns OFS='\t'

pr

... | pr -at -number-of-desired-columns

ou

... | pr -at -s$'\t' -number-of-desired-columns

colonnes (du package autogen)

... | columns -c number-of-desired-columns

Sortie typique:

a   aa  aaa
b   bb  bbb
c   cc  ccc
d   dd  ddd
e   ee  eee
f   ff  fff
g   gg  ggg
h   hh  hhh
i   ii  iii
j   jj  jjj
Thor
la source
2
Slam dunk. +1 monsieur
Steven Penny
La xargsligne ne devrait-elle pas appeler echoou printf?
Wildcard
1
@Wildcard: xargsappels /bin/echopar défaut
Thor
1
Wow, je n'en avais aucune idée! Il est même spécifié par POSIX . Merci!
Wildcard
@Wildcard: L'envoi de données à xargsqui ressemble à des options /bin/echopose des problèmes ... J'ai ajouté un avertissement.
Thor
9
$ sed -E 's/\s+/\n/g' ip.txt | paste - -
a   aa
aaa b
bb  bbb
c   cc
ccc d
dd  ddd
e   ee
eee f
ff  fff
g   gg
ggg h
hh  hhh
i   ii
iii j
jj  jjj

$ sed -E 's/\s+/\n/g' ip.txt | paste - - -
a   aa  aaa
b   bb  bbb
c   cc  ccc
d   dd  ddd
e   ee  eee
f   ff  fff
g   gg  ggg
h   hh  hhh
i   ii  iii
j   jj  jjj
Sundeep
la source
9

Comme l'a souligné Wildcard, cela ne fonctionnera que si votre fichier est bien formaté, en ce sens qu'il n'y a pas de caractères spéciaux que le shell interprétera comme des globes et que vous êtes satisfait des règles de division des mots par défaut. Si vous ne savez pas si vos fichiers "réussiront" ce test, n'utilisez pas cette approche.

Une possibilité serait d'utiliser printfpour le faire comme

printf '%s\t%s\n' $(cat your_file)

Cela divisera les mots sur le contenu de your_fileet les appairera et les imprimera avec des onglets entre les deux. Vous pouvez utiliser plus de %schaînes de format dans le printfpour avoir des colonnes supplémentaires.

Eric Renouf
la source
1
Cela dépend du fichier ne contenant aucun caractère spécial. S'il contient, par exemple, des astérisques (*), vous obtiendrez des résultats très inattendus.
Wildcard
4
perl -n0E 'say s/\s+/ ++$n % 4 ?"\t":"\n"/gre' file

(remplacer 4 par le nombre de colonnes)

JJoao
la source
4

rsUtilitaire BSD (remodeler):

$ rs 0 2
a   aa  aaa     b   bb  bbb     c   cc  ccc
d   dd  ddd     e   ee  eee     f   ff  fff
g   gg  ggg     h   hh  hhh     i   ii  iii
j   jj  jjj
[Ctrl-D][Enter]
a    aa
aaa  b
bb   bbb
c    cc
ccc  d
dd   ddd
e    ee
eee  f
ff   fff
g    gg
ggg  h
hh   hhh
i    ii
iii  j
jj   jjj

0 2est des lignes et des colonnes . La spécification 0signifie "calculer automatiquement les lignes à partir des colonnes".

Kaz
la source
3

Approche par script Python.

L'idée de base ici est d'aplatir tous les mots de votre texte en une seule liste, puis d'imprimer une nouvelle ligne après chaque deuxième élément (c'est-à-dire pour la colonne en deux colonnes). Si vous voulez 3 colonnes, passez index%2àindex%3

#!/usr/bin/env python3
import sys

items = [i for l in sys.stdin 
           for i in l.strip().split()]
line = []
for index,item in enumerate(items,1):
    line.append(item)
    if index%2 == 0:
       print("\t".join(line))
       line = []

Exemple de sortie:

$ python recolumnate.py < input.txt                                            
a   aa
aaa b
bb  bbb
c   cc
ccc d
dd  ddd
e   ee
eee f
ff  fff
g   gg
ggg h
hh  hhh
i   ii
iii j
jj  jjj

Version à trois colonnes (comme indiqué ci-dessus, seulement index%3 == 0modifiée)

$ cat recolumnate.py                                                           
#!/usr/bin/env python3
import sys

items = [i for l in sys.stdin 
           for i in l.strip().split()]
line = []
for index,item in enumerate(items,1):
    line.append(item)
    if index%3 == 0:
       print("\t".join(line))
       line = []

$ python recolumnate.py < input.txt                                            
a   aa  aaa
b   bb  bbb
c   cc  ccc
d   dd  ddd
e   ee  eee
f   ff  fff
g   gg  ggg
h   hh  hhh
i   ii  iii
j   jj  jjj
Sergiy Kolodyazhnyy
la source