Une meilleure commande de collage

11

J'ai les deux fichiers suivants (j'ai rempli les lignes avec des points pour que chaque ligne d'un fichier ait la même largeur et ai mis file1 en majuscules pour le rendre plus clair).

contents of file1:

ETIAM......
SED........
MAECENAS...
DONEC......
SUSPENDISSE

contents of file2

Lorem....
Proin....
Nunc.....
Quisque..
Aenean...
Nam......
Vivamus..
Curabitur
Nullam...

Notez que file2 est plus long que file1.

Lorsque j'exécute cette commande:

paste file1 file2

Je reçois cette sortie

ETIAM...... Lorem....
SED........ Proin....
MAECENAS... Nunc.....
DONEC...... Quisque..
SUSPENDISSE Aenean...
    Nam......
    Vivamus..
    Curabitur
    Nullam...

Que puis-je faire pour que la sortie soit la suivante?

ETIAM...... Lorem....
SED........ Proin....
MAECENAS... Nunc.....
DONEC...... Quisque..
SUSPENDISSE Aenean...
            Nam......
            Vivamus..
            Curabitur
            Nullam...

j'ai essayé

paste file1 file2 | column -t

mais il fait ceci:

ETIAM......  Lorem....
SED........  Proin....
MAECENAS...  Nunc.....
DONEC......  Quisque..
SUSPENDISSE  Aenean...
Nam......
Vivamus..
Curabitur
Nullam...

pas aussi moche que la sortie d'origine mais de toute façon incorrecte en colonne.

Tulains Córdova
la source
2
pasteutilise des tabulations devant les lignes du deuxième fichier. Vous devrez peut-être utiliser un post-processeur pour aligner les colonnes de manière appropriée.
unxnut
3
paste file1 file2 | column -tn?
ninjalj
file1 a-t-il toujours des colonnes de taille fixe?
RSFalcon7
@ RSFalcon7 Oui, c'est le cas.
Tulains Córdova

Réponses:

17

En supposant que vous n'ayez aucun caractère de tabulation dans vos fichiers,

paste file1 file2 | expand -t 13

l'argument étant -tchoisi de manière appropriée pour couvrir la largeur de ligne maximale souhaitée dans le fichier 1.

OP a ajouté une solution plus flexible:

Je l'ai fait pour que cela fonctionne sans le numéro magique 13:

paste file1 file2 | expand -t $(( $(wc -L <file1) + 2 ))

Ce n'est pas facile à taper mais peut être utilisé dans un script.

Mark Plotnick
la source
agréable! Je ne connaissais pas le développement avant de lire votre réponse :)
TabeaKischka
4

Je pensais que awk pourrait le faire bien, alors j'ai googlé "awk en lisant l'entrée de deux fichiers" et j'ai trouvé un article sur stackoverflow à utiliser comme point de départ.

Est d'abord la version condensée, puis entièrement commentée ci-dessous. Cela a pris plus de quelques minutes à travailler. Je serais ravi des améliorations apportées par des gens plus intelligents.

awk '{if(length($0)>max)max=length($0)}
FNR==NR{s1[FNR]=$0;next}{s2[FNR]=$0}
END { format = "%-" max "s\t%-" max "s\n";
  numlines=(NR-FNR)>FNR?NR-FNR:FNR;
  for (i=1; i<=numlines; i++) { printf format, s1[i]?s1[i]:"", s2[i]?s2[i]:"" }
}' file1 file2

Et voici la version entièrement documentée de ce qui précède.

# 2013-11-05 [email protected]
# Invoke thus:
#   awk -f this_file file1 file2
# The result is what you asked for and the columns will be
# determined by input file order.
#----------------------------------------------------------
# No matter which file we're reading,
# keep track of max line length for use
# in the printf format.
#
{ if ( length($0) > max ) max=length($0) }

# FNR is record number in current file
# NR is record number over all
# while they are equal, we're reading the first file
#   and we load the strings into array "s1"
#   and then go to the "next" line in the file we're reading.
FNR==NR { s1[FNR]=$0; next }

# and when they aren't, we're reading the
#   second file and we put the strings into
#   array s2
{s2[FNR]=$0}

# At the end, after all lines from both files have
# been read,
END {
  # use the max line length to create a printf format
  # the right widths
  format = "%-" max "s\t%-" max "s\n"
  # and figure the number of array elements we need
  # to cycle through in a for loop.
  numlines=(NR-FNR)>FNR?NR-FNR:FNR;
  for (i=1; i<=numlines; i++) {
     printf format, s1[i]?s1[i]:"", s2[i]?s2[i]:""
  }
}
Mike Diehn
la source
1
+1 c'est la seule réponse qui fonctionne avec une entrée arbitraire (c'est-à-dire avec des lignes pouvant contenir des tabulations). Je ne pense pas que cela pourrait être considérablement affiné / amélioré.
don_crissti
2

