Jak usunąć wiele pustych linii z pliku?

14

Mam kilka plików tekstowych, których używam do robienia notatek - po prostu zwykły tekst, zwykle tylko za pomocą cat >> file. Czasami używam pustej linii lub dwóch (po prostu return - znak nowej linii), aby określić nowy temat / linię myśli. Na koniec każdej sesji, przed zamknięciem pliku za pomocą Ctrl+ D, zwykle dodam wiele (5-10) pustych wierszy (klawisz powrotu), aby oddzielić sesje.

Nie jest to oczywiście zbyt sprytne, ale do tego celu działa. I mają jednak kończą się z dużą i mnóstwo niepotrzebnych pustych wierszy, więc szukam sposobu, aby usunąć (większość) dodatkowe linie. Czy istnieje polecenie dla systemu Linux (wytnij, wklej, grep, ...?), Którego można użyć bezpośrednio z kilkoma opcjami? Alternatywnie, czy ktoś ma pomysł na sed, awk lub perl (dobrze w jakimkolwiek języku skryptowym, choć wolałbym sed lub awk) skrypt, który zrobiłby to, czego chcę? Pisanie czegoś w C ++ (co właściwie mógłbym zrobić sam), wydaje się po prostu przesadą.

Przypadek 1: Potrzebuję skryptu / polecenia, które usunie więcej niż dwie (3 lub więcej) kolejnych pustych linii i zastąpi je tylko dwiema pustymi liniami. Chociaż byłoby dobrze, gdyby można go również dostosować, aby usunąć więcej niż jedną linię (2 lub więcej) i / lub zastąpić wiele pustych linii tylko jedną pustą linią.

Przypadek nr 2: Mógłbym również użyć skryptu / polecenia, które usuną pojedynczą pustą linię między dwoma liniami tekstu, ale pozostawią wiele pustych linii bez zmian (chociaż usunięcie jednej z pustych linii byłoby również dopuszczalne).

Baard Kopperud
źródło
2
@ l0b0, to zupełnie inne pytanie (drugie było vimjednym i miało zastąpić puste linie jednym pustym wierszem).
Stéphane Chazelas

Odpowiedzi:

14

Przypadek 1:

awk '!NF {if (++n <= 2) print; next}; {n=0;print}'

Przypadek 2:

awk '!NF {s = s $0 "\n"; n++; next}
     {if (n>1) printf "%s", s; n=0; s=""; print}
     END {if (n>1) printf "%s", s}'
Stéphane Chazelas
źródło
+1 za awk zamiast sed
Rob
Ponieważ ten przypadek użycia jest często powtarzany, sugerowałbym utworzenie skryptu.
ChuckCottrill 10.10.2013
15

Możesz użyć, uniqaby zwinąć wiele wystąpień pustych linii w jedną pustą linię, ale zwinie również linie zawierające tekst, jeśli są takie same i znajdują się pod sobą.

Anthon
źródło
6

Przypadek 1:

perl -i -ane '$n=(@F==0) ? $n+1 : 0; print if $n<=2'

Przypadek 2:

perl -i -ane '$n=(@F==0) ? $n+1 : 0; print $n==2 ? "\n$_" : $n==1 ? "" : $_ '
Basharat Sialvi
źródło
+1 perl ftw! Awk jest (prawdopodobnie) kanoniczny do tego, ale (DRY) zmusza mnie do pisania skryptów dla przypadków użycia, które są powtarzane w ten sposób.
ChuckCottrill 10.10.2013
3

Możesz rozwiązać przypadek nr 1 w ten sposób za pomocą GNU sed:

sed -r ':a; /^\s*$/ {N;ba}; s/( *\n *){2,}/\n\n/'

Oznacza to, że zbierz puste linie w obszarze wzorów, a jeśli jest więcej niż trzy lub więcej linii, zmniejsz ją do dwóch linii.

Aby połączyć linie z pojedynczymi odstępami, jak w przypadku 2, możesz to zrobić w następujący sposób:

sed -r '/^ *\S/!b; N; /\n *$/!b; N; /\S *$/!b; s/\n *\n/\n/'

