łączyć pliki tekstowe według kolumn


52

Mam dwa pliki tekstowe. Pierwszy ma treść:

Languages
Recursively enumerable
Regular

podczas gdy drugi ma treść:

Minimal automaton
Turing machine
Finite

Chcę połączyć je w jeden plik pod względem kolumn. Więc próbowałem, paste 1 2a jego wynik to:

Languages   Minimal automaton
Recursively enumerable  Turing machine
Regular Finite

Chciałbym jednak dobrze wyrównać kolumny, takie jak

Languages               Minimal automaton
Recursively enumerable  Turing machine
Regular                 Finite

Zastanawiałem się, czy można to osiągnąć bez ręcznej obsługi?


Dodany:

Oto inny przykład, w którym metoda Bruce'a prawie ją przybiła, z wyjątkiem niewielkiego przesunięcia, o które zastanawiam się dlaczego?

$ 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
Ten ostatni przykład, z niewspółosiowością, jest niefortunny. Mogę powielić go na Arch Linux, pr (GNU coreutils) 8.12. Nie mogę go zduplikować na starszym Slackware (11.0). Mam też około: pr (GNU coreutils) 5.97. Problem tkwi w znaku „-” i jest w pr, a nie wklejaniu.
Bruce Ediger,

1
To samo dotyczy EM-DASH z obydwoma pri expand... columnsunika tego problemu.
Peter.O,

Stworzyłem dane wyjściowe dla większości różnych odpowiedzi, z wyjątkiem awk + wklej , który spowoduje przesunięcie w lewo najbardziej wysuniętych kolumn, jeśli lewy plik jest krótszy niż jakikolwiek z prawej strony. To samo i więcej dotyczy „wklejania + kolumny”, która ma również ten problem z pustymi liniami w lewej kolumnie (kolumnach) ... Jeśli chcesz zobaczyć wszystkie dane wyjściowe razem. tutaj jest link: paste.ubuntu.com/643692 Użyłem 4 kolumn.
Peter.O

Właśnie zauważyłem coś mylących na paste.ubuntu link ... I początkowo ustawić zapasowych danych do testowania moich skryptów, (a to doprowadziło do prowadzenia innych) ... więc pola, które mówią ➀ unicode may render oddly but the column count is ok zdecydowanie nie nie dotyczy wc-paste-pri wc-paste-proni pokazują różnice w liczbie kolumn. Inne są w porządku.
Peter.O

1
@BruceEdiger: Problem wyrównania występuje, gdy używane są znaki spoza ASCII (w jego pytaniu OP użył myślnika (-) zamiast znaku minus (-)), najprawdopodobniej z powodu złej obsługi lub braku obsługi przez prwielobajt znaki w bieżącym locale (zwykle UTF8).
WhiteWinterWolf,

Odpowiedzi:


68

Potrzebujesz tylko columnpolecenia i powiedz mu, aby używało tabulatorów do oddzielania kolumn

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

Aby rozwiązać problem kontrowersji związanych z „pustą komórką”, potrzebujemy tylko -nopcji 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

Moja strona podręcznika wskazuje, że -njest to „rozszerzenie Debian GNU / Linux”. Mój system Fedora nie wykazuje problemu pustych komórek: wydaje się, że pochodzi on z BSD, a strona podręcznika mówi: „Wersja 2.23 zmieniła opcję -s, aby nie była chciwa”


4
glenn: Jesteś bohaterem godziny! Wiedziałem, że coś takiego jest w pobliżu, ale nie pamiętałem tego. Czaiłem się nad tym pytaniem; czekam na ciebie :) ... columnoczywiście; jak oczywiste (z perspektywy czasu) +1 ... Dzięki ...
Peter.O

