combiner des fichiers texte par colonne

52

J'ai deux fichiers texte. Le premier a un contenu:

Languages
Recursively enumerable
Regular

tandis que le second a du contenu:

Minimal automaton
Turing machine
Finite

Je veux les combiner dans un fichier par colonne. Alors j'ai essayé paste 1 2et sa sortie est:

Languages   Minimal automaton
Recursively enumerable  Turing machine
Regular Finite

Cependant, j'aimerais bien que les colonnes soient bien alignées, comme

Languages               Minimal automaton
Recursively enumerable  Turing machine
Regular                 Finite

Je me demandais s'il serait possible d'y parvenir sans manipulation manuelle?


Ajoutée:

Voici un autre exemple, où la méthode de Bruce la cloue presque, à l'exception d'un léger désalignement sur lequel je me demande pourquoi?

$ cat 1
Chomsky hierarchy
Type-0
—

$ cat 2
Grammars
Unrestricted

$ paste 1 2 | pr -t -e20
Chomsky hierarchy   Grammars
Type-0              Unrestricted
—                    (no common name)
Tim
la source
3
Ce dernier exemple, avec un désalignement, est un doozy. Je peux le dupliquer sur Arch linux, pr (GNU coreutils) 8.12. Je ne peux pas le dupliquer sur une personne âgée de Slackware (11.0). J'ai également environ: pr (GNU coreutils) 5.97. Le problème est avec le caractère '-', et c'est dans pr, pas coller.
Bruce Ediger
1
J'obtiens la même chose avec EM-DASH avec les deux pret expand... columnsévite ce problème.
Peter.O
J'ai produit une sortie pour la plupart des réponses différentes, à l'exception de awk + paste , qui décale à gauche les colonnes les plus à droite si un fichier de gauche est plus court que tout autre à droite. La même chose, et bien plus, s'applique à 'coller + colonne' qui a également ce problème avec les lignes vides dans la (les) colonne (s) de gauche ... Si vous voulez voir toutes les sorties ensemble. voici le lien: paste.ubuntu.com/643692 J'ai utilisé 4 colonnes.
Peter.O
Je viens de remarquer quelque chose de trompeur sur le lien paste.ubuntu ... J'ai initialement configuré les données pour tester mes scripts (et cela a conduit à faire les autres) ... donc les champs qui disent ➀ unicode may render oddly but the column count is ok définitivement ne s'appliquent pas à wc-paste-pret wc-paste-prils affichez les différences de nombre de colonnes .. Les autres sont ok.
Peter.O
1
@BruceEdiger: le problème d'alignement se produit lorsque des caractères non-ASCII sont utilisés (dans sa question, l'OP utilisait un tiret (-) au lieu d'un caractère moins (-)), probablement en raison d'une manipulation incorrecte ou inexistante prdu multi-octet caractères dans les paramètres régionaux actuels (généralement UTF8).
WhiteWinterWolf

Réponses:

68

Vous avez juste besoin de la columncommande et lui dire d'utiliser des tabulations pour séparer les colonnes

paste file1 file2 | column -s $'\t' -t

Pour résoudre le problème de la "cellule vide", nous avons simplement besoin de l' -noption suivante column:

$ paste <(echo foo; echo; echo barbarbar) <(seq 3) | column -s $'\t' -t
foo        1
2
barbarbar  3

$ paste <(echo foo; echo; echo barbarbar) <(seq 3) | column -s $'\t' -tn
foo        1
           2
barbarbar  3

Ma page de manuel de colonne indique -nune "extension Debian GNU / Linux". Mon système Fedora ne présente pas le problème des cellules vides: il semble provenir de BSD et la page de manuel indique "La version 2.23 a modifié l'option -s pour qu'elle ne soit pas gourmande"