Lub w formie komentarza:

sed -r '
  /^ *\S/!b        # non-empty line
  N                # 
  /\n *$/!b        # followed by empty line
  N                # 
  /\S *$/!b        # non-empty line
  s/\n *\n/\n/     # remove the empty line
'
Thor
źródło
1

To rozwiązanie zajmuje się również ostatnimi pustymi wierszami w pliku:

sed -r -n '
  /^ *$/!{p;b}  # non-blank line - print and next cycle
  h             # blank line - save it in hold space
  :loop
  $b end        # last line - go to end
  n             # read next line in pattern space
  /^ *$/b loop  # blank line - loop to next one
  :end          # pattern space has non-blank line or last blank line
  /^ *$/{p;b}   # last blank line: print and exit
  H;x;p         # non-blank line: print hold + pattern space and next cycle
'
PJ_Finnegan
źródło
0

Zgodnie z sugestią Anthon , aby użyć „uniq” ...

Usuń wiodące, końcowe i zduplikowane puste linie.

# Get large random string.
rand_str=; while [[ ${#rand_str} -lt 40 ]]; do rand_str=$rand_str$RANDOM; done

# Add extra lines at beginning and end of stdin.
(echo $rand_str; cat; echo $rand_str) |

# Convert empty lines to random strings.
sed "s/^$/$rand_str/" |

# Remove duplicate lines.
uniq |

# Remove first and last line.
sed '1d;$d' |

# Convert random strings to empty lines.
sed "s/$rand_str//"

W jednej długiej linii:

(rand_str=; while [[ ${#rand_str} -lt 40 ]]; do rand_str=$rand_str$RANDOM; done; (echo $rand_str; cat; echo $rand_str) | sed "s/^$/$rand_str/" | uniq | sed '1d;$d' | sed "s/$rand_str//")

Lub po prostu użyj „cat -s”.

Zmieniłem nawias klamrowy na nawiasy klamrowe, aby pozostać w bieżącym kontekście powłoki, który, jak zakładam, jest bardziej wydajny. Zauważ, że nawiasy klamrowe wymagają średnika po ostatnim poleceniu i potrzebują miejsca na separację.

# Add extra blank lines at beginning and end.
# These will be removed in final step.
{ echo; cat; echo; } |

# Replace multiple blank lines with a single blank line.
cat -s |

# Remove first and last line.
sed '1d;$d'

W jednym wierszu.

{ { echo; cat; echo; } | cat -s | sed '1d;$d'; }
JohnMudd
źródło
0

Opublikowane rozwiązania wyglądały dla mnie trochę tajemniczo. Oto rozwiązanie w Pythonie 3.6:

#!/usr/bin/env python3

from pathlib import Path                                                                                                                                                              
import sys                                                                                                                                                                            
import fileinput                                                                                                                                                                      


def remove_multiple_blank_lines_from_file(path, strip_right=True): 
    non_blank_lines_out_of_two_last_lines = [True, True] 
    for line in fileinput.input(str(path), inplace=True): 
        non_blank_lines_out_of_two_last_lines.pop(0) 
        non_blank_lines_out_of_two_last_lines.append(bool(line.strip())) 
        if sum(non_blank_lines_out_of_two_last_lines) > 0: 
            line_to_write = line.rstrip() + '\n' if strip_right else line 
            sys.stdout.write(line_to_write)


def remove_multiple_blank_lines_by_glob(rglob='*', path=Path('.'), strip_right=True): 
    for p in path.rglob(rglob): 
        if p.is_file(): 
            try:
                remove_multiple_blank_lines_from_file(p, strip_right=strip_right)
            except Exception as e:
                print(f"File '{p}' was not processed due the error: {e}")


if __name__ == '__main__':
    remove_multiple_blank_lines_by_glob(sys.argv[1], Path(sys.argv[2]), next(iter(sys.argv[3:]), None) == '--strip-right')

Możesz wywoływać funkcje z interpretera lub uruchamiać je z powłoki, np .:

$ ./remove_multiple_lines.py '*' /tmp/ --strip-right
rominf
źródło