Przetwarzanie tekstu - łącz co dwa wiersze przecinkami

35

Mam w pliku ponad 1000 wierszy. Plik rozpoczyna się w następujący sposób (dodano numery linii):

Station Name
Station Code
A N DEV NAGAR
ACND
ABHAIPUR
AHA
ABOHAR
ABS
ABU ROAD
ABR

Muszę przekonwertować to do pliku z wpisami oddzielonymi przecinkami, łącząc się co dwa wiersze. Ostateczne dane powinny wyglądać

Station Name,Station Code
A N DEV NAGAR,ACND
ABHAIPUR,AHA
ABOHAR,ABS
ABU ROAD,ABR
...

Próbowałem napisać skrypt powłoki, a następnie echoprzecinkiem. Ale wydaje mi się, że prostszy skuteczny liniowiec wykonałby zadanie tutaj, może być w sed/ awk.

Jakieś pomysły?

Mt.
źródło
@ l0b0 Zredagowałeś uwagę OP, że numery linii są „tylko tam dla wyjaśnienia” ...
jasonwryan 18.10
@jasonwryan Przepraszam, myślałem, że linie były tam dla wyjaśnienia. Błąd analizy w linii 0.
10b0 18.10
stackoverflow.com/questions/9605232/merge-two-lines-into-one
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Odpowiedzi:

39

Po prostu użyj cat(jeśli lubisz koty ;-)) i paste:

cat file.in | paste -d, - - > file.out

Objaśnienie: pasteodczytuje z wielu plików i wkleja razem odpowiednie linie (linia 1 z pierwszego pliku z linią 1 z drugiego pliku itp.):

paste file1 file2 ...

Zamiast nazwy pliku możemy użyć -(myślnik). pastepobiera pierwszy wiersz z pliku1 (który jest standardem). Następnie chce odczytać pierwszy wiersz z pliku2 (który jest również stdin). Ponieważ jednak pierwszy wiersz stdin został już odczytany i przetworzony, w strumieniu wejściowym czeka teraz druga linia stdin, która z pasteradością przylega do pierwszego. Ta -dopcja ustawia separator jako przecinek, a nie tabulator.

Możesz też zrobić

cat file.in | sed "N;s/\n/,/" > file.out

PS Tak, można uprościć powyższe do

< file.in sed "N;s/\n/,/" > file.out

lub

< file.in paste -d, - - > file.out

który ma tę zaletę, że nie używa cat.

Jednak nie użyłem tego idiomu celowo , ze względu na przejrzystość - jest mniej gadatliwy i lubię cat(CATS ARE NICE). Więc proszę nie edytować.

Alternatywnie, jeśli wolisz wklejanie niż koty (wklej to polecenie łączenia plików w poziomie, podczas gdy kot łączy je w pionie), możesz użyć:

paste file.in | paste -d, - -
styczeń
źródło
Żeby to jeszcze raz wspomnieć. Numery linii nie są częścią pliku :)
mtk
paste Komenda doskonale działa, można proszę dać trochę więcej wyjaśnień na ten temat. Łączniki ???
mtk
2
Łączniki oznaczają „czytaj ze standardowego”. Jeśli to samo źródło wejściowe zostanie powtórzone, wklej wie, że odczytuje z niego kilka razy w wierszu wyniku.
dubiousjim
@sch: cool edit, nie będę go dotykać :-)
styczeń
1
W odniesieniu do twojego catargumentu. Nie sed "N;s/\n/,/" file.in > file.outdziała?
Bernhard
8

Jeśli ktoś lądujący tutaj chce połączyć wszystkie linie w jeden liniowiec CSV, spróbuj

cat file | tr '\n' ','
Darren Weber
źródło
3
sed 'N;s/\n/,/' file

Używając sed, łącz (N) co 2 linie i zamień nowy wiersz (\ n) na „,”.

Guru
źródło
3
paste -sd ',\n' file.in > file.out

Zauważ też, że ponieważ zastępujemy tylko jeden znak innym (każdy nowy wiersz przecinkiem), możemy pracować nad plikiem wejściowym:

paste -sd ',\n' file.in 1<> file.in

(ale uwaga: może nie działać na systemach innych niż Unix, które mają terminatory CRLF (takie jak Microsoft), które niektóre emulowane POSIX pastemogą traktować w sposób inny niż Unix)

Stéphane Chazelas
źródło
Co 1to tutaj robi 1<>? czy to literówka?
αғsнιη
@ αғsнιη, zobacz to
iruvar
@iruvar dziękuję
αғsнιη
2

