Wie entferne ich mehrere Leerzeilen aus einer Datei?

14

Ich habe einige Textdateien, mit denen ich Notizen mache - einfach nur Text, normalerweise nur mit cat >> file. Gelegentlich verwende ich ein oder zwei Leerzeilen (einfach zurück - das Zeichen für die neue Zeile), um ein neues Thema / eine neue Gedankenlinie anzugeben. Am Ende jeder Sitzung füge ich vor dem Schließen der Datei mit Ctrl+ Dnormalerweise viele (5-10) Leerzeilen (Return-Taste) hinzu, um die Sitzungen zu trennen.

Das ist natürlich nicht sehr klug, aber es funktioniert für mich zu diesem Zweck. Ich habe jedoch sehr viele unnötige Leerzeilen, daher suche ich nach einer Möglichkeit, die (meisten) zusätzlichen Zeilen zu entfernen. Gibt es einen Linux-Befehl (cut, paste, grep, ...?), Der mit ein paar Optionen direkt verwendet werden kann? Oder hat jemand eine Idee für ein sed-, awk- oder perl-Skript (auch in jeder anderen Skriptsprache, obwohl ich sed- oder awk-Skripts vorziehen würde), das tun würde, was ich will? Etwas in C ++ zu schreiben (was ich eigentlich selbst tun könnte), scheint einfach übertrieben.

Fall 1: Was ich brauche, ist ein Skript / Befehl, der mehr als zwei (3 oder mehr) aufeinanderfolgende Leerzeilen entfernt und durch nur zwei Leerzeilen ersetzt. Es wäre aber auch schön, wenn man mehr als eine Zeile (2 oder mehr) entfernen und / oder mehrere Leerzeilen durch nur eine Leerzeile ersetzen könnte.

Fall 2: Ich könnte auch ein Skript / einen Befehl verwenden, mit dem eine einzelne Leerzeile zwischen zwei Textzeilen entfernt wird, aber mehrere Leerzeilen unverändert bleiben (obwohl das Entfernen einer der Leerzeilen auch akzeptabel wäre).

Baard Kopperud
quelle
2
@ l0b0, das ist eine ganz andere Frage (die andere war eine vimund sollte Leerzeilen durch eine Leerzeile ersetzen ).
Stéphane Chazelas

Antworten:

14

Fall 1:

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

Fall 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
quelle
+1 für awk statt sed
Rob
Da dieser Anwendungsfall häufig wiederholt wird, würde ich vorschlagen, ein Skript zu erstellen.
ChuckCottrill
15

Sie können verwenden, uniqum mehrere Instanzen von Leerzeilen in eine Leerzeile zu reduzieren. Es werden jedoch auch Zeilen reduziert, die Text enthalten, wenn sie gleich und untereinander sind.

Anthon
quelle
6

Fall 1:

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

Fall 2:

perl -i -ane '$n=(@F==0) ? $n+1 : 0; print $n==2 ? "\n$_" : $n==1 ? "" : $_ '
Basharat Sialvi
quelle
+1 perl ftw! Awk ist (wahrscheinlich) kanonisch dafür, aber (DRY) zwingt mich, Skripte für Anwendungsfälle zu schreiben, die sich wie folgt wiederholen.
ChuckCottrill
3

Sie können Fall 1 wie folgt mit GNU sed ansprechen:

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

Das heißt, sammeln Sie leere Zeilen im Musterbereich, und reduzieren Sie sie auf zwei Zeilen, wenn mehr als drei oder mehr Zeilen vorhanden sind.

So fügen Sie Zeilen mit einfachem Abstand wie in Fall 2 zusammen:

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

Oder in kommentierter Form:

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
quelle
1

Diese Lösung berücksichtigt auch die letzten Leerzeilen in der Datei:

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
quelle
0

Auf Anthons Vorschlag, "uniq" zu verwenden ...

Entfernen Sie führende, nachfolgende und doppelte Leerzeilen.

# 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//"

In einer langen Reihe:

(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//")

Oder benutze einfach "cat -s".

Ich habe von Klammern zu geschweiften Klammern gewechselt, um im aktuellen Shell-Kontext zu bleiben, von dem ich annehme, dass er effizienter ist. Beachten Sie, dass geschweifte Klammern nach dem letzten Befehl ein Semikolon und ein Leerzeichen zur Trennung benötigen.

# 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'

In einer einzigen Zeile.

{ { echo; cat; echo; } | cat -s | sed '1d;$d'; }
JohnMudd
quelle
0

Die veröffentlichten Lösungen sahen für mich etwas kryptisch aus. Hier ist die Lösung in Python 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')

Sie können die Funktionen von einem Interpreter aufrufen oder wie folgt von der Shell aus ausführen:

$ ./remove_multiple_lines.py '*' /tmp/ --strip-right
rominf
quelle