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

dà

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.