Como mesclar cada duas linhas em uma da linha de comando?


152

Tenho um arquivo de texto com o seguinte formato. A primeira linha é a "CHAVE" e a segunda linha é o "VALOR".

KEY 4048:1736 string
3
KEY 0:1772 string
1
KEY 4192:1349 string
1
KEY 7329:2407 string
2
KEY 0:1774 string
1

Preciso do valor na mesma linha da chave. Portanto, a saída deve ser semelhante a esta ...

KEY 4048:1736 string 3
KEY 0:1772 string 1
KEY 4192:1349 string 1
KEY 7329:2407 string 2
KEY 0:1774 string 1

Seria melhor se eu pudesse usar algum delimitador como $ou ,:

KEY 4048:1736 string , 3

Como faço para mesclar duas linhas em uma?


Há muitas maneiras de fazer isso! Eu fiz um pequeno banco com pr, paste, awk, xargs, sedepure bash ! ( xargsé o mais lento, mais lento que o bash !)
F. Hauri

Respostas:


187

awk:

awk 'NR%2{printf "%s ",$0;next;}1' yourFile

observe que há uma linha vazia no final da saída.

sed:

sed 'N;s/\n/ /' yourFile

Não funciona com saída colorida. Tentei de tudo neste Q&A e nada funcionou quando a saída foi colorida ansi. Testado no Ubuntu 13.04
Leo Gallucci

1
@elgalu: Porque as cores ANSI são apenas um monte de combinações de caracteres de escape. Faça um hexedit em tal saída, para ver o que você tem.
not2qubit

7
Esta solução awk pode quebrar se printfstrings de expansão como %sforem encontradas dentro $0. Essa falha pode ser evitada assim:'NR%2{printf "%s ",$0;next;}1'
ghoti

9
Como é muito difícil pesquisar no Google, o que significa 1depois da chave de fechamento?
erikbwork


249

paste é bom para este trabalho:

paste -d " "  - - < filename

10
Acho que essa é a melhor solução apresentada, apesar de não usar nem sed nem awk. Na entrada que é um número ímpar de linhas, a solução awk de Kent pula a nova linha final, sua solução sed pula a linha final em sua totalidade e minha solução repete a última linha. paste, por outro lado, se comporta perfeitamente. +1.
ghoti

8
Costumo usar, cutmas sempre esqueço paste. É demais para esse problema. Eu precisava combinar todas as linhas de stdin e fiz isso facilmente com paste -sd ' ' -.
Clint Pachl

4
Simples e lindo!
krlmlr

8
então -significa stdin, então paste - -significa ler de stdin, então ler de stdin, você pode empilhar quantos deles quiser, eu espero.
ThorSummoner

1
Sim, @ThorSummoner ... tive que colar cada três linhas em uma única linha e colar - - - e funcionou perfeitamente.
Daniel Goldfarb

38

Alternativa para sed, awk, grep:

xargs -n2 -d'\n'

Isso é melhor quando você deseja unir N linhas e precisa apenas de saída delimitada por espaço.

Minha resposta original foi xargs -n2que separa em palavras ao invés de linhas. -dpode ser usado para dividir a entrada por qualquer caractere único.


4
Este é um bom método, mas funciona com palavras, não com linhas. Para fazer funcionar nas linhas, poderia adicionar-d '\n'
Don Hatch

2
Uau, sou um xargsusuário regular, mas não sabia disso. Ótima dica.
Sridhar Sarnobat

1
Eu amo isto. Tão limpo.
Genius Billionaire

29

Existem mais maneiras de matar um cachorro do que enforcamento. [1]

awk '{key=$0; getline; print key ", " $0;}'

Coloque o delimitador que desejar entre as aspas.


Referências:

  1. Originalmente "Muitas maneiras de esfolar o gato", revertido para uma expressão mais velha e potencialmente originária que também não tem nada a ver com animais de estimação.

Eu amo essa solução.
luis.espinal

5
Como dono de um gato, não aprecio esse tipo de humor.
witkacy26

4
@ witkacy26, Expressão ajustada de acordo com sua preocupação.
ghoti

Eu amo essa solução awk, mas não entendo como funciona: S
Rubendob

@Rubendob - awk lê cada linha de entrada e a coloca na variável $0 . O getlinecomando também pega "a próxima" linha de entrada e a coloca $0. Assim, a primeira instrução pega a primeira linha e o comando print concatena o que foi salvo na variável keycom uma string contendo uma vírgula, junto com a linha que foi buscada usando getline. Mais claro? :)
ghoti

13

Aqui está minha solução no bash:

while read line1; do read line2; echo "$line1, $line2"; done < data.txt

11

Embora pareça que as soluções anteriores funcionariam, se uma única anomalia ocorrer no documento, a saída será fragmentada. Abaixo está um pouco mais seguro.

sed -n '/KEY/{
N
s/\n/ /p
}' somefile.txt

3
Por que é mais seguro? O que /KEY/fazer? O que o pfaz no final?
Stewart

