Scal dwa pliki linia po linii z symbolem potrójnej rurki separatora „|||”

14

Mam dwa równoległe pliki z taką samą liczbą wierszy w dwóch językach i planuję połączyć te dwa pliki linia po linii z separatorem |||. Np. Dwa pliki są następujące:

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

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

Oczekiwany wynik jest następujący:

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.

Próbowałem pastepolecenia, takiego jak:

paste -d "|||" fileA fileB

Ale zwracane dane wyjściowe zawierają tylko jedną potok, na przykład:

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

Czy jest jakiś sposób na oddzielenie każdej pary linii za pomocą trójnogu |||?

Marszczyć brwi
źródło
8
paste -d '|||' fileA - - fileB < /dev/null
Stéphane Chazelas
5
offtopic, ale twoje tłumaczenia są niepoprawne;) „Ich habe Durst” = Jestem ten, „Ich bin neu” = Jestem nowy ... niekoniecznie oznacza, że ​​jesteś głupi. ... na wypadek, gdybyś faktycznie uczył się niemieckiego ...
dave_alcarin
@ StéphaneChazelas Thx, ale moje dane wyjściowe nadal zawierają tylko jedną rurę ...
Frown
@dave_alcarin Dank sehr!
Zmarszczy

Odpowiedzi:

20

Z pastą POSIX :

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

pastepołączy odpowiednie linie wszystkich plików wejściowych. Tutaj mamy sześć plików, fileAcztery atrapy plików ze standardowych w -i fileB.

Lista ograniczników obejmuje spację, trzy potoki i spację w tej kolejności będą używane pastecyklicznie.

Dla pierwszego wiersza sześciu plików fileAzostanie połączony z pierwszym plikiem zastępczym (który jest niczym, dzięki operatorowi no-op :), produkuje line1-fileA<space>.

Pierwszy plik fikcyjny zostanie połączony z drugim przez potok, produkuj line1-fileA |, a następnie drugi plik fikcyjny z trzecim plikiem fikcyjnym, produkuj line1-fileA ||, trzeci plik fikcyjny z czwartym plikiem fikcyjnym, produkuj line1-fileA |||.

A czwarty plik atrapa z fileB, produkuj line1-fileA ||| line1-fileB.

Te kroki zostaną powtórzone dla wszystkich linii, dając oczekiwany wynik.


Użycie :|jest do pisania na maszynie mniej i głównie w interaktywnej powłoce. W skrypcie należy użyć:

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

aby zapobiec spawnowaniu podpowłoki.

Cuonglm
źródło
1
+1 za :|. sprytna alternatywa dla</dev/null
cas
4
... i +1 za inteligentne użycie 4 fałszywych plików ze standardowego wejścia - - - -, ale następnym razem możesz nawet napisać kilka wierszy dla wyjaśnienia :)
Hastur
Dzięki, ale wciąż otrzymuję wynik za pomocą jednej rury ...
Zmarszczy
@hui, czy wykonałeś polecenie dokładnie tak, jak podano, uwzględniając wszystkie myślniki i spacje? Jaki jest twój system operacyjny?
Stéphane Chazelas
:|paste -d '|' fileA - - fileBdaje bardziej poprawną wersję bez separatora spacji.
Pål GD
7

Cóż, to nie używa sed, awk ani grep, ale możesz to zrobić dość łatwo w bash. Polecenie to:

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

Problem z wklejaniem polega na tym, że separator jest pojedynczym znakiem. Możesz także wstawić pojedynczy znak i użyć sed, aby go przekształcić, ale byłoby to podatne na błędy, jeśli znak już pojawił się w pliku wejściowym.

użytkownik3188445
źródło
2
Twoje rozwiązanie nie będzie działać, jeśli wiersz zawiera znak odwrotnego ukośnika lub zacznie się od myślnika. Chcesz użyć IFS=przed każdym read. Możesz łatwo to zrobić paste. Zobacz moją odpowiedź , a także tę, aby dowiedzieć się, dlaczego warto unikać używania whilepętli w skrypcie powłoki.
cuonglm,
Działa dla mojego pliku. Wielkie dzięki !!!
Zmarszczy
5

Wersja awk (GNU)

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

Za pomocą getlinepolecenia w awkmożesz ustawić $0(wszystkie zmienne dla kolumn) z następnego rekordu wejściowego, jeśli getline < "filename"ustawisz następny $0z określonego pliku.

getline <"plik" Ustaw 0 $ od następnego rekordu pliku; ustaw NF.


Dlaczego Twoja próba nie zadziałała zgodnie z oczekiwaniami? Z man pastemożemy przeczytać

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

ale używa ograniczników po jednym dla każdej kolumny .

Więc polecenie
paste -d '|*|*' fileA fileB fileA fileBpodaje mi linie jako

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


sedRozwiązanie, które proponuję, aby uniknąć nawet jeśli blisko do oryginalnej próbie, gdyż łata uzyskany zachowania do swojego pierwotnego celu:

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

Aby tego uniknąć, ponieważ zastępujesz każdy wzorzec |nowym |||, ale musisz założyć, że symbol potoku ( |) nie jest obecny w twoich danych , w przeciwnym razie musisz poradzić sobie ze specjalnymi przypadkami i uczynić kod bardziej złożonym, aby uniknąć skutków ubocznych.


Wariant z konstrukcją Here String [ 1 ]<<<

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

Ustawiasz 5 ograniczników za pomocą -d ' ||| '(spacja, |, |, |, spacja) i 4 plików zastępczych ( - - - -), które będą pobierać dane z pustego ciągu ''.


Testowane na GNU Awk 4.0.1, wklej (GNU coreutils) 8.21 i sed (GNU sed) 4.2.2

Hastur
źródło
Dzięki, polecenie awk działa!
Zmarszczy
1
Proszę bardzo. Zaktualizowano odpowiedź dodając sedprzykład, aby uniknąć (:-)) i więcej komentarzy.
Hastur
4

Jeśli chcesz uniknąć magii i dramaturgii okrągłych separatorów i plików zastępczych, możesz po prostu dołączyć separator do jednego pliku przed ich wklejeniem:

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

daje

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.
snth
źródło
Lubię to dla uproszczenia. Uważam, że masz na myśli „prepend”, a nie „append”. Zapoznaj się z odpowiedzią Hastura na awk dla tej wersji awk.
Wildcard
Powinieneś zmienić podstawienie procesu na potok, więc nie będziesz mieć limitu liczby obsługiwanych przez niego powłok.
cuonglm
@Wildcard tak, dodawaj, ale przepiszę, aby dołączyć do filea. Myślę, że awk to trochę przesada.
snth
@cuonglm to prawda, ale chciałem uniknąć rur dla przejrzystości. Czułem, że fajka sprawi, że zacznie wyglądać jak atrapy plików, ale masz rację
snth
0

możesz to zrobić w Pythonie również w ten sposób.

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.
c4f4t0r
źródło