4
Właśnie zauważyłem, że column -s $'\t' -tignoruje puste komórki , w wyniku czego wszystkie kolejne komórki po prawej stronie (w tym wierszu) przesuwają się w lewo; tj. w wyniku pustej linii w pliku lub jej krótszej ... :(
Peter.O

1
@masi, poprawione
glenn jackman

-n nie działa w RHEL. Czy istnieje alternatywa?
Koshur

Mogę wreszcie skomentować, więc chcę zauważyć, że wcześniej dodałem poniżej odpowiedź, która rozwiązuje problem Peter.O dotyczący uruchamiania pustych komórek za pomocą wartości null.
techno

11

Szukasz poręcznego prpolecenia dandy :

paste file1 file2 | pr -t -e24

„-E24” to „rozwiń tabulacje do 24 spacji”. Na szczęście pasteumieszcza znak tabulacji między kolumnami, więc prmożna go rozwinąć. Wybrałem 24, licząc znaki w „Rekurencyjnie wyliczalne” i dodając 2.


Dzięki! Co oznacza „rozwiń tabulatory do 24 spacji”?
Tim

Aktualizuję również z przykładem, w którym twoja metoda prawie ją przybija, z wyjątkiem niewielkiego przesunięcia.
Tim

Tradycyjnie „tabstopsy” uderzają co 8 pól. „123TABabc” zostanie wydrukowane ze znakiem „a” o szerokości 8 znaków od początku wiersza. Ustawienie go na 24 umieściłoby „a” przy szerokości 24 znaków od początku linii.
Bruce Ediger,

Mówisz z „-e24” jest „rozwinąć tabulatory do 24 przestrzeni” , więc dlaczego nie skorzystać z expandpolecenia bezpośrednio: paste file1 file2 | expand -t 24?
WhiteWinterWolf,

1
@Masi - moja odpowiedź jest podobna, ale mniej skomplikowana, niż odpowiedź @ techno poniżej. Nie wywołuje, sedwięc jest jeden proces, który nie działa. Używa prstarożytnego polecenia, datowanego na dni Uniksa SysV, tak myślę, więc może istnieć przy większej liczbie instalacji niż expand. Krótko mówiąc, to tylko stara szkoła.
Bruce Ediger,

9

Aktualizacja : tutaj jest o wiele prostszy skrypt (ten na końcu pytania) dla danych tabelarycznych. Po prostu przekaż do niego nazwę pliku, tak jak chcesz paste... Służy htmldo tworzenia ramki, więc można ją modyfikować. Zachowuje wiele spacji, a wyrównanie kolumny jest zachowywane, gdy napotka znaki Unicode. Jednak sposób, w jaki redaktor lub przeglądający renderuje Unicode, to zupełnie inna sprawa ...

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

---

Streszczenie narzędzi przedstawionych w odpowiedziach (jak dotąd).
Przyjrzałem się im z bliska; oto co znalazłem:

paste# To narzędzie jest wspólne dla wszystkich dotychczas prezentowanych odpowiedzi # Może obsługiwać wiele plików; dlatego wiele kolumn ... Dobrze! # Określa każdą kolumnę tabulatorem ... Dobrze. # Dane wyjściowe nie są zestawione w tabelach.

Wszystkie poniższe narzędzia usuwają ten ogranicznik! ... Źle, jeśli potrzebujesz ogranicznika.

column # Usuwa ogranicznik Tab, więc identyfikacja pola jest oparta wyłącznie na kolumnach, które wydają się całkiem dobrze obsługiwać .. Nie zauważyłem niczego złego ... # Poza brakiem unikalnego ogranicznika, działa dobrze!

expand # Ma tylko ustawienie pojedynczej tabulacji, więc jest nieprzewidywalny poza 2 kolumnami # Wyrównanie kolumn nie jest dokładne przy obsłudze Unicode, i usuwa separator Tab, więc identyfikacja pola odbywa się wyłącznie na podstawie wyrównania kolumn

pr# Ma tylko ustawienie jednej karty, więc jest nieprzewidywalny poza 2 kolumnami. # Wyrównanie kolumn nie jest dokładne podczas obsługi Unicode i usuwa separator Tab, więc identyfikacja pola odbywa się wyłącznie na podstawie wyrównania kolumn

Dla mnie columnto oczywiste najlepsze rozwiązanie jako linijka .. Chcesz separator lub tablicę ASCII-art swoich plików, czytaj dalej, w przeciwnym razie ... columnsjest całkiem dobra :) ...


Oto skrypt, który pobiera dowolną liczbę plików i tworzy tabelaryczną prezentację ASCII-art. (Pamiętaj, że Unicode może nie renderować oczekiwanej szerokości, np. ௵, która jest pojedynczym znakiem. Jest to całkiem inna kolumna liczby są niepoprawne, jak ma to miejsce w przypadku niektórych narzędzi wymienionych powyżej.) ... Wyjście skryptu, pokazane poniżej, pochodzi z 4 plików wejściowych o nazwie 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

Oto moja oryginalna odpowiedź (nieco przycięta zamiast powyższego skryptu)

Używając, wcaby uzyskać szerokość kolumny i sedprawy pad z widocznym znakiem .(tylko dla tego przykładu) ... a następnie pastepołączyć dwie kolumny znakiem tab ...

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

Jeśli chcesz uzupełnić prawą kolumnę:

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

Dzięki! Wykonałeś sporo pracy. To wspaniale.
Tim

5

Jesteś prawie na miejscu. pasteumieszcza znak tabulacji między każdą kolumną, więc wszystko, co musisz zrobić, to rozwinąć tabulatory. (Zakładam, że twoje pliki nie zawierają tabulatorów.) Musisz określić szerokość lewej kolumny. Przy (wystarczająco niedawnych) narzędziach GNU wc -Lpokazuje długość najdłuższej linii. W innych systemach wykonaj pierwsze przejście z awk. Jest +1to ilość pustego miejsca między kolumnami.

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

Jeśli masz narzędzie kolumny BSD, możesz go użyć do określenia szerokości kolumny i rozwinięcia zakładek za jednym razem. ( to dosłowny znak tabulacji; pod bash / ksh / zsh możesz użyć $'\t'zamiast tego, i w dowolnej powłoce, której możesz użyć "$(printf '\t')").

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

