Lepsze polecenie wklejania

11

Mam dwa następujące pliki (wypełniałem linie kropkami, więc każda linia w pliku ma tę samą szerokość i utworzyłam plik1 wszystkie wielkie litery, aby było bardziej wyraźne).

contents of file1:

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

contents of file2

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

Zauważ, że plik2 jest dłuższy niż plik1.

Po uruchomieniu tego polecenia:

paste file1 file2

Dostaję ten wynik

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

Co mogę zrobić, aby wynik był następujący?

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

próbowałem

paste file1 file2 | column -t

ale robi to:

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

nie tak brzydkie jak oryginalne wyjście, ale i tak niewłaściwe pod względem kolumn.

Tulains Córdova
źródło
2
pasteużywa tabulatorów przed wierszami z drugiego pliku. Być może będziesz musiał użyć postprocesora, aby odpowiednio wyrównać kolumny.
unxnut
3
paste file1 file2 | column -tn?
ninjalj
czy plik1 zawsze ma kolumny o stałym rozmiarze?
RSFalcon7,
@ RSFalcon7 Tak, robi.
Tulains Córdova

Odpowiedzi:

17

Zakładając, że nie masz żadnych znaków tabulacji w swoich plikach,

paste file1 file2 | expand -t 13

z argumentem -todpowiednio dobranym do pokrycia żądanej maksymalnej szerokości linii w pliku 1.

OP dodał bardziej elastyczne rozwiązanie:

Zrobiłem to, więc działa bez magicznej liczby 13:

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

Nie jest łatwo pisać, ale można go użyć w skrypcie.

Mark Plotnick
źródło
miły! Nie wiedziałem o rozszerzeniu, zanim przeczytałem twoją odpowiedź :)
TabeaKischka,
4

Pomyślałem, że awk może to zrobić ładnie, więc poszukałem „awk czytającego dane wejściowe z dwóch plików” i znalazłem artykuł o stosie przepływu jako punkt wyjścia.

Najpierw jest wersja skrócona, a następnie całkowicie skomentowana poniżej. Zajęło to kilka minut. Byłbym zadowolony z udoskonaleń od mądrzejszych ludzi.

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

A oto w pełni udokumentowana wersja powyższego.

# 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
źródło
1
+1 to jedyna odpowiedź, która działa z dowolnymi danymi wejściowymi (tj. Z wierszami, które mogą zawierać tabulatory). Nie sądzę, że można to znacznie ulepszyć / ulepszyć.
don_crissti
2

Niezbyt dobre rozwiązanie, ale udało mi się to zrobić za pomocą

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

gdzie TAB zostaje zastąpione znakiem tabulacji.

unxnut
źródło
Jaka jest rola &&polecenia sed?
coffeMug
1
Pojedynczy &umieszcza to, co jest wyszukiwane (w tym przypadku tabulator). To polecenie po prostu zastępuje kartę na początku dwiema kartami.
unxnut
Musiałem zmienić TABsię \tdo tej pracy w zsh na Debianie Ubuntu.
Działa
2

W Debianie i pochodnych columnma opcję -n nomerge , która pozwala kolumnie robić właściwe rzeczy z pustymi polami. Wewnętrznie columnużywa wcstok(wcs, delim, ptr)funkcji, która dzieli szeroki ciąg znaków na tokeny rozdzielone szerokimi znakami w delimargumencie.

wcstokzaczyna się od pominięcia szerokich znaków delimprzed rozpoznaniem tokena. -nOpcja wykorzystuje algorytm, który nie pominąć początkowe szerokokątny znaki delim.

Niestety, nie jest to zbyt przenośne: -njest specyficzne dla Debiana i columnnie znajduje się w POSIX, najwyraźniej jest to kwestia BSD.

ninjalj
źródło
2

Usuwanie kropek użytych do wypełnienia:

plik1:

ETIAM
SED
MAECENAS
DONEC
SUSPENDISSE

plik2:

Lorem
Proin
Nunc
Quisque
Aenean
Nam
Vivamus
Curabitur
Nullam

Spróbuj tego:

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

I dostaniesz:

ETIAM         Lorem
SED           Proin
MAECENAS      Nunc
DONEC         Quisque
SUSPENDISSE   Aenean
              Nam
              Vivamus
              Curabitur
              Nullam
Jeff Taylor
źródło
To, podobnie jak inne stosowane rozwiązania paste, nie wydrukuje poprawnego wydruku, jeśli są jakieś wiersze zawierające tabulatory. +1 za bycie innym
don_crissti
+1. Czy mógłbyś wyjaśnić, jak działa to rozwiązanie?
Tulains Córdova
1

awkRozwiązanie, które powinno być dość przenośne i powinny pracować dla dowolnej liczby plików wejściowych:

# 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
źródło
Jak tego używasz w plikach 1 i 2? Zadzwoniłem do skryptu paste-awki próbowałem paste file1 file2|paste-awki próbowałem, awk paste-awk file1 file2ale żaden nie działał.
rubo77
Dostajęawk: Line:1: (FILENAME=file1 FNR=1) Fatal: Division by zero
rubo77
@ rubo77: awk -f paste-awk file1 file2powinien działać, przynajmniej dla GNU awk i mawk.
ninjalj
Działa to, chociaż jest nieco inne niż pastemiędzy dwoma rzędami jest mniej miejsca. A jeśli plik wejściowy nie ma wszystkich wierszy tej samej długości, spowoduje to wyrównanie wiersza do prawej
rubo77
@ rubo77: separator pól można ustawić za pomocą opcji-F\\t
ninjalj