Oto jedna linijka (choć potencjalnie milion-poleceń-run-er) wykorzystująca czysty Bash:

(IFS=; while read -r name; do read -r code; printf '%s\n" "$name,$code"; done < file.in) > file.out

Używam podpowłoki (nawiasu), aby nie musiałem przechowywać i przywracać IFS. Które inaczej należy zrobić, aby nie zepsuć środowiska użytkownika na wypadek, gdyby źródło zostało pozyskane. Alternatywą byłoby przekazanie tego nowego IFS tylko readtak IFS= read -r name, jak w ,IFS= read -r code .

Fakt, że wszystkie polecenia w pętli są wbudowane w powłokę, sprawia, że ​​jego wydajność jest akceptowalna i jest nawet szybsza niż inne rozwiązania dla małych plików. Ale wiele osób uważa to za złą praktykę i należy zachować ostrożność przy uogólnianiu tego na cokolwiek innego.

Usunięte
źródło
ogólnie za używanie podpowłoki do lokalizowania zmian środowiska. Ale w tym przypadku nie jest to konieczne: możesz zamiast tego zrobić while IFS='\n' read -r name; do IFS='\n' read -r code ... done < file.in, co często widzę w skryptach powłoki. -rFlagi na readdrodze „interpretacji znaku«\», po której następuje znak«n»w strumieniu stdin jako dwóch znaków, a nie jako nową linią.” Być może tworzenie podpowłoki może być bardziej estetyczne niż powtarzanie IFS='\n'.
dubiousjim
@dubiousjim: Technicznie -rpoprawiono rozwiązanie. Świetny! Nie jestem fanem pomysłu IFSdwukrotnego przejścia zmienionego . Gdybym użył jednego czytania, bardzo fajnie, ale nie dwa razy. Oczywiście to kwestia opinii . Powiedziałbym, że używanie podpowłoki jest nieco lepsze od ogólnej wiedzy Bash, więc wielu ludzi będzie miało problem ze zrozumieniem jej celu. To zła rzecz.
Usunięte
2

Dla pełnego zestawu odpowiedzi możliwym awkrozwiązaniem może być:

awk 'NR%2==1 {printf $0","} NR%2==0 { print $0}' *file*
Bernhard
źródło
@downvoter: Co jest złego w mojej odpowiedzi, aby zasłużyć na głosowanie? Jak można to poprawić?
Bernhard
Może dlatego, że leniwi printf? Błąd w rzadkim przypadku, gdy nazwa stacji zawiera specyfikator formatu. (Zobacz przykład pastebin.com/wgxFttrJ .) Ale to tylko przypuszczenie, że opinia nie pochodzi ode mnie.
manatwork
1

Siwy stary kasztan awkidiomu

awk '{ORS=NR%2?",":"\n";print}' file
Station Name,Station Code
A N DEV NAGAR,ACND
ABHAIPUR,AHA
ABOHAR,ABS
ABU ROAD,ABR
iruvar
źródło
awk '{ORS=NR%2?",":"\n"};1'jest krótszy i bardziej
idiomiczny
@cuonglm, wątpię w to. W tym przypadku jest to nadal jedna linijka, mimo printże intencja jest jasna. 1jest tak samo jasne dla starych awkrąk takich jak ja, ale wolęprint
iruvar
To było pierwsze proste rozwiązanie, które znalazłem i które można łatwo skonfigurować do więcej niż 2 linii. sedPrzed rozpoczęciem poszukiwań walczyłem przez chwilę, ale awkułatwiałem łączenie co 4 linie. Uratowała mi podróż do $EDITOR!
opello
0

Możliwe również z perlem,

perl -pe 's/^\d+\.\s+//;$.&1?chomp:print","' file

stokrotka
źródło
0

Na przykład:

seq 0 70 | xargs -L 2 | sed 's/ /,/g'

Wyjście: (uwaga: xargs -L number_of_columnsdziała ładnie z większością dowolnej liczby kolumn, nie tylko co dwie linie)

0,1
2,3
4,5
6,7
8,9
10,11
12,13
14,15
16,17
18,19
20,21
22,23
24,25
26,27
28,29
30,31
32,33
34,35
36,37
38,39
40,41
42,43
44,45
46,47
48,49
50,51
52,53
54,55
56,57
58,59
60,61
62,63
64,65
66,67
68,69
70
jmunsch
źródło