Jaki jest dobry sposób na filtrowanie pliku tekstowego w celu usunięcia pustych linii?

11

Mam plik .csv (na komputerze Mac), który ma kilka pustych linii, np .:

"1", "2", "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum 

lorem ipsum ","2","3","4"
"1", "2", "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum 

lorem ipsum ","2","3","4"

Które chcę przekonwertować na:

"1", "2", "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum ","2","3","4"
"1", "2", "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum  lorem ipsum ","2","3","4"

Wiem, że musi być jeden liniowiec, ale nie znam awk ani sed. Wszelkie wskazówki bardzo mile widziane!

pitosalas
źródło
1
Zgodnie z tą próbką faktycznie chcesz usunąć osadzone podziały linii z pól. Czy to jest poprawne? Innymi słowy, istnieje 6 linii wejściowych i powinny być 2 linie wyjściowe?
manatwork
Tak, dokładnie tego chcę się pozbyć: osadzone znaki nowej linii w cytowanym ciągu.
pitosalas
Więc potrzebujesz czegoś, co usuwa znaki nowej linii w cudzysłowie. Będzie to trochę bardziej skomplikowane, ponieważ potrzebujesz wyrażenia regularnego z wieloma wierszami.
tongpu

Odpowiedzi:

11

-vAby to zrobić, możesz użyć trybu grep (dopasowanie odwrócone):

grep -v '^$' old-file.csv > new-file.csv

Pamiętaj, że muszą to być różne pliki, ze względu na działanie przekierowań powłoki. Plik wyjściowy jest otwierany (i opróżniany) przed odczytaniem pliku wejściowego. Jeśli masz moreutils (domyślnie nie w systemie Mac OS X), możesz skorzystać spongez obejścia tego:

grep -v '^$' file.csv | sponge file.csv

Ale oczywiście trudniej jest wrócić, jeśli coś pójdzie nie tak.

Jeśli „puste wiersze” faktycznie mogą zawierać spacje (brzmi jak one), możesz zamiast tego użyć tego:

egrep -v '^[[:space:]]*$' old-file.csv > new-file.csv

To zignoruje puste linie, a także linie zawierające tylko białe znaki. Możesz oczywiście wykonać na nim tę samą spongetransformację.

derobert
źródło
Dzięki .... Nie usunąłem żadnych pustych linii ... Może ^ $ nie pasuje? Ale zgodnie z moją najlepszą wiedzą linie są puste. Pamiętaj, że to cdv stworzony przez excla na komputerze Mac ... Czy to coś mówi? (Nie uciekaj krzycząc, bo powiedziałem Excel :)
pitosalas
@pitosalas Prawdopodobnie nie są to puste linie. Spróbuj zmienić go na egrep -v '^[[:space:]]*$'... uwaga grep -> egrep i dziwny nowy wzór
derobert
Nie działało. Usunąłem kilka podwójnych cytatów i zrobiłem bałagan ...
pitosalas
@pitosalas Nie jestem pewien, jak usuną podwójne cudzysłowy. Powinno być w stanie usunąć tylko białe znaki. I rzeczywiście tak właśnie działa, gdy
testuję
@pitosalas możesz sprawdzić, czy któreś z tych poleceń wyrzuca coś, co wygląda rozsądnie (w przeciwieństwie do bełkotu): iconv -f utf16le file.csv | headlubiconv -f utf16be file.csv | head
derobert
8

Najłatwiejsza opcja jest właśnie grep .. Tutaj kropka oznacza „dopasuj wszystko”, więc jeśli linia jest pusta, nie jest dopasowana. W przeciwnym razie drukuje całą linię bez zmian.

Onturenio
źródło
6

Aby usunąć puste linie na miejscu za pomocą ksh93:

sed '/./!d' file 1<>; file

<>;Operatorem przekierowania jest specyficzna ksh93 i jest taka sama jak standardowego <>operatora tą różnicą, że obcina ksh plik po komendzie został zakończony.

sed '/./!d'jest skomplikowanym sposobem pisania grep ., ale niestety GNU grep przynajmniej narzeka, jeśli jego stdout wskazuje ten sam plik co stdin. Powiedziałbyś, że można napisać:

