Ein besserer Einfügebefehl

11

Ich habe die folgenden zwei Dateien (ich habe die Zeilen mit Punkten aufgefüllt, sodass jede Zeile in einer Datei die gleiche Breite hat, und Datei1 mit Großbuchstaben versehen, um sie klarer zu machen).

contents of file1:

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

contents of file2

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

Beachten Sie, dass Datei2 länger als Datei1 ist.

Wenn ich diesen Befehl ausführe:

paste file1 file2

Ich bekomme diese Ausgabe

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

Was kann ich tun, damit die Ausgabe wie folgt ist?

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

Ich habe es versucht

paste file1 file2 | column -t

aber es macht das:

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

Nicht so hässlich wie die ursprüngliche Ausgabe, aber trotzdem spaltenweise falsch.

Tulains Córdova
quelle
2
pasteverwendet Tabulatoren vor den Zeilen aus der zweiten Datei. Möglicherweise müssen Sie einen Postprozessor verwenden, um die Spalten entsprechend auszurichten.
Unxnut
3
paste file1 file2 | column -tn?
Ninjalj
Hat Datei1 immer Spalten mit fester Größe?
RSFalcon7
@ RSFalcon7 Ja, das tut es.
Tulains Córdova

Antworten:

17

Angenommen, Ihre Dateien enthalten keine Tabulatorzeichen.

paste file1 file2 | expand -t 13

mit dem Argument -tentsprechend ausgewählt, um die gewünschte maximale Linienbreite in Datei1 abzudecken.

OP hat eine flexiblere Lösung hinzugefügt:

Ich habe das gemacht, damit es ohne die magische Nummer 13 funktioniert:

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

Es ist nicht einfach zu tippen, kann aber in einem Skript verwendet werden.

Mark Plotnick
quelle
nett! Ich wusste nicht über erweitern, bevor ich Ihre Antwort las :)
TabeaKischka
4

Ich dachte, awk könnte es gut machen, also googelte ich "awk read input from two files" und fand einen Artikel über stackoverflow , der als Ausgangspunkt dienen könnte.

Zuerst ist die komprimierte Version, dann unten vollständig kommentiert. Das Training dauerte mehr als ein paar Minuten. Ich würde mich über einige Verfeinerungen von klügeren Leuten freuen.

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

Und hier ist die vollständig dokumentierte Version des oben genannten.

# 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
quelle
1
+1 Dies ist die einzige Antwort, die mit willkürlichen Eingaben funktioniert (dh mit Zeilen, die möglicherweise Tabulatoren enthalten). Ich denke nicht, dass dies wesentlich verfeinert / verbessert werden könnte.
don_crissti
2

Keine sehr gute Lösung, aber ich konnte es mit

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

Dabei wird TAB durch das Tabulatorzeichen ersetzt.

unxnut
quelle
Welche Rolle spielt der &&Befehl sed?
Kaffee Tasse
1
Eine einzelne &setzt, wonach gesucht wird (in diesem Fall eine Registerkarte). Dieser Befehl ersetzt einfach die Registerkarte am Anfang durch zwei Registerkarten.
Unxnut
Ich musste wechseln TAB, \tdamit dies in zsh unter Ubuntu debian funktioniert. Und es funktioniert nur, wenn Datei1 weniger als 15 Zeichen hat
rubo77
2

Hat unter Debian und Derivaten columneine -n Nomerge- Option, mit der die Spalte mit leeren Feldern das Richtige tun kann. columnVerwendet intern die wcstok(wcs, delim, ptr)Funktion, die eine breite Zeichenfolge in Token aufteilt, die durch die breiten Zeichen im delimArgument begrenzt sind.

wcstokBeginnen Sie mit dem Überspringen breiter Zeichen delim, bevor Sie den Token erkennen. Die -nOption verwendet einen Algorithmus, der keine anfänglichen breiten Zeichen überspringt delim.

Leider ist dies nicht sehr portabel: -nist Debian-spezifisch und columnnicht in POSIX, es ist anscheinend eine BSD-Sache.

Ninjalj
quelle
2

Entfernen Sie die Punkte, die Sie zum Auffüllen verwendet haben:

Datei1:

ETIAM
SED
MAECENAS
DONEC
SUSPENDISSE

Datei2:

Lorem
Proin
Nunc
Quisque
Aenean
Nam
Vivamus
Curabitur
Nullam

Versuche dies:

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

Und du wirst bekommen:

ETIAM         Lorem
SED           Proin
MAECENAS      Nunc
DONEC         Quisque
SUSPENDISSE   Aenean
              Nam
              Vivamus
              Curabitur
              Nullam
Jeff Taylor
quelle
Wie bei den anderen verwendeten Lösungen pastewird auch hier nicht die richtige Ausgabe gedruckt, wenn Zeilen mit Registerkarten vorhanden sind. +1 für anders zu sein
don_crissti
+1. Würden Sie bitte erklären, wie die Lösung funktioniert?
Tulains Córdova
1

Eine awkLösung, die ziemlich portabel sein sollte und für eine beliebige Anzahl von Eingabedateien funktionieren sollte:

# 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
quelle
Wie verwenden Sie dies für Datei1 und Datei2? Ich rief das Skript an paste-awkund versuchte es paste file1 file2|paste-awkund ich versuchte es, awk paste-awk file1 file2aber keines funktionierte.
Rubo77
Ich bekommeawk: Line:1: (FILENAME=file1 FNR=1) Fatal: Division by zero
rubo77
@ rubo77: awk -f paste-awk file1 file2sollte funktionieren, zumindest für GNU awk und mawk.
Ninjalj
Dies funktioniert, obwohl es sich geringfügig davon unterscheidet, pastedass zwischen den beiden Zeilen weniger Platz ist. Und wenn die Eingabedatei nicht alle Zeilen gleich lang ist, führt dies zu einer rechtsbündigen Zeile
rubo77
@ rubo77: Das Feldtrennzeichen kann mit-F\\t
ninjalj