Pas une très bonne solution mais j'ai pu le faire en utilisant

paste file1 file2 | sed 's/^TAB/&&/'

où TAB est remplacé par le caractère de tabulation.

unnut
la source
Quel est le rôle de &&la commande sed?
coffeMug
1
Un seul &met ce qui est recherché (un onglet dans ce cas). Cette commande remplace simplement l'onglet au début par deux onglets.
unxnut
J'ai dû changer TABpour \tfaire ce travail dans zsh sur Debian Ubuntu. Et cela ne fonctionne que si file1 a moins de 15 caractères
rubo77
2

Sur Debian et dérivés, columna une option -n nomerge qui permet à la colonne de faire la bonne chose avec des champs vides. En interne, columnutilise la wcstok(wcs, delim, ptr)fonction, qui fractionne une chaîne de caractères larges en jetons délimités par les caractères larges dans l' delimargument.

wcstokcommence en sautant de larges caractères delim, avant de reconnaître le jeton. L' -noption utilise un algorithme qui n'ignore pas les caractères larges initiaux dans delim.

Malheureusement, ce n'est pas très portable: -nest spécifique à Debian, et columnn'est pas en POSIX, c'est apparemment une chose BSD.

ninjalj
la source
2

Supprimer les points que vous avez utilisés pour le rembourrage:

fichier1:

ETIAM
SED
MAECENAS
DONEC
SUSPENDISSE

fichier2:

Lorem
Proin
Nunc
Quisque
Aenean
Nam
Vivamus
Curabitur
Nullam

Essaye ça:

$ ( echo ".TS"; echo "l l."; paste file1 file2; echo ".TE" ) | tbl | nroff | more

Et vous obtiendrez:

ETIAM         Lorem
SED           Proin
MAECENAS      Nunc
DONEC         Quisque
SUSPENDISSE   Aenean
              Nam
              Vivamus
              Curabitur
              Nullam
Jeff Taylor
la source
Ceci, comme les autres solutions utilisant paste, échouera à imprimer la sortie appropriée s'il y a des lignes contenant des tabulations. +1 pour avoir été différent cependant
don_crissti
+1. Pourriez-vous expliquer comment fonctionne la solution?
Tulains Córdova
1

Une awksolution qui devrait être assez portable et devrait fonctionner pour un nombre arbitraire de fichiers d'entrée:

# Invoke thus:
#   awk -F\\t -f this_file file1 file2

# every time we read a new file, FNR goes to 1

FNR==1 {
    curfile++                       # current file
}

# read all files and save all the info we'll need
{
    column[curfile,FNR]=$0          # save current line
    nlines[curfile]++               # number of lines in current file
    if (length > len[curfile])
            len[curfile] = length   # max line length in current file
}

# finally, show the lines from all files side by side, as a table
END {
    # iterate through lines until there are no more lines in any file
    for (line = 1; !end; line++) {
            $0 = _
            end = 1

            # iterate through all files, we cannot use
            #   for (file in nlines) because arrays are unordered
            for (file=1; file <= curfile; file++) {
                    # columnate corresponding line from each file
                    $0 = $0 sprintf("%*s" FS, len[file], column[file,line])
                    # at least some file had a corresponding line
                    if (nlines[file] >= line)
                            end = 0
            }

            # don't print a trailing empty line
            if (!end)
                    print
    }
}
ninjalj
la source
Comment utilisez-vous cela sur le fichier1 et le fichier2? J'ai appelé le script paste-awket paste file1 file2|paste-awkj'ai essayé et j'ai essayé awk paste-awk file1 file2mais aucun n'a fonctionné.
rubo77
Je reçoisawk: Line:1: (FILENAME=file1 FNR=1) Fatal: Division by zero
rubo77
@ rubo77: awk -f paste-awk file1 file2devrait fonctionner, au moins pour GNU awk et mawk.
ninjalj
Cela fonctionne, bien qu'il soit légèrement différent car il pastey a moins d'espace entre les deux rangées. Et si le fichier d'entrée n'a pas toutes les lignes de la même longueur, il en résultera une ligne alignée à droite
rubo77
@ rubo77: le séparateur de champ peut être réglé avec-F\\t
ninjalj