as /KEY/pesquisas pela linha com o KEY. o pimprime o resultado. é mais seguro porque só aplica a operação em linhas com um KEYnele.
minghua

11

Aqui está outra maneira com awk:

awk 'ORS=NR%2?FS:RS' file

$ cat file
KEY 4048:1736 string
3
KEY 0:1772 string
1
KEY 4192:1349 string
1
KEY 7329:2407 string
2
KEY 0:1774 string
1

$ awk 'ORS=NR%2?FS:RS' file
KEY 4048:1736 string 3
KEY 0:1772 string 1
KEY 4192:1349 string 1
KEY 7329:2407 string 2
KEY 0:1774 string 1

Conforme indicado por Ed Morton nos comentários, é melhor adicionar chaves para segurança e parênteses para portabilidade.

awk '{ ORS = (NR%2 ? FS : RS) } 1' file

ORSsignifica separador de registro de saída. O que estamos fazendo aqui é testar uma condição usando o NRque armazena o número da linha. Se o módulo de NRé um valor verdadeiro (> 0), então definimos o separador de campo de saída para o valor de FS(separador de campo), que por padrão é espaço, caso contrário atribuímos o valor deRS (Separador de Registro), que é nova linha.

Se você deseja adicionar ,como separador, use o seguinte:

awk '{ ORS = (NR%2 ? "," : RS) } 1' file

1
Definitivamente, a abordagem certa então +1, mas eu me pergunto qual é a condição que está sendo avaliada para invocar a ação padrão de imprimir o registro. É que a atribuição foi bem-sucedida? É simples ORSe está sendo tratado como trueuma vez que ORS obtém um valor diferente de zero ou uma string nula e desperta a suposição correta de que deveria ser uma comparação em vez de uma comparação numérica? É outra coisa? Eu realmente não tenho certeza e então o teria escrito como awk '{ORS=(NR%2?FS:RS)}1' file. Coloquei entre parênteses a expressão ternária para garantir a portabilidade também.
Ed Morton de

1
@EdMorton Sim, acabei de ver alguns votos positivos sobre esta resposta que estava prestes a atualizá-la para incluir as chaves de segurança. Também adicionará parênteses.
Jaypal Singh

7

"ex" é um editor de linha programável que está na mesma família que sed, awk, grep, etc. Acho que pode ser o que você está procurando. Muitos clones / sucessores modernos do vi também possuem um modo vi.

 ex -c "%g/KEY/j" -c "wq" data.txt

Isso diz que para cada linha, se ela corresponder a "KEY", execute um j oin da linha seguinte. Depois que completa comando (contra todas as linhas), emitir um w rito e q uit.


4

Se Perl for uma opção, você pode tentar:

perl -0pe 's/(.*)\n(.*)\n/$1 $2\n/g' file.txt

Diz ao -0perl para definir o separador de registros ( $/)para nulo, para que possamos abranger várias linhas em nosso padrão de correspondência. As páginas de manual são um pouco técnicas demais para eu descobrir o que isso significa na prática.
Sridhar Sarnobat

4

Você pode usar o awk assim para combinar 2 pares de linhas:

awk '{ if (NR%2 != 0) line=$0; else {printf("%s %s\n", line, $0); line="";} } \
     END {if (length(line)) print line;}' flle

4

Outras soluções usando o vim (apenas para referência).

Solução 1 :

Abra o arquivo no vim vim filenamee execute o comando:% normal Jj

Este comando é muito fácil de entender:

  • %: para todas as linhas,
  • normal: executa o comando normal
  • Jj: execute o comando Join e vá para a linha abaixo

Depois disso, salve o arquivo e saia com :wq

Solução 2 :

Execute o comando no shell, vim -c ":% normal Jj" filenamesalve o arquivo e saia com :wq.


Também norm!mais robusto que normalno caso de Jser remapeado. +1 para solução vim.
qeatzy

@qeatzy Obrigado por me ensinar isso. Muito feliz em saber disso. ^ _ ^
Jensen

3

Você também pode usar o seguinte comando vi:

:%g/.*/j

Ou mesmo :%g//jporque tudo que você precisa é uma correspondência para que a junção seja executada, e uma string nula ainda é uma regex válida.
ghoti de

1
@ghoti, No Vim, ao usar apenas //, o padrão de pesquisa anterior será usado. Se não houver um padrão anterior, o Vim simplesmente relata um erro e não faz nada. A solução de Jdamian funciona o tempo todo.
Tzunghsing David Wong de

1
@TzunghsingDavidWong - é um bom indicador para usuários do vim. Para minha sorte, nem a pergunta nem essa resposta mencionaram vim.
ghoti de

3

Uma ligeira variação na resposta de glenn jackman usando paste: se o valor da -dopção delimitador contém mais de um caractere, pastealterna entre os caracteres um por um e, combinado com as -sopções, continua fazendo isso enquanto processa o mesmo arquivo de entrada.

Isso significa que podemos usar o que quisermos como separador mais a sequência de escape \npara mesclar duas linhas por vez.

Usando uma vírgula:

$ paste -s -d ',\n' infile
KEY 4048:1736 string,3
KEY 0:1772 string,1
KEY 4192:1349 string,1
KEY 7329:2407 string,2
KEY 0:1774 string,1

e o cifrão:

$ paste -s -d '$\n' infile
KEY 4048:1736 string$3
KEY 0:1772 string$1
KEY 4192:1349 string$1
KEY 7329:2407 string$2
KEY 0:1774 string$1

O que isso não pode fazer é usar um separador que consiste em vários caracteres.

Como um bônus, se o pastefor compatível com POSIX, isso não modificará a nova linha da última linha no arquivo, portanto, para um arquivo de entrada com um número ímpar de linhas como

KEY 4048:1736 string
3
KEY 0:1772 string

paste não adicionará o caractere de separação na última linha:

$ paste -s -d ',\n' infile
KEY 4048:1736 string,3
KEY 0:1772 string

1
nawk '$0 ~ /string$/ {printf "%s ",$0; getline; printf "%s\n", $0}' filename

Isso é lido como

$0 ~ /string$/  ## matches any lines that end with the word string
printf          ## so print the first line without newline
getline         ## get the next line
printf "%s\n"   ## print the whole line and carriage return

1

No caso em que precisei combinar duas linhas (para facilitar o processamento), mas permitir que os dados passassem do específico, achei isso útil

data.txt

string1=x
string2=y
string3
string4
cat data.txt | nawk '$0 ~ /string1=/ { printf "%s ", $0; getline; printf "%s\n", $0; getline } { print }' > converted_data.txt

a saída então se parece com:

convertido_data.txt

string1=x string2=y
string3
string4

1

Outra abordagem usando o vim seria:

:g/KEY/join

Isso se aplica a join(à linha abaixo) a todas as linhas que contêm a palavra KEY. Resultado:

KEY 4048:1736 string 3
KEY 0:1772 string 1
KEY 4192:1349 string 1
KEY 7329:2407 string 2
KEY 0:1774 string 1

0

A maneira mais simples é aqui:

  1. Remova as linhas pares e escreva-as em algum arquivo temporário 1.
  2. Remova as linhas estranhas e escreva-as em algum arquivo temporário 2.
  3. Combine dois arquivos em um usando o comando de colar com -d (significa excluir espaço)

sed '0~2d' file > 1 && sed '1~2d' file > 2 && paste -d " " 1 2

0
perl -0pE 's{^KEY.*?\K\s+(\d+)$}{ $1}msg;' data.txt > data_merged-lines.txt

-0engole todo o arquivo em vez de lê-lo linha por linha;
pEenvolve o código com loop e imprime a saída, consulte os detalhes em http://perldoc.perl.org/perlrun.html ;
^KEYcorresponder a "KEY" no início da linha, seguido por correspondência não gananciosa de qualquer coisa ( .*?) antes da sequência de

  1. um ou mais espaços \s+de qualquer tipo, incluindo quebras de linha;
  2. um ou mais dígitos (\d+)que capturamos e reinserimos posteriormente como $1;

seguido pelo fim da linha $.

\Kexclui convenientemente tudo em seu lado esquerdo da substituição, então { $1}substitui apenas a sequência 1-2, consulte http://perldoc.perl.org/perlre.html .


0

Uma solução mais geral (permite que mais de uma linha de acompanhamento seja unida) como um script de shell. Isso adiciona uma linha entre cada um, porque eu precisava de visibilidade, mas isso é facilmente remediado. Este exemplo é onde a linha "chave" terminou em: e nenhuma outra linha terminou.

#!/bin/bash
#
# join "The rest of the story" when the first line of each   story
# matches $PATTERN
# Nice for looking for specific changes in bart output
#

PATTERN='*:';
LINEOUT=""
while read line; do
    case $line in
        $PATTERN)
                echo ""
                echo $LINEOUT
                LINEOUT="$line"
                        ;;
        "")
                LINEOUT=""
                echo ""
                ;;

        *)      LINEOUT="$LINEOUT $line"
                ;;
    esac        
done

-1

Experimente a seguinte linha:

while read line1; do read line2; echo "$line1 $line2"; done <old.txt>new_file

Coloque o delimitador no meio

"$line1 $line2";

por exemplo, se o delimitador for |, então:

"$line1|$line2";

Esta resposta não está adicionando nada não fornecido na resposta do Hai Vu que foi postada 4 anos antes da sua.
fedorqui 'SO pare de prejudicar'

Concordo parcialmente, procuro acrescentar explicações e mais genéricas Não vou editar o arquivo antigo também. Obrigado pela sua sugestão
Suman

-2

Você pode usar xargsassim:

xargs -a file

% cat> arquivo abc% xargs -a arquivo abc% Funciona para mim
RSG

Faz alguma coisa, sim, mas não o que o OP pediu. Especificamente, ele une o máximo de linhas possível. Você pode realmente conseguir o que deseja, xargs -n 2mas esta resposta não explica isso de forma alguma.
tripleee
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.