Glenn Jackman
la source
4
Glenn: Vous êtes le héros de l'heure! Je savais qu'il y avait quelque chose comme ça autour, mais je ne pouvais pas m'en souvenir. Je me suis caché sur cette question; en attendant vous :) ... column, bien sûr; comment évident (avec le recul) +1 ... Merci ...
Peter.O
4
Je viens de remarquer que column -s $'\t' -tles cellules vides sont ignorées , toutes les cellules suivantes situées à droite de celle-ci (sur cette ligne) étant déplacées vers la gauche; c'est-à-dire à la suite d'une ligne blanche dans un fichier ou
parce
1
@masi, corrigé
glenn jackman
-n ne fonctionne pas dans RHEL. Y a-t-il une alternative?
Koshur
Je peux enfin commenter, alors je tiens à noter que j’ai précédemment ajouté une réponse ci-dessous qui résout le problème de Peter.O avec des exécutions de cellules vides à l’aide de NULL.
techno
11

Vous recherchez la prcommande pratique de dandy :

paste file1 file2 | pr -t -e24

Le "-e24" est "L'onglet Développer s'arrête à 24 espaces". Heureusement, pasteplace un caractère de tabulation entre les colonnes pour prpouvoir le développer. J'ai choisi 24 en comptant les caractères dans "Récursivement énumérable" et en ajoutant 2.

Bruce Ediger
la source
Merci! Que signifie "élargir l'onglet s'arrête à 24 espaces"?
Tim
Je mets également à jour avec un exemple où votre méthode clou presque presque sauf un léger désalignement.
Tim
Traditionnellement, les "tabstops" frappent tous les 8 espaces. "123TABABC" serait imprimé avec le caractère 'a' à 8 largeurs de caractère à partir du début de la ligne. Si vous définissez cette valeur sur 24, le "a" aurait une largeur de 24 caractères à partir du début de la ligne.
Bruce Ediger
Vous dites que le « -e24 » est « étendre à 24 arrêts de tabulation espaces » , alors pourquoi ne pas utiliser la expandcommande directement: paste file1 file2 | expand -t 24?
WhiteWinterWolf
1
@Masi - ma réponse est similaire mais moins compliquée que celle de @ techno ci-dessous. Il n'invoque pas, sedalors il y a un processus qui ne fonctionne pas. Il utilise prune commande ancienne datant des jours Unix SysV, je pense, donc il pourrait exister sur plus d’installations que expand. C'est juste la vieille école, en bref.
Bruce Ediger
9

Mise à jour : Voici un script beaucoup plus simple (celui à la fin de la question) pour la sortie sous forme de tableau. Il suffit de lui transmettre le nom du fichier comme vous le feriez pour paste... Il utilise htmlpour créer le cadre, il est donc ajustable. Il conserve plusieurs espaces et l'alignement des colonnes est préservé lorsqu'il rencontre des caractères unicode. Cependant, la manière dont l'éditeur ou le visualiseur rend l'unicode est un tout autre problème ...

┌──────────────────────┬────────────────┬──────────┬────────────────────────────┐
│ Languages            │ Minimal        │ Chomsky  │ Unrestricted               │
├──────────────────────┼────────────────┼──────────┼────────────────────────────┤
│ Recursive            │ Turing machine │ Finite   │     space indented         │
├──────────────────────┼────────────────┼──────────┼────────────────────────────┤
│ Regular              │ Grammars       │          │ ➀ unicode may render oddly │
├──────────────────────┼────────────────┼──────────┼────────────────────────────┤
│ 1 2  3   4    spaces │                │ Symbol-& │ but the column count is ok │
├──────────────────────┼────────────────┼──────────┼────────────────────────────┤
│                      │                │          │ Context                    │
└──────────────────────┴────────────────┴──────────┴────────────────────────────┘

#!/bin/bash
{ echo -e "<html>\n<table border=1 cellpadding=0 cellspacing=0>"
  paste "$@" |sed -re 's#(.*)#\x09\1\x09#' -e 's#\x09# </pre></td>\n<td><pre> #g' -e 's#^ </pre></td>#<tr>#' -e 's#\n<td><pre> $#\n</tr>#'
  echo -e "</table>\n</html>"
} |w3m -dump -T 'text/html'

---

