Mesclar dois arquivos linha por linha com o símbolo de barra tripla delimitador “|||”


14

Eu tenho dois arquivos paralelos com o mesmo número de linhas em dois idiomas e planejo mesclar esses dois arquivos linha por linha com o delimitador |||. Por exemplo, os dois arquivos são os seguintes:

Arquivo A:

1Mo 1,1 I love you.
1Mo 1,2 I like you.
Hi 1,3 I am hungry.
Hi 1,4 I am foolish.

Arquivo B:

1Mo 1,1 Ich liebe dich.
1Mo 1,2 Ich mag dich.
Hi 1,3 Ich habe Durst.
Hi 1,4 Ich bin neu.

A saída esperada é assim:

1Mo 1,1 I love you. ||| 1Mo 1,1 Ich liebe dich.
1Mo 1,2 I like you. ||| 1Mo 1,2 Ich mag dich.
Hi 1,3 I am hungry. ||| Hi 1,3 Ich habe Durst.
Hi 1,4 I am foolish. ||| Hi 1,4 Ich bin neu.

Eu tentei o pastecomando como:

paste -d "|||" fileA fileB

Mas a saída retornada contém apenas um canal, como:

1Mo 1,1 I love you. |1Mo 1,1 Ich liebe dich.
1Mo 1,2 I like you. |1Mo 1,2 Ich mag dich.

Existe alguma maneira de separar cada par de linhas por tubo de tripa |||?


8
paste -d '|||' fileA - - fileB < /dev/null
Stéphane Chazelas

5
offtopic, mas suas traduções não estão corretas;) "Ich habe Durst" = Eu sou thisrty, "Ich bin neu" = Eu sou novo ... não significa necessariamente que você é tolo. ... apenas no caso de você está realmente aprendendo alemão ...
dave_alcarin

@ StéphaneChazelas Thx, mas minha saída ainda está contendo apenas um pipe ... #
23515

@dave_alcarin Dank sehr!
Franzir a testa

Respostas:


20

Com a pasta POSIX :

:|paste -d ' ||| ' fileA - - - - fileB

pasteconcatenará as linhas correspondentes de todos os arquivos de entrada. Aqui temos seis arquivos fileA, quatro arquivos fictícios do padrão em- , e fileB.

A lista de delimitadores inclui um espaço, três tubos e um espaço nessa ordem que serão usados ​​por paste circularmente.

Para a primeira linha de seis arquivos, fileAserá concatenada com o primeiro arquivo fictício (o que não é nada, graças ao no -op: operador :), produz line1-fileA<space>.

O primeiro arquivo fictício será concatenado com o segundo por um pipe, produza line1-fileA |, e o segundo arquivo fictício com o terceiro arquivo fictício, produziráline1-fileA || , o terceiro arquivo fictício com o quarto arquivo fictício, produzirá line1-fileA |||.

E o quarto arquivo fictício com fileB, produz line1-fileA ||| line1-fileB.

Essas etapas serão repetidas para todas as linhas, fornecendo o resultado esperado.


O uso de :|é para menos digitação e, principalmente, no shell interativo. Em um script, você deve usar:

</dev/null paste -d ' ||| ' fileA - - - - fileB

para impedir que um subshell seja gerado.


1
+1 para o :|. alternativa inteligente para</dev/null
cas

4
... e uma para o uso inteligente de 4 arquivos fictícios de entrada padrão com - - - -, mas da próxima vez que você pode até escrever um par de linhas para explicação :)
Hastur

Thx, mas eu ainda recebo a saída com um tubo ... #
2325

@hui, você executou o comando exatamente como fornecido, incluindo todos os traços e caracteres de espaço? Qual é o seu sistema operacional?
Stéphane Chazelas

:|paste -d '|' fileA - - fileBfornece a versão mais correta sem o delimitador de espaço.
precisa

7

Bem, isso não usa sed, awk ou grep, mas você pode fazer isso facilmente no bash. O comando é:

(while IFS= read -r a <&3 && IFS= read -r b <&4; do echo "$a ||| $b"; done) 3<fileA 4<fileB

O problema com a pasta é que o delimitador é um único caractere. Você também pode inserir um único caractere e o uso de sed para transformá-lo, mas isso pode ser passível de erro se o caractere já aparecer no arquivo de entrada.


