Kombinieren Sie Textdateien spaltenweise


52

Ich habe zwei Textdateien. Der erste hat Inhalt:

Languages
Recursively enumerable
Regular

während der zweite Inhalt hat:

Minimal automaton
Turing machine
Finite

Ich möchte sie spaltenweise in einer Datei zusammenfassen. Also habe ich es versucht paste 1 2und seine Ausgabe ist:

Languages   Minimal automaton
Recursively enumerable  Turing machine
Regular Finite

Ich möchte jedoch, dass die Spalten wie z

Languages               Minimal automaton
Recursively enumerable  Turing machine
Regular                 Finite

Ich habe mich gefragt, ob es möglich wäre, dies ohne manuelle Bearbeitung zu erreichen.


Hinzugefügt:

Hier ist ein anderes Beispiel, in dem Bruce es fast nagelt, außer einer leichten Fehlausrichtung, über die ich mich wundere, warum?

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

3
Das letzte Beispiel mit Fehlausrichtung ist ein Trottel. Ich kann es unter Arch Linux duplizieren, pr (GNU coreutils) 8.12. Ich kann es nicht auf einer älteren Slackware (11.0) duplizieren, die ich auch in der Nähe habe: pr (GNU coreutils) 5.97. Das Problem liegt beim '-' Zeichen, und es ist in PR, nicht in Paste.
Bruce Ediger

1
Ich bekomme das gleiche mit dem EM-DASH mit beiden prund expand... columnsvermeidet dieses Problem.
Peter.O

Ich habe für die meisten Antworten eine Ausgabe erstellt, mit Ausnahme von awk + paste , bei der die am weitesten rechts stehende Spalte (n) nach links verschoben wird, wenn eine linke Datei kürzer als eine rechts davon ist. Das Gleiche und mehr gilt für 'Einfügen + Spalte', bei dem dieses Problem auch bei Leerzeilen in der linken Spalte (n) auftritt ... Wenn Sie alle Ausgaben zusammen anzeigen möchten. Hier ist der Link: paste.ubuntu.com/643692 Ich habe 4 Spalten verwendet.
Peter.O

Ich habe gerade bemerkt etwas irreführend auf dem paste.ubuntu Link ... Ich habe ursprünglich die Daten für meine Skripte zu testen, (und das zu tun , die andere führte auf) ... so die Felder , die sagen , ➀ unicode may render oddly but the column count is ok definitiv nicht nicht anzuwenden wc-paste-prund wc-paste-prSie Zeige Unterschiede in der Spaltenanzahl. Die anderen sind in Ordnung.
Peter.O

1
@BruceEdiger: Das Ausrichtungsproblem tritt auf, wenn Nicht-ASCII-Zeichen verwendet werden (in seiner Frage verwendete das OP einen Bindestrich (-) anstelle eines Minuszeichens (-)), höchstwahrscheinlich aufgrund einer schlechten oder fehlenden Behandlung durch prdas Multibyte Zeichen im aktuellen Gebietsschema (normalerweise UTF8).
WhiteWinterWolf

Antworten:


68

Sie müssen nur den columnBefehl eingeben und ihn anweisen, Tabulatoren zum Trennen von Spalten zu verwenden

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

Um die Kontroverse um "leere Zellen" anzugehen, brauchen wir nur die -nOption 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

Meine Spaltenmanpage zeigt an, dass -nes sich um eine "Debian GNU / Linux-Erweiterung" handelt. Mein Fedora-System weist kein Problem mit leeren Zellen auf: Es scheint von BSD abgeleitet zu sein und in der Manpage heißt es "Version 2.23 hat die Option -s so geändert, dass sie nicht gierig ist".


