Unisci due file riga per riga con il simbolo delimitatore triple pipe "|||"


14

Ho due file paralleli con lo stesso numero di righe in due lingue e ho intenzione di unire questi due file riga per riga con il delimitatore |||. Ad esempio, i due file sono i seguenti:

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

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

L'output atteso è così:

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.

Ho provato il pastecomando come:

paste -d "|||" fileA fileB

Ma l'output restituito contiene solo una pipe come:

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

Esiste un modo per separare ogni coppia di linee dalla trippa |||?


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

5
offtopic, ma le tue traduzioni non sono corrette;) "Ich habe Durst" = I am thisrty, "Ich bin neu" = I am new ... non significa necessariamente che sei sciocco. ... nel caso in cui tu stia imparando il tedesco ...
dave_alcarin,

@ StéphaneChazelas Thx, ma la mia uscita contiene ancora solo una pipe ...
Frown

@dave_alcarin Dank sehr!
Frown,

Risposte:


20

Con incolla POSIX :

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

pasteconcatenerà le righe corrispondenti di tutti i file di input. Qui abbiamo sei file, fileAquattro file fittizi dallo standard in -, e fileB.

L'elenco dei delimitatori include uno spazio, tre pipe e uno spazio in quell'ordine verrà utilizzato in modo pastecircolare.

Per la prima riga di sei file, fileAverrà concatenato con il primo file fittizio (che è nulla, grazie all'operatore no-op :), produce line1-fileA<space>.

Il primo file fittizio verrà concatenato con il secondo da una pipe, producendo line1-fileA |, quindi producendo il secondo file fittizio con il terzo file fittizio, producendo line1-fileA ||, il terzo file fittizio con il quarto file fittizio line1-fileA |||.

E il quarto file fittizio con fileB, produce line1-fileA ||| line1-fileB.

Quel passaggio verrà ripetuto per tutte le linee, dandoti il ​​risultato atteso.


L'uso di :|è per digitare meno e principalmente nella shell interattiva. In uno script, dovresti usare:

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

per impedire la generazione di una subshell.


1
+1 per il :|. intelligente alternativa a</dev/null
cas

4
... e +1 per l'uso intelligente di 4 file fittizi dall'input standard con - - - -, ma la prossima volta puoi persino scrivere un paio di righe per una spiegazione :)
Hastur,

Grazie, ma ho ancora l'output con una pipe ...
Frown,

@hui, hai eseguito il comando esattamente come indicato includendo tutti i trattini e gli spazi? Qual è il tuo sistema operativo?
Stéphane Chazelas,

:|paste -d '|' fileA - - fileBfornisce la versione più corretta senza il delimitatore di spazio.
Pål GD,

7

Bene, questo non usa sed, awk o grep, ma puoi farlo abbastanza facilmente in bash. Il comando è:

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

Il problema con incolla è che il delimitatore è un singolo carattere. Potresti anche inserire un singolo carattere e usare sed per trasformarlo, ma sarebbe un tipo soggetto a errori se il personaggio fosse già apparso nel file di input.


2
La tua soluzione non funzionerà se la riga contiene un carattere barra rovesciata o inizia con il trattino. Si desidera utilizzare IFS=prima di ciascuno read. Puoi farlo facilmente con paste. Vedi la mia risposta , e anche questa per vedere perché dovrebbe evitare di usare whileloop nello script della shell.
cuonglm,

Funziona per il mio file. Molti grazie !!!
Frown,

5

Una versione awk (GNU)

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

Con il getlinecomando in awk, è possibile impostare $0(tutte le variabili per le colonne) dal successivo record di input, se getline < "filename"si imposta il successivo $0dal file specificato.

getline <"file" Imposta $ 0 dal prossimo record di file; impostare NF.


Perché il tuo tentativo non ha funzionato come previsto? Da man pastenoi possiamo leggere

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

ma usa i delimitatori uno per ogni colonna .

Quindi il comando
paste -d '|*|*' fileA fileB fileA fileBmi dà linee come

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


Una sedsoluzione che suggerisco di evitare anche se vicina al tuo tentativo originale, perché corregge il comportamento ottenuto al tuo scopo originale:

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

Per evitare perché sostituisci ogni modello |con quello nuovo |||, ma devi presumere che il simbolo pipe ( |) non sia presente nei tuoi dati , altrimenti devi affrontare casi speciali e rendere un codice più complesso per evitare effetti collaterali.


Una variante con il costrutto Here String [ 1 ]<<<

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

Si impostano 5 delimitatori con -d ' ||| '(spazio, |, |, |, spazio) e 4 file fittizi ( - - - -) che prenderanno i dati dalla stringa vuota ''.


Testato su GNU Awk 4.0.1, incolla (GNU coreutils) 8.21 e sed (GNU sed) 4.2.2


Grazie, il comando awk funziona!
Frown,

1
Prego. Aggiornata la risposta aggiungendo un sedesempio per evitare (:-)) e altri commenti.
Hastur,

4

Se vuoi evitare la magia e il dramma dei delimitatori circolari e dei file fittizi, puoi semplicemente aggiungere il delimitatore a un file prima di incollarli:

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.

Mi piace questo per semplicità. Credo che tu intenda "anteporre", non "aggiungere" però. Guarda la risposta awk di Hastur per la versione awk di questo.
Wildcard il

Dovresti cambiare la sostituzione del processo in una pipe, quindi non avrai il limite per il numero di shell che la supportano.
cuonglm,

@Wildcard sì, anteponi, ma la riscriverò per aggiungerla al file. Penso che Awk sia un po 'eccessivo per questo.
snth

@cuonglm vero, ma volevo evitare le pipe per chiarezza. Mi sono sentito un tubo renderebbe iniziare a guardare come i file fittizi, ma tu sei corretta
snth

0

puoi farlo anche in Python in questo modo.

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.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.