web-dev-qa-db-fra.com

combiner les fichiers texte en colonnes

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 en un seul fichier par colonne. J'ai donc essayé paste 1 2 et sa sortie est:

Languages   Minimal automaton
Recursively enumerable  Turing machine
Regular Finite

Cependant, j'aimerais bien aligner les colonnes telles que

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 Bruce le cloue presque, sauf 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)
54
Tim

Vous avez juste besoin de la commande column , et dites-lui d'utiliser des tabulations pour séparer les colonnes

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

Pour résoudre la controverse sur les "cellules vides", nous avons juste besoin du -n option pour 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

La page de manuel de ma colonne indique -n est une "extension Debian GNU/Linux". Mon système Fedora ne présente pas le problème des cellules vides: il semble être dérivé de BSD et la page de manuel indique "La version 2.23 a changé l'option -s pour qu'elle ne soit pas gourmande"

71
glenn jackman

Vous recherchez la commande pratique dandy pr:

paste file1 file2 | pr -t -e24

Le "-e24" est "développer les taquets de tabulation à 24 espaces". Heureusement, paste place un caractère de tabulation entre les colonnes, afin que pr puisse le développer. J'ai choisi 24 en comptant les caractères dans "Récursivement énumérable" et en ajoutant 2.

12
Bruce Ediger

pdate: Voici ia un script beaucoup plus simple (celui à la fin de la question) pour la sortie tabulée. Il suffit de lui passer le nom du fichier comme vous le feriez pour paste... Il utilise html pour créer le cadre, il est donc modifiable. Il conserve plusieurs espaces et l'alignement des colonnes est préservé lorsqu'il rencontre des caractères unicode. Cependant, la façon dont l'éditeur ou la visionneuse rend l'unicode est une tout autre affaire ...

┌──────────────────────┬────────────────┬──────────┬────────────────────────────┐
│ 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'

---

n synopsis des outils présenté dans les réponses (jusqu'à présent).
Je les ai regardés de très 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 ... Bon! # Il délimite chaque colonne avec un onglet ... Bon. # 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 des champs se fait uniquement par colonnes qu'il semble assez bien gérer .. Je n'ai rien repéré 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, il est donc imprévisible au-delà de 2 colonnes # L'alignement des colonnes n'est pas précis lors de la manipulation d'unicode, et il supprime le délimiteur de tabulation, donc l'identification des champs se fait uniquement par alignement des colonnes

pr # N'a qu'un seul onglet, il est donc imprévisible au-delà de 2 colonnes. # L'alignement des colonnes n'est pas précis lors de la manipulation d'unicode, et il supprime le délimiteur de tabulation, donc l'identification des champs se fait uniquement par alignement des colonnes

Pour moi, column c'est la meilleure solution évidente en tant que doublure .. Si vous voulez soit le délimiteur, soit une tabulation ASCII de vos fichiers, lisez la suite, sinon .. columns est sacrément bon:) ...


Voici un script qui prend n'importe quel nombre de fichiers et crée une présentation tabulée de type ASCII. (Gardez à l'esprit que l'unicode peut ne pas être rendu à la largeur attendue, par exemple. les chiffres étant erronés, comme c'est le cas 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ée un peu au lieu du script ci-dessus)

Utiliser wc pour obtenir la largeur de la colonne et sed pour le pavé droit avec un caractère visible. (juste pour cet exemple) ... puis paste pour joindre les deux colonnes avec un Tab char ...

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 souhaitez 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...........
9
Peter.O

Tu y es presque. paste met un caractère de tabulation entre chaque colonne, donc tout ce que vous avez à faire est de développer les tabulations. (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) GNU utilities, wc -L Montre la longueur de la ligne la plus longue. Sur d'autres systèmes, faites une première passe avec awk. Le +1 Est la quantité d'espace vide que vous souhaitez 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 disposez de l'utilitaire de colonne BSD, vous pouvez l'utiliser pour déterminer la largeur de colonne et développer les onglets en une seule fois. ( Est un caractère d'onglet littéral; sous bash/ksh/zsh, vous pouvez utiliser $'\t' À la place, et dans n'importe quel shell, vous pouvez utiliser "$(printf '\t')".)

paste left.txt right.txt | column -s '␉' -t

Ceci est en plusieurs étapes, donc ce n'est pas optimal, mais voilà.

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

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

Avec votre exemple, la ligne la plus longue est 22.

2) Utilisez awk pour remplir file1.txt, en remplissant chaque ligne de moins de 22 caractères jusqu'à 22 avec l'instruction printf.

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 de la pâte comme vous l'avez fait auparavant.

$ 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.

4
bahamat

Je ne peux pas commenter la réponse de Glenn Jackman, alors j'ajoute ceci pour résoudre le problème des cellules vides que Peter.O a noté. L'ajout d'un caractère nul avant chaque onglet élimine les exécutions de délimiteurs qui sont traités comme une seule interruption et résout le problème. (J'ai utilisé à l'origine des espaces, mais l'utilisation du caractère nul élimine 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 cause 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

sed et column semblent varier dans l'implémentation entre les versions et versions d'Unix/Linux, en particulier BSD (et Mac OS X) vs GNU/Linux.

4
techno

En s'appuyant sur réponse de bahamat : cela peut être fait entièrement dans awk, en ne lisant les fichiers qu'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 avec de nombreux scripts awk de cet acabit, ce qui précède lit d'abord file1, en enregistrant toutes les données dans le tableau save et en calculant simultanément la longueur de ligne maximale. Ensuite, il lit file2 et imprime le fichier enregistré (file1) côte à côte avec les données actuelles (file2) Les données. Enfin, si file1 est plus long que file2 (a plus de lignes), nous imprimons les dernières lignes de file1 (ceux pour lesquels il n'y a pas de ligne correspondante dans la deuxième colonne).

Concernant le format printf:

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

Le script ci-dessus ne fonctionne que pour deux fichiers. Il peut être trivialement modifié pour gérer trois fichiers, ou pour gérer quatre fichiers, etc., mais ce serait fastidieux et est laissé comme un exercice. Cependant, il s'avère qu'il n'est pas difficile de le modifier pour gérer n'importe quel nombre sur 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

  • Ça tourne max_length dans un tableau.
  • Ça tourne max_FNR dans un tableau.
  • Il transforme save en un tableau à deux dimensions.
  • Il lit tous les fichiers, enregistrant tous le contenu. Ensuite, il écrit tous la sortie du bloc END.