2
Sua solução não funcionará se a linha contiver qualquer caractere de barra invertida ou começar com traço. Você deseja usar IFS=antes de cada um read. Você pode fazer isso facilmente com paste. Veja minha resposta e também esta para ver por que evitar o uso de whileloop no shell script.
cuonglm

Funciona para o meu arquivo. Muitos Thx !!!
olhar severo

5

Uma versão do awk (GNU)

awk '{printf ("%s ||| ", $0); getline < "fileB"; print $0 }' fileA

Com o getlinecomando in awk, é possível definir $0(todas as variáveis ​​para colunas) do próximo registro de entrada, se getline < "filename"você definir o próximo $0no arquivo especificado.

getline <"file" Define $ 0 do próximo registro do arquivo; definir NF.


Por que sua tentativa não funcionou como o esperado? De man pastenós podemos ler

-d, --delimiters=LIST
     reuse characters from LIST instead of TABs

mas usa os delimitadores um para cada coluna .

Então o comando
paste -d '|*|*' fileA fileB fileA fileBme dá linhas como

Hi 1,3 I am hungry.|Hi 1,3 Ich habe Durst.*Hi 1,3 I am hungry.|Hi 1,3 Ich...
Hi 1,4 I am foolish.|Hi 1,4 Ich bin neu.*Hi 1,4 I am foolish.|Hi 1,4 Ich...


Uma sedsolução que sugiro evitar, mesmo que próxima à sua tentativa original, porque corrige o comportamento obtido para o seu objetivo original:

 paste -d '|' fileA fileB | sed 's/|/|||/g'

Para evitar porque você substitui cada padrão |pelo novo |||, mas você deve assumir que o símbolo de barra vertical ( |) não está presente nos seus dados ; caso contrário, você deve lidar com casos especiais e tornar o código mais complexo para evitar efeitos colaterais.


Uma variante com a construção Here String [ 1 ]<<<

 paste -d ' ||| ' fileA - - - - fileB  <<< ''

Você define 5 delimitadores com -d ' ||| '(espaço, |, |, |, espaço) e 4 arquivos fictícios ( - - - -) que coletam dados da sequência vazia ''.


Testado no GNU Awk 4.0.1, cole (GNU coreutils) 8.21 e sed (GNU sed) 4.2.2


Thx, o comando awk funciona!
olhar severo

1
De nada. Atualizada a resposta, adicionando um sedexemplo para evitar (:-)) e mais comentários.
Hastur

4

Se você quiser evitar a magia e o drama de delimitadores circulares e arquivos fictícios, basta anexar seu delimitador a um arquivo antes de colá-los:

paste <(sed 's/$/ |||/' filea) fileb

1Mo 1,1 I love you. ||| 1Mo 1,1 Ich liebe dich.
1Mo 1,2 I like you. ||| 1Mo 1,2 Ich mag dich.
Hi 1,3 I am hungry. ||| Hi 1,3 Ich habe Durst.
Hi 1,4 I am foolish. |||    Hi 1,4 Ich bin neu.

Eu gosto disso por simplicidade. Eu acredito que você quer dizer "anexar", mas não "anexar". Confira a resposta awk de Hastur para a versão awk disso.
Curinga

Você deve alterar a substituição do processo para um tubo, para não ter o limite de número de cartuchos para suportá-lo.
cuonglm

@Wildcard sim, prepend, mas vou reescrevê-lo para adicionar ao filea. Eu acho que awk é um pouco exagerado para isso.
snth

@cuonglm verdade, mas eu queria evitar tubos para maior clareza. Senti um tubo tornaria começar a olhar como os arquivos fictícios, mas você está correto
snth

0

você pode fazer isso em python também dessa maneira.

lines1 = [ line.rstrip() for line in open("file1") ]
lines2 = [ line.rstrip() for line in open("file2") ]
for i in xrange((len(lines1))): print lines1[i] + " ||| " + lines2[i]
... 
1Mo 1,1 I love you. ||| 1Mo 1,1 Ich liebe dich.
1Mo 1,2 I like you. ||| 1Mo 1,2 Ich mag dich.
Hi 1,3 I am hungry. ||| Hi 1,3 Ich habe Durst.
Hi 1,4 I am foolish. ||| Hi 1,4 Ich bin neu.
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.