4
Glenn: Du bist der Held der Stunde! Ich wusste, dass es so etwas gibt, aber ich konnte mich nicht daran erinnern. Ich habe über diese Frage nachgedacht. warte auf dich :) ... columnnatürlich; wie offensichtlich (im
nachhinein

4
Ich habe gerade bemerkt, dass column -s $'\t' -tleere Zellen ignoriert werden , was dazu führt , dass alle nachfolgenden Zellen rechts davon (in dieser Zeile) nach links verschoben werden. dh als Ergebnis einer leeren Zeile in einer Datei, oder es ist kürzer ... :(
Peter.O

1
@masi, korrigiert
Glenn Jackman

-n funktioniert nicht in RHEL. Gibt es eine alternative
Koshur

Ich kann endlich einen Kommentar abgeben und möchte darauf hinweisen, dass ich zuvor eine Antwort hinzugefügt habe, die das Problem von Peter.O mit Durchläufen von leeren Zellen durch Verwendung von Nullen behebt.
Techno

11

Sie suchen nach dem praktischen Dandy- prBefehl:

paste file1 file2 | pr -t -e24

Das "-e24" ist "Tabulatorstopps auf 24 Leerzeichen erweitern". Zum Glück wird pasteein Tabulator-Zeichen zwischen die Spalten gesetzt, damit pres erweitert werden kann. Ich habe 24 gewählt, indem ich die Zeichen in "Recursively enumerable" gezählt und 2 hinzugefügt habe.


Vielen Dank! Was bedeutet "Tabulatoren auf 24 Leerzeichen erweitern"?
Tim

Ich aktualisiere auch mit einem Beispiel, bei dem Ihre Methode es bis auf eine leichte Fehlausrichtung fast nagelt.
Tim

Traditionell schlagen "Tabstops" alle 8 Felder ein. "123TABabc" würde mit dem 'a'-Zeichen 8 Zeichenbreite vom Zeilenanfang ausgedruckt. Wenn Sie den Wert auf 24 setzen, wird das 'a' ab dem Zeilenanfang auf 24 Zeichenbreite gesetzt.
Bruce Ediger

Sie sagen , die „-e24“ ist „erweitern Registerkarte 24 Räume stoppt“ , warum also nicht die Verwendung expandBefehl direkt: paste file1 file2 | expand -t 24?
WhiteWinterWolf

1
@Masi - meine Antwort ist ähnlich, aber weniger kompliziert als die Antwort von @techno unten. Es wird nicht aufgerufen sed, es gibt also einen Prozess, der nicht ausgeführt wird. Es verwendet prdas ist ein alter Befehl, aus dem Jahr Unix SysV Tage, ich glaube, so ist es , als auf mehr Installationen bestehen könnte expand. Kurz gesagt, es ist nur alte Schule.
Bruce Ediger

9

Update : Hier ist ein viel einfacheres Skript (das am Ende der Frage) für die tabellarische Ausgabe. Übergeben pasteSie einfach den Dateinamen wie htmlgewohnt ... Der Frame wird so erstellt, dass er angepasst werden kann. Es werden mehrere Leerzeichen beibehalten, und die Spaltenausrichtung wird beibehalten, wenn Unicode-Zeichen gefunden werden. Die Art und Weise, wie der Editor oder der Betrachter den Unicode rendert, ist jedoch eine ganz andere Sache ...

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

---

Eine Übersicht über die in den Antworten vorgestellten Tools (bisher).
Ich habe sie mir ziemlich genau angesehen. Folgendes habe ich gefunden:

paste# Dieses Tool ist allen bisher vorgestellten Antworten gemeinsam. # Es kann mehrere Dateien verarbeiten. daher mehrere Spalten ... Gut! # Es begrenzt jede Spalte mit einem Tabulator ... Gut. # Die Ausgabe ist nicht tabellarisch.

Alle folgenden Tools entfernen dieses Trennzeichen! ... Schlecht, wenn Sie ein Trennzeichen benötigen.

column # Es entfernt das Tab-Trennzeichen, so dass die Feldidentifizierung nur durch Spalten erfolgt, die es anscheinend recht gut handhabt. Ich habe nichts Falsches entdeckt ... # Abgesehen davon, dass es kein eindeutiges Trennzeichen gibt, funktioniert es einwandfrei!

expand # Hat nur eine Tabulatoreinstellung, ist also über 2 Spalten hinaus unvorhersehbar

pr# Verfügt nur über eine einzige Tabulatoreinstellung, so dass eine Überschreitung von 2 Spalten nicht vorhersehbar ist. # Die Ausrichtung der Spalten ist bei der Verarbeitung von Unicode nicht korrekt. Das Tabulator-Trennzeichen wird entfernt, sodass die Feldidentifizierung ausschließlich durch die Spaltenausrichtung erfolgt

Für mich ist columnes die offensichtlich beste Lösung als Einzeiler. Wenn Sie entweder das Trennzeichen oder eine ASCII-artige Tabellierung Ihrer Dateien wünschen, lesen Sie weiter, ansonsten columnsist es verdammt gut :) ...


Hier ist ein Skript, das eine beliebige Anzahl von Dateien aufnimmt und eine tabellarische ASCII-Darstellung erstellt. (Beachten Sie, dass Unicode möglicherweise nicht die erwartete Breite aufweist, z. B. eg, das ein einzelnes Zeichen ist. Dies unterscheidet sich erheblich von der Spalte Zahlen sind falsch, wie es bei einigen der oben genannten Dienstprogramme der Fall ist.) ... Die unten gezeigte Ausgabe des Skripts stammt aus 4 Eingabedateien mit dem Namen 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

Hier ist meine ursprüngliche Antwort (anstelle des obigen Skripts etwas gekürzt)

Verwenden Sie wc, um die Spaltenbreite zu ermitteln und seddas rechte Feld mit einem sichtbaren Zeichen zu versehen .(nur für dieses Beispiel) ... und dann paste, um die beiden Spalten mit einem Tabulatorzeichen zu verbinden ...

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

Wenn Sie die rechte Spalte ausfüllen möchten:

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

Vielen Dank! Sie haben ziemlich viel Arbeit geleistet. Das ist erstaunlich.
Tim,

5

Du bist fast da. pasteFügt zwischen jede Spalte ein Tabulatorzeichen ein, sodass Sie nur die Tabulatoren erweitern müssen. (Ich gehe davon aus, dass Ihre Dateien keine Tabulatoren enthalten.) Sie müssen die Breite der linken Spalte bestimmen. wc -LZeigt mit (aktuell genug) GNU-Dienstprogrammen die Länge der längsten Zeile an. Machen Sie auf anderen Systemen einen ersten Durchgang mit awk. Dabei +1handelt es sich um die Menge an Leerzeichen, die Sie zwischen den Spalten einfügen möchten.

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}')