W mojej wersji wcpolecenie musi być: wc -L <left.txt... ponieważ gdy nazwa pliku jest spedyfikowana jako arg wiersza polecenia , jego nazwa jest wyprowadzana na standardowe wyjście
Peter.O

4

Jest to wieloetapowe, więc nie jest optymalne, ale proszę bardzo.

1) Znajdź długość najdłuższej linii file1.txt.

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

W twoim przykładzie najdłuższa linia to 22.

2) Użyj awk, aby wstawić file1.txt, wypełniając każdą linię mniej niż 22 znaki do 22 za pomocą printfinstrukcji.

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

Uwaga: W przypadku FS użyj ciągu, który nie istnieje w file1.txt.

3) Użyj wklejania tak jak wcześniej.

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

Jeśli robisz to często, możesz to łatwo zmienić w skrypt.


W swoim kodzie, aby znaleźć najdłuższą linię, potrzebujesz while IFS= read -r line, w przeciwnym razie powłoka będzie mieszać białe spacje i odwrotne ukośniki. Ale powłoka nie jest najlepszym narzędziem do tego zadania; Nowsze wersje mają coreutils GNU wc -L(patrz odpowiedź Freda), lub użyć awk: awk 'n<length {n=length} END {print +n}'.
Gilles „SO - przestań być zły”,

4

Nie jestem w stanie skomentować odpowiedzi Glenna Jackmana, więc dodaję to, aby rozwiązać problem pustych komórek, który zauważył Peter.O. Dodanie znaku zerowego przed każdą kartą eliminuje przebiegi ograniczników, które są traktowane jako pojedyncza przerwa i rozwiązuje problem. (Pierwotnie użyłem spacji, ale użycie znaku null eliminuje dodatkowe spacje między kolumnami.)

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

Jeśli znak zerowy powoduje problemy z różnych powodów, spróbuj:

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

lub

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

Zarówno sedi columnwydaje się zmieniać w realizacji całej smaków i wersje Unix / Linux, BSD zwłaszcza (i Mac OS X) vs. GNU / Linux.


To polecenie sed wydaje się nic nie robić. Zastępuję polecenie kolumny od -ci nie widzę żadnych pustych bajtów. Dotyczy to centos i ubuntu.
glenn jackman

1
To działało dla mnie w RedHat EL4. Zarówno sed, jak i kolumna wydają się zmieniać w czasie i systemie. W Ubuntu 14.4 używanie \0nie działało jako nullsed, ale działało \x0. Jednak wtedy kolumna podała line too longbłąd. Najprostszą rzeczą wydaje się być użycie przestrzeni i życie z dodatkową postacią.
techno

0

Opierając się na odpowiedzi bahamata : można tego dokonać całkowicie awk, czytając pliki tylko raz i nie tworząc żadnych plików tymczasowych. Aby rozwiązać problem zgodnie z opisem, wykonaj

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

Podobnie jak w przypadku wielu awkskryptów tego rodzaju, powyższe pierwsze czyta file1, zapisując wszystkie dane w savetablicy i jednocześnie obliczając maksymalną długość linii. Następnie odczytuje file2 i drukuje zapisane ( file1) dane obok siebie z bieżącymi file2danymi ( ). Wreszcie, jeśli file1jest dłuższy niż file2(ma więcej wierszy), wypisujemy kilka ostatnich wierszy file1 (tych, dla których nie ma odpowiadającej linii w drugiej kolumnie).

Jeśli chodzi o printfformat:

  • "%-nns"wypisuje ciąg wyrównany do lewej strony w polu o nnszerokości znaków.
  • "%-*s", nnrobi to samo - *każe pobrać szerokość pola z następnego parametru.
  • Używając for , otrzymujemy dwie spacje między kolumnami. Oczywiście można to zmienić.maxlength+2nn+2

Powyższy skrypt działa tylko dla dwóch plików. Można go w prosty sposób zmodyfikować, aby obsługiwał trzy pliki lub cztery pliki itp., Ale byłoby to żmudne i pozostawione jako ćwiczenie. Jednak, jak się okazuje, nie będzie trudno go zmodyfikować, aby obsługiwać dowolną liczbę z plików:

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*

Jest to bardzo podobne do mojego pierwszego skryptu, z wyjątkiem tego, że

  • Zmienia max_lengthsię w tablicę.
  • Zmienia max_FNRsię w tablicę.
  • Zmienia savesię w dwuwymiarowy układ.
  • Odczytuje wszystkie pliki, zapisując całą zawartość. Następnie wypisuje wszystkie dane wyjściowe z ENDbloku.

Wiem, że to pytanie jest stare; Natknąłem się na to. Zgadzam się, że pasteto najlepsze rozwiązanie; konkretnie, glenn jackman's paste file1 file2 | column -s $'\t' -t. Ale pomyślałem, że fajnie byłoby spróbować poprawić awkpodejście.
G-Man mówi „Reinstate Monica”
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.