grep . file | cat 1<>; file

Ale niestety jest błąd w ksh93 (przynajmniej moja wersja (93u +)), w tym przypadku plik wydaje się w tym przypadku obcięty do zera.

grep . file | { cat; } 1<>; file

Wygląda na to, że omija ten błąd, ale teraz jest znacznie bardziej skomplikowany niż polecenie sed.

Stéphane Chazelas
źródło
Połącz odpowiedzi w jeden dobrze sformatowany wpis z krótkim przewodnikiem określającym, kiedy należy zastosować każde rozwiązanie. Różne podejścia do różnych problemów pomieszane razem w płynnych odpowiedziach sprawiają, że pytanie to jest trochę katastrofą do przeczytania.
Caleb,
@Caleb, Wszystko sprowadza się do tego, że pytanie jest bardzo niejasne, więc wszystkie odpowiedzi dotyczą różnych interpretacji pytania. Dla każdej odpowiedzi próbowałem powiedzieć, na które pytanie próbuje odpowiedzieć.
Stéphane Chazelas,
Po prostu FYI: Próbowałem, awk '/./' file 1<>; filektóry zadziałał. Dla mnie to jeszcze wyraźniejsze niżsed '/./!d'
grebneke
5

Oto Perljedna linijka:

perl -pi -e 's/^\s*\n//' yourfile

EDYCJA: Ulepszony kod oparty na komentarzach ruakh poniżej.