Un résumé des outils présentés dans les réponses (jusqu'à présent).
Je les ai regardées de près. voici ce que j'ai trouvé:

paste# Cet outil est commun à toutes les réponses présentées jusqu'à présent. # Il peut gérer plusieurs fichiers. donc plusieurs colonnes ... Bien! # Il délimite chaque colonne avec un onglet ... Bien. # Sa sortie n'est pas tabulée.

Tous les outils ci-dessous suppriment tous ce délimiteur! ... Mauvais si vous avez besoin d'un délimiteur.

column # Il supprime le délimiteur de tabulation, donc l'identification de champ est purement basée sur des colonnes qu'il semble gérer assez bien .. Je n'ai rien vu de mal ... # En plus de ne pas avoir de délimiteur unique, cela fonctionne bien!

expand # N'a qu'un seul paramètre de tabulation, de sorte qu'il est imprévisible au-delà de 2 colonnes. # L'alignement des colonnes n'est pas précis lors de la gestion d'unicode et il supprime le délimiteur de tabulation. L'identification de champ est donc purement par alignement de colonne.

pr# Ne comporte qu'un seul paramètre de tabulation, il est donc imprévisible au-delà de 2 colonnes. # L'alignement des colonnes n'est pas précis lors de la gestion de l'unicode et supprime le délimiteur de tabulation. L'identification du champ se fait donc uniquement par alignement de colonne.

Pour moi, columnc’est la meilleure solution évidente en tant que one-liner .. Si vous voulez soit le délimiteur, soit une tabluation ASCII-art de vos fichiers, lisez la suite, sinon ... columnsc’est sacrément bon:) ...


Voici un script qui prend n’importe quel nombre de fichiers et crée une présentation sous forme de tableau ASCII-art. (Notez que l’unicode peut ne pas restituer la largeur attendue, par exemple. ௵, qui est un seul caractère. Ceci est très différent de la colonne. les nombres étant incorrects, comme dans certains des utilitaires mentionnés ci-dessus.) ... La sortie du script, illustrée ci-dessous, provient de 4 fichiers d'entrée, nommés F1 F2 F3 F4 ...

+------------------------+-------------------+-------------------+--------------+
| Languages              | Minimal automaton | Chomsky hierarchy | Grammars     |
| Recursively enumerable | Turing machine    | Type-0            | Unrestricted |
| Regular                | Finite            | —                 |              |
| Alphabet               |                   | Symbol            |              |
|                        |                   |                   | Context      |
+------------------------+-------------------+-------------------+--------------+

#!/bin/bash

# Note: The next line is for testing purposes only!
set F1 F2 F3 F4 # Simulate commandline filename args $1 $2 etc...