Wenn Sie über das BSD-Dienstprogramm für Spalten verfügen, können Sie damit die Spaltenbreite bestimmen und die Registerkarten auf einmal erweitern. ( Ist ein wörtliches Tabulatorzeichen. Sie können es $'\t'stattdessen unter bash / ksh / zsh und in jeder beliebigen Shell verwenden "$(printf '\t')".)

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

In meiner Version wcmuss der Befehl sein: wc -L <left.txt... weil, wenn Sie einen Dateinamen als Befehlszeilen spedified ist arg , seinen Namen auf stdout ausgegeben ist
Peter.O

4

Das ist mehrstufig, also nicht optimal, aber hier ist es.

1) Ermitteln Sie die Länge der längsten Zeile in file1.txt.

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

In Ihrem Beispiel ist die längste Zeile 22.

2) Füllen Sie mit awk file1.txtjede Zeile mit weniger als 22 Zeichen bis zu 22 mit der printfAnweisung auf.

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

Hinweis: Verwenden Sie für FS eine Zeichenfolge, die in nicht vorhanden ist file1.txt.

3) Verwenden Sie die Paste wie zuvor.

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

Wenn Sie dies häufig tun, kann dies leicht in ein Skript umgewandelt werden.


In Ihrem Code, um die längste Zeile zu finden, müssen Sie while IFS= read -r line, sonst wird die Shell Leerzeichen und Backslashes entstellen. Aber die Shell ist nicht das beste Werkzeug für diesen Job. Aktuelle Versionen von GNU Coreutils haben wc -L(siehe freds Antwort), oder Sie können awk: verwenden awk 'n<length {n=length} END {print +n}'.
Gilles 'SO - hör auf böse zu sein'

