Um melhor comando de colar


11

Eu tenho os dois arquivos a seguir (preenchi as linhas com pontos para que todas as linhas de um arquivo tenham a mesma largura e tornei file1 todas maiúsculas para torná-lo mais claro).

contents of file1:

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

contents of file2

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

Observe que o arquivo2 é maior que o arquivo1.

Quando eu executo este comando:

paste file1 file2

Eu recebo essa saída

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

O que posso fazer para que a saída seja a seguinte?

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

eu tentei

paste file1 file2 | column -t

mas faz isso:

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

não tão feia quanto a saída original, mas incorreta em colunas.


2
pasteestá usando guias na frente das linhas do segundo arquivo. Talvez você precise usar um pós-processador para alinhar as colunas adequadamente.
unxnut

3
paste file1 file2 | column -tn?
Ninjalj

file1 sempre tem colunas de tamanho fixo?
RSFalcon7

@ RSFalcon7 Sim, tem.
Tulains Córdova

Respostas:


17

Supondo que você não tenha caracteres de tabulação em seus arquivos,

paste file1 file2 | expand -t 13

com o arg para -tescolher adequadamente para cobrir a largura máxima desejada da linha no arquivo1.

O OP adicionou uma solução mais flexível:

Eu fiz isso para que funcione sem o número mágico 13:

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

Não é fácil digitar, mas pode ser usado em um script.


legais! Eu não sabia sobre expandir antes de eu ler a sua resposta :)
TabeaKischka

4

Eu pensei que o awk poderia fazer isso muito bem, então pesquisei "awk reading input from two files" e encontrei um artigo sobre o stackoverflow para usar como ponto de partida.

Primeiro é a versão condensada, depois totalmente comentada abaixo. Demorou mais do que alguns minutos para resolver. Eu ficaria feliz com alguns aprimoramentos de pessoas mais inteligentes.

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

E aqui está a versão totalmente documentada do acima.

# 2013-11-05 mike@diehn.net
# 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]:""
  }
}

1
+1 é a única resposta que funciona com entradas arbitrárias (ou seja, com linhas que podem conter tabulações). Eu não acho que isso possa ser significativamente refinado / aprimorado.
22417 Don_crissti

2

Não é uma solução muito boa, mas consegui fazer isso usando

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

onde TAB é substituído pelo caractere de tabulação.


Qual é o papel do &&comando sed?
precisa saber

1
Um único &coloca o que está sendo pesquisado (uma guia nesse caso). Este comando simplesmente substitui a guia no início por duas guias.
unxnut

Eu tive que mudar TABpara \tfazer isso funcionar no zsh no Ubuntu debian. E ele faz só funcionam se file1 tem menos de 15 caracteres
rubo77

2

No Debian e derivativos, columntem uma opção -n nomerge que permite que a coluna faça a coisa certa com campos vazios. Internamente, columnusa a wcstok(wcs, delim, ptr)função, que divide uma cadeia de caracteres ampla em tokens delimitados pelos caracteres largos no delimargumento.

wcstokcomeça pulando caracteres largos delimantes de reconhecer o token. A -nopção usa um algoritmo que não ignora caracteres largos iniciais delim.

Infelizmente, isso não é muito portátil: -né específico do Debian e columnnão está no POSIX, é aparentemente uma coisa do BSD.


2

Retirando os pontos que você usou para preenchimento:

file1:

ETIAM
SED
MAECENAS
DONEC
SUSPENDISSE

arquivo2:

Lorem
Proin
Nunc
Quisque
Aenean
Nam
Vivamus
Curabitur
Nullam

Tente o seguinte:

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

E você receberá:

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

Isso, como as outras soluções utilizadas paste, falhará ao imprimir a saída adequada se houver alguma linha contendo guias. +1 por ser diferente
don_crissti

+1. Você poderia explicar como a solução funciona?
Tulains Córdova 15/02

1

Uma awksolução que deve ser razoavelmente portátil e deve funcionar para um número arbitrário de arquivos de entrada:

# 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
    }
}

Como você usa isso no arquivo1 e no arquivo2? Liguei para o script paste-awke tentei paste file1 file2|paste-awke tentei, awk paste-awk file1 file2mas nenhum funcionou.
rubo77

Eu receboawk: Line:1: (FILENAME=file1 FNR=1) Fatal: Division by zero
rubo77 30/11

@ rubo77: awk -f paste-awk file1 file2deve funcionar, pelo menos para GNU awk e mawk.
Ninjalj 02/12

Isso funciona, embora seja um pouco diferente, pastepois há menos espaço entre as duas linhas. E se o arquivo de entrada tem nem todas as linhas mesmo comprimento, que irá resultar em uma alinhar com o botão direito linha
rubo77

@ rubo77: o separador de campo pode ser definido com-F\\t
ninjalj
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.