p=' '                                # The pad character
# Get line and column stats
cc=${#@}; lmax=                      # Count of columns (== input files)
for c in $(seq 1 $cc) ;do            # Filenames from the commandline 
  F[$c]="${!c}"        
  wc=($(wc -l -L <${F[$c]}))         # File length and width of longest line 
  l[$c]=${wc[0]}                     # File length  (per file)
  L[$c]=${wc[1]}                     # Longest line (per file) 
  ((lmax<${l[$c]})) && lmax=${l[$c]} # Length of longest file
done
# Determine line-count deficits  of shorter files
for c in $(seq 1 $cc) ;do  
  ((${l[$c]}<lmax)) && D[$c]=$((lmax-${l[$c]})) || D[$c]=0 
done
# Build '\n' strings to cater for short-file deficits
for c in $(seq 1 $cc) ;do
  for n in $(seq 1 ${D[$c]}) ;do
    N[$c]=${N[$c]}$'\n'
  done
done
# Build the command to suit the number of input files
source=$(mktemp)
>"$source" echo 'paste \'
for c in $(seq 1 $cc) ;do
    ((${L[$c]}==0)) && e="x" || e=":a -e \"s/^.{0,$((${L[$c]}-1))}$/&$p/;ta\""
    >>"$source" echo '<(sed -re '"$e"' <(cat "${F['$c']}"; echo -n "${N['$c']}")) \'
done
# include the ASCII-art Table framework
>>"$source" echo ' | sed  -e "s/.*/| & |/" -e "s/\t/ | /g" \'   # Add vertical frame lines
>>"$source" echo ' | sed -re "1 {h;s/[^|]/-/g;s/\|/+/g;p;g}" \' # Add top and botom frame lines 
>>"$source" echo '        -e "$ {p;s/[^|]/-/g;s/\|/+/g}"'
>>"$source" echo  
# Run the code
source "$source"
rm     "$source"
exit

Voici ma réponse originale (coupé un peu au lieu du script ci-dessus)

Utiliser wcpour obtenir la largeur de colonne, et sedpour pad droit avec un caractère visible. (juste pour cet exemple) ... et ensuite pastepour joindre les deux colonnes avec un caractère de tabulation ...

paste <(sed -re :a -e 's/^.{1,'"$(($(wc -L <F1)-1))"'}$/&./;ta' F1) F2

# output (No trailing whitespace)
Languages.............  Minimal automaton
Recursively enumerable  Turing machine
Regular...............  Finite

Si vous voulez remplir la colonne de droite:

paste <( sed -re :a -e 's/^.{1,'"$(($(wc -L <F1)-1))"'}$/&./;ta' F1 ) \
      <( sed -re :a -e 's/^.{1,'"$(($(wc -L <F2)-1))"'}$/&./;ta' F2 )  

# output (With trailing whitespace)
Languages.............  Minimal automaton
Recursively enumerable  Turing machine...
Regular...............  Finite...........
Peter.O
la source
Merci! Vous avez fait beaucoup de travail. C'est incroyable.
Tim
5

Tu y es presque. pasteplace un caractère de tabulation entre chaque colonne, il suffit donc de développer les onglets. (Je suppose que vos fichiers ne contiennent pas d'onglets.) Vous devez déterminer la largeur de la colonne de gauche. Avec (assez récent) utilitaires GNU, wc -Laffiche la longueur de la plus longue ligne. Sur d’autres systèmes, faites un premier passage avec awk. La +1est la quantité d'espace vide que vous voulez entre les colonnes.

paste left.txt right.txt | expand -t $(($(wc -L <left.txt) + 1))
paste left.txt right.txt | expand -t $(awk 'n<length {n=length} END {print n+1}')

Si vous avez l'utilitaire de colonne BSD, vous pouvez l'utiliser pour déterminer la largeur de la colonne et développer les onglets en une fois. ( est un caractère de tabulation littéral; vous pouvez utiliser à la $'\t'place, sous bash / ksh / zsh , et n'importe quel shell, vous pouvez utiliser "$(printf '\t')".)

paste left.txt right.txt | column -s '␉' -t
Gilles, arrête de faire le mal
la source
Dans ma version wc, la commande doit être: wc -L <left.txt... parce que, quand un nom de fichier est spedified comme une ligne de commande arg , son nom est sortie stdout
Peter.O
4

Ceci est multi-étapes, donc ce n'est pas optimal, mais voilà.

1) Trouvez la longueur de la plus longue ligne dans file1.txt.

while read line
do
echo ${#line}
done < file1.txt | sort -n | tail -1

Avec votre exemple, la plus longue ligne est 22.

2) Utilisez awk pour pad file1.txt, en tapant chaque ligne de moins de 22 caractères jusqu'à 22 caractères avec l' printfinstruction.

awk 'FS="---" {printf "%-22s\n", $1}' < file1.txt > file1-pad.txt

Remarque: Pour FS, utilisez une chaîne qui n'existe pas dans file1.txt.

3) Utilisez la pâte comme avant.

$ paste file1-pad.txt file2.txt
Languages               Minimal automaton
Recursively enumerable  Turing machine
Regular                 Finite

Si c'est quelque chose que vous faites souvent, cela peut facilement être transformé en script.

bahamat
la source
Dans votre code, vous avez besoin de la ligne la plus longue while IFS= read -r line, sinon le shell modifiera les espaces et les barres obliques inverses. Mais le shell n'est pas le meilleur outil pour ce travail; les versions récentes de GNU coreutils ont wc -L(voir la réponse de fred), ou vous pouvez utiliser awk: awk 'n<length {n=length} END {print +n}'.
Gilles 'SO- arrête d'être méchant'
4

Je ne peux pas commenter la réponse de Glenn Jackman, je l'ajoute donc pour résoudre le problème des cellules vides noté par Peter.O. L'ajout d'un caractère nul avant chaque onglet élimine les exécutions de délimiteurs traités comme une simple rupture et résout le problème. (À l'origine, j'avais utilisé des espaces, mais l'utilisation du caractère nul éliminait l'espace supplémentaire entre les colonnes.)

paste file1 file2 | sed 's/\t/\0\t/g' | column -s $'\t' -t

Si le caractère nul provoque des problèmes pour diverses raisons, essayez soit:

paste file1 file2 | sed 's/\t/ \t/g' | column -s $'\t' -t

ou

paste file1 file2 | sed $'s/\t/ \t/g' | column -s $'\t' -t

Les deux sedet columnsemblent varier la mise en œuvre à travers les saveurs et les versions d'Unix / Linux, BSD en particulier (et Mac OS X) par rapport à GNU / Linux.

techno
la source
Cette commande sed semble ne rien faire. Je remplace la commande de colonne par od -cet je ne vois aucun octet nul. Ceci est sur centos et Ubuntu.
Glenn Jackman
1
Cela a fonctionné pour moi dans RedHat EL4. Sed et column semblent varier dans le temps et le système. Dans Ubuntu, l’utilisation de 14.4 \0ne fonctionnait pas en tant que telle null, mais elle le \x0faisait. Cependant, alors la colonne a donné une line too longerreur. La chose la plus simple semble être d'utiliser un espace et de vivre avec le personnage supplémentaire.
techno
0

S'appuyant sur la réponse de bahamat : cela peut être entièrement fait en awklisant les fichiers une seule fois et en ne créant aucun fichier temporaire. Pour résoudre le problème comme indiqué, faites

awk '
        NR==FNR { if (length > max_length) max_length = length
                  max_FNR = FNR
                  save[FNR] = $0
                  next
                }
                { printf "%-*s", max_length+2, save[FNR]
                  print
                }
        END     { if (FNR < max_FNR) {
                        for (i=FNR+1; i <= max_FNR; i++) print save[i]
                  }
                }
    '   file1 file2

Comme beaucoup de awkscripts de ce type, le premier est lu file1, en enregistrant toutes les données du savetableau et en calculant simultanément la longueur de ligne maximale. Ensuite, il lit file2 et affiche les file1données sauvegardées ( ) côte à côte avec les file2données actuelles ( ). Enfin, si file1est plus long que file2(a plus de lignes), nous imprimons les dernières lignes de file1 (celles pour lesquelles il n'y a pas de ligne correspondante dans la deuxième colonne).

En ce qui concerne le printfformat:

  • "%-nns"imprime une chaîne justifiée à gauche dans un champ de nncaractères large.
  • "%-*s", nnfait la même chose - le lui *dit de prendre la largeur du champ du paramètre suivant.
  • En utilisant pour , nous obtenons deux espaces entre les colonnes. Évidemment le peut être ajusté.maxlength+2nn+2

Le script ci-dessus ne fonctionne que pour deux fichiers. Il peut être modifié de manière triviale pour gérer trois fichiers, ou quatre, etc., mais cela serait fastidieux et laissé comme exercice. Cependant, il n’est pas difficile de le modifier pour gérer un nombre quelconque de fichiers:

awk '
        FNR==1  { file_num++ }
                { if (length > max_length[file_num]) max_length[file_num] = length
                  max_FNR[file_num] = FNR
                  save[file_num,FNR] = $0
                }
        END     { for (j=1; j<=file_num; j++) {
                        if (max_FNR[j] > global_max_FNR) global_max_FNR = max_FNR[j]
                  }
                  for (i=1; i<=global_max_FNR; i++) {
                        for (j=1; j<file_num; j++) printf "%-*s", max_length[j]+2, save[j,i]
                        print save[file_num,i]
                  }
                }
    '   file*

Ceci est très similaire à mon premier script, sauf

  • Cela se transforme max_lengthen tableau.
  • Cela se transforme max_FNRen tableau.
  • Il se transforme saveen un tableau à deux dimensions.
  • Il lit tous les fichiers, enregistre tout le contenu. Ensuite, il écrit toutes les sorties du ENDbloc.
G-Man dit 'Réintégrez Monica'
la source
Je sais que cette question est ancienne; Je suis juste tombé dessus. Je conviens que pastec'est la meilleure solution. en particulier, Glenn Jackman paste file1 file2 | column -s $'\t' -t. Mais j'ai pensé que ce serait amusant d'essayer d'améliorer l' awkapproche.
G-Man dit 'Réintégrez Monica'