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?


@ 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

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

Ż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' ','

3
sed 'N;s/\n/,/' file

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


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)


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.


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*

@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

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


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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.