Joseph R.
źródło
1
Lubperl -ni -e '/./ and print' yourfile
derobert
1
@peterph $jest kotwicą (tj. o zerowej szerokości), więc wyklucza nową linię. Co do zbędnej przestrzeni, to jest powód, dla którego dodałem /x, że nie chcę Perlpróbować interpolować „$ \” w wyrażeniu regularnym
Joseph R.
1
Nie potrzebujesz $, biorąc pod uwagę, że masz \n. (Alternatywnie - nie potrzebujesz \n, biorąc pod uwagę, że masz \s*i $, ale myślę, że s/^\s*\n//wyjaśnia, że ​​nowa linia jest usuwana.) Nie potrzebujesz również /m; nie ma to wpływu na to polecenie. A kiedy pozbędziesz $się przestrzeni i przestrzeni, nie będziesz jej potrzebować /x.
ruakh
1
@JosephR .: \nSam można usunąć; czego nie można zrobić, to usunąć zarówno $ i\n . s/^\s*//Miałby więc problem, który opisujesz, ale s/^\s*$//byłby w porządku, z powodu \s*i $. (Czy rozumiesz, co mam na myśli?)
ruakh
1
@JosephR .: Może się zdarzyć, $ że dopasuje się przed nową linią (pod warunkiem, że /mflaga jest włączona, albo nowa linia jest ostatnim znakiem ciągu lub obydwoma), ale może także pasować do końca łańcucha. Na przykład "abc" =~ m/^abc$/prawda. W przypadku \s*$, \s*jest chciwy co jeść aż do nowej linii, a następnie $dopasowuje koniec-of-string. (Ale myślę, że s/^\s*\n//i tak jest jaśniejsze, więc twoja odpowiedź jest w porządku, tak jak jest teraz.)
ruakh
5

W oparciu o wyjaśnienie w komentarzach do twojego pytania, coś takiego:

awk -v RS= -v ORS= 1

może robić co chcesz.

Pusty separator rekordów jest specjalnym przypadkiem, który mówi, awkże rekordy mają być akapitami (oddzielonymi sekwencjami pustych linii). Ustawienie separatora rekordów wyjściowych również na pusty ciąg oznacza, że ​​treść tych akapitów (bez separatorów) należy połączyć. 1jest tylko prawdziwym warunkiem wydrukowania każdego rekordu.

Pominąłoby to jednak końcowy znak nowej linii, dzięki czemu można wykonać:

awk -v RS= -v ORS= '1;END{if (NR) printf "\n"}'
Stéphane Chazelas
źródło
3

Wiem, że byłoby łatwiej, gdybym dał plik, ale niestety zawierał poufne informacje, których nie mogłem udostępnić. W międzyczasie napisałem mi skrypt rubinowy, który wydawał się załatwić sprawę:

require 'csv'
c = CSV.open("outfile1.csv", "w")
CSV.foreach("data.csv", :encoding => 'windows-1251:utf-8') do |row|
  row = row.map { |a| a.class == String ? a.gsub(/\r/, '') : a}
  c << row
end
c.close

Dziękujemy wszystkim za pomoc!

pitosalas
źródło
2
awk '
    length == 0 {next} 
    /^[^"]/ && /"$/ {print; next} 
    {printf("%s", $0)}
' filename

produkuje

"1", "2", "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum ","2","3","4"
"1", "2", "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum ","2","3","4"
Glenn Jackman
źródło
2

Znalazłem pomysł na możliwe rozwiązanie dotyczące przepełnienia stosu .

sed -i ':a;N;$!ba;s/[^"]\n\s*\n/ /g' file.csv

Prawdopodobnie powinieneś wykonać kopię zapasową pliku csv przed przetestowaniem go, ale przynajmniej w podanym przykładzie działa on bezbłędnie.

Odpowiednie wyjaśnienie na temat wewnętrznego działania tego wyrażenia znajduje się w odpowiedzi, właśnie go edytowałem, aby wyszukać linie, które nie kończą się znakiem "( [^"]\n).

tongpu
źródło
1

Jeśli z własnej odpowiedzi chcesz usunąć znaki nowej linii zawarte w ciągach cytowanych, możesz:

 perl -0777 -pe 's/".*?"/$_=$&;s:\n::g;$_/gse'

Możesz także użyć -iflagi perla do edycji plików na miejscu .

 perl -0777 -pe 's/".*?"/$_=$&;s:\n::g;$_/gse' file1 file2...

Lub z GNU awk:

 awk -v RS=\" 'NR%2==0 {gsub("\n","")}; {printf "%s", $0 RT}'

lub:

 awk -vRS=\" '1-NR%2{gsub("\n","")}{ORS=RT}1'

(jeśli walczysz o najkrótszą)

Zauważ, że te zakładają, że nie są tam uciekł podwójnych znaków cudzysłowu w wejściu.

Stéphane Chazelas
źródło
0

Wygląda na to, że chcesz więcej niż usuwanie pustych linii, ale usuń każdą sekwencję 2 lub więcej znaków nowej linii.

Co możesz zrobić z perlem:

perl -0777 -pe 's/\n{2,}//gs' file

Możesz także użyć -iflagi perla do edycji plików na miejscu .

perl -0777 -pi -e 's/\n{2,}//gs' file1 file2...
Stéphane Chazelas
źródło
0

Istnieje coraz krótszy sposób usuwania pustych linii w AWK:

awk 'NF' file

Ale aby uzyskać pożądany wynik, wystarczy jeden prosty linijka:

awk 'NF {printf("%s ", $0); i++;} !(i % 2) {printf("\n");}' file

Wyjaśnienie

W AWKpusta linia oznacza, że ​​wiersz / rekord nie ma pól, co oznacza, że NFzmienna (Number of Fields) ma wartość zero. Jedna linijka powyżej zostanie wykonana tylko podczas NF > 0drukowania wszystkich linii, ale pustych.

i++Jest niepusty linie licznik.

!(i % 2)Jest stosowany w celu wydrukowania dwa kolejne niepuste linie na drodze żądanego wyjścia, to znaczy za każdym razem wielokrotnością 2 zostanie znaleziony, modulorachunku !(i % 2)rentowności 1, co kończy konkatenacji dwóch niepustych wierszy.

Marcelo Augusto
źródło
Mój błąd! Przepraszam. Nie przeczytałem całego jego pytania i pożądanego rezultatu. Odpowiedź jest już naprawiona. Dzięki. :-)
Marcelo Augusto
0

Możesz używać Vima w trybie Ex:

ex -sc v/./d -cx b.csv
  1. v/./ znajdź puste linie

  2. d kasować

  3. x Zapisz i zamknij

Steven Penny
źródło