4

Ich kann die Antwort von Glenn Jackman nicht kommentieren, daher füge ich dies hinzu, um das Problem der leeren Zellen zu beheben, das Peter.O festgestellt hat. Durch Hinzufügen eines Nullzeichens vor jedem Tab werden die Trennzeichenfolgen eliminiert, die als einzelne Unterbrechungen behandelt werden, und das Problem wird behoben. (Ich habe ursprünglich Leerzeichen verwendet, aber durch die Verwendung des Nullzeichens wird der zusätzliche Abstand zwischen den Spalten eliminiert.)

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

Wenn das Nullzeichen aus verschiedenen Gründen Probleme verursacht, versuchen Sie Folgendes:

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

oder

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

Beide sedund columnscheinen sich in der Implementierung in verschiedenen Versionen und Varianten von Unix / Linux zu unterscheiden, insbesondere BSD (und Mac OS X) gegenüber GNU / Linux.


Dieser sed Befehl scheint nichts zu tun. Ich ersetze den Spaltenbefehl durch od -cund sehe keine Null-Bytes. Dies ist auf Centos und Ubuntu.
Glenn Jackman

1
Dies funktionierte für mich in RedHat EL4. Sowohl sed als auch column scheinen im Laufe der Zeit und des Systems zu variieren. In Ubuntu 14.4 \0funktionierte die Verwendung nicht als nullin sed, \x0tat es aber. Die Spalte ergab jedoch einen line too longFehler. Am einfachsten scheint es, ein Leerzeichen zu verwenden und mit dem zusätzlichen Charakter zu leben.
Techno

0

Aufbauend auf der Antwort von Bahamat : Dies kann vollständig in awk, nur einmaliges Lesen der Dateien und keine temporären Dateien erstellt werden. Um das Problem wie angegeben zu lösen, gehen Sie folgendermaßen vor

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

Wie bei vielen awkSkripten dieses Typs wird zuerst gelesen file1, wobei alle Daten im saveArray gespeichert und gleichzeitig die maximale Zeilenlänge berechnet werden. Dann liest file2 und druckt es die gespeicherten ( file1) Daten neben den aktuellen ( file2) Daten. Wenn file1es länger ist als file2(mehr Zeilen), drucken wir die letzten Zeilen von file1 (die, für die es keine entsprechende Zeile in der zweiten Spalte gibt).

Zum printfFormat:

  • "%-nns"Gibt eine Zeichenfolge aus, die linksbündig in einem Feld mit einer nnBreite von Zeichen angeordnet ist.
  • "%-*s", nntut das Gleiche - das *sagt, es soll die Feldbreite vom nächsten Parameter übernehmen.
  • Mit for erhalten wir zwei Leerzeichen zwischen den Spalten. Offensichtlich kann das eingestellt werden.maxlength+2nn+2

Das obige Skript funktioniert nur für zwei Dateien. Es kann trivial modifiziert werden, um drei Dateien oder vier Dateien usw. zu handhaben, aber dies wäre mühsam und wird als Übung belassen. Es stellt sich jedoch heraus, dass es nicht schwierig ist, es zu ändern, um eine beliebige Anzahl von Dateien zu verarbeiten:

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*

Dies ist meinem ersten Skript sehr ähnlich, außer

  • Es verwandelt sich max_lengthin ein Array.
  • Es verwandelt sich max_FNRin ein Array.
  • Es wird savezu einem zweidimensionalen Array.
  • Es liest alle Dateien und speichert den gesamten Inhalt. Dann schreibt es die gesamte Ausgabe des ENDBlocks aus.

Ich weiß, dass diese Frage alt ist; Ich bin nur darauf gestoßen. Ich stimme zu, dass dies pastedie beste Lösung ist. speziell Glenn Jackmans paste file1 file2 | column -s $'\t' -t. Aber ich dachte, es würde Spaß machen, den awkAnsatz zu verbessern .
G-Man sagt, dass Monica
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.