Es muss eine bessere Möglichkeit geben, nur einzelne Zeilenumbrüche zu ersetzen.

27

Ich habe die Gewohnheit, eine Zeile pro Satz zu schreiben, weil ich normalerweise Dinge in LaTex kompiliere oder in einem anderen Format schreibe, bei dem Zeilenumbrüche ignoriert werden. Ich verwende eine leere Zeile, um den Beginn eines neuen Absatzes anzugeben.

Jetzt habe ich eine Datei in diesem Stil, die ich nur als einfachen Text senden möchte. Ich möchte alle einzelnen Zeilenumbrüche entfernen, aber die doppelten Zeilenumbrüche intakt lassen. Das habe ich gemacht:

sed 's/$^/NEWLINE/' file.txt | awk '{printf "%s ",$0}' | sed 's/NEWLINE/\n\n/g' > linebreakfile.txt

Dies ersetzt leere Zeilen durch Text, von dem ich sicher bin, dass er nicht in der Datei enthalten ist. NEWLINEDann werden alle Zeilenumbrüche mit awk entfernt (ich habe diesen Trick auf einer Website gefunden) und das NEWLINEs durch die erforderlichen zwei Zeilenumbrüche ersetzt .

Dies scheint ein langwieriger Weg zu sein, um eine ziemlich einfache Sache zu tun. Gibt es einen einfacheren Weg? Wenn es eine Möglichkeit gäbe, mehrere Leerzeichen (die sich manchmal aus irgendeinem Grund einschleichen) durch einzelne Leerzeichen zu ersetzen, wäre dies auch gut.

Ich benutze Emacs. Wenn es also einen Emacs-spezifischen Trick gibt, der gut ist, würde ich lieber eine reine sed- oder reine awk-Version sehen.

Seamus
quelle
Sie meinten im ersten sed-Befehl ^ $, nicht $ ^.
Benutzer unbekannt
@user ja ja hab ich gemacht.
Seamus
Ein einfacher Weg , um alle Zeilenumbrüche zu entfernen: tr -d "\n".
jfg956

Antworten:

18

Sie können awk folgendermaßen verwenden:

$ awk ' /^$/ { print; } /./ { printf("%s ", $0); } ' test

Oder wenn Sie am Ende einen zusätzlichen Zeilenumbruch benötigen:

$ awk ' /^$/ { print; } /./ { printf("%s ", $0); } END { print ""; } ' test

Oder wenn Sie die Absätze durch eine neue Zeile trennen möchten:

$ awk ' /^$/ { print "\n"; } /./ { printf("%s ", $0); } END { print ""; } ' test

Diese awk-Befehle verwenden Aktionen, die von Mustern geschützt werden:

/regex/

oder

END

Eine folgende Aktion wird nur ausgeführt, wenn das Muster mit der aktuellen Zeile übereinstimmt.

Und die ^$.Zeichen haben in regulären Ausdrücken eine besondere Bedeutung, wobei sie ^mit dem Zeilenanfang, $dem Ende und .einem beliebigen Zeichen übereinstimmen .

maxschlepzig
quelle
Das ist gut, obwohl ich es vorziehen würde , die leere Zeile zwischen den Absätzen zu belassen. Ich nehme an, Sie könnten so etwas tun, indem Sie irgendwo im ersten Druckbefehl eine zusätzliche neue Zeile einfügen. /./Und was macht es: Es scheint sich so zu verhalten und elsefür die /^$/Stringübereinstimmung, ist das richtig?
Seamus
1
@Seamus, sicher - ersetzen Sie einfach den ersten Ausdruck (die Antwort wurde aktualisiert) - /./ stimmt mit allen Zeilen überein, die mindestens ein Zeichen lang sind, dh mit dem Komplement des Musters / ^ $ /, das nur mit leeren Zeilen übereinstimmt.
maxschlepzig
9

Verwenden awk oder Perl- Absatz - Modus eine Datei Absatz für Absatz zu verarbeiten, wobei die Absätze durch Leerzeilen getrennt sind.

awk -vRS= '
  NR!=1 {print ""}      # print blank line before every record but the first
  {                     # do this for every record (i.e. paragraph):
    gsub(" *\n *"," "); # replace newlines by spaces, compressing spaces
    sub(" *$","");      # remove spaces at the end of the paragraph
    print
  }
'
perl -000 -pe '             # for every paragraph:
  print "\n" unless $.==1;  # print a blank line, except before the first paragraph
  s/ *\n *(?!$)/ /g;        # replace newlines by spaces, compressing spaces, but not at the end of the paragraph
  s/ *\n+\z/\n/             # normalize the last line end of the paragraph
'

Da dies das (La) TeX nicht parst, werden Kommentare, wörtliche Umgebungen und andere spezielle Syntax schrecklich beschädigt. Möglicherweise möchten Sie sich mit DeTeX oder anderen (La) TeX-zu-Text-Konvertern befassen .

Gilles 'SO - hör auf böse zu sein'
quelle
8

Sed Solution

$ sed -e ':a;N;$!ba;s/\(.\)\n/\1 /g' -e 's/\n/\n\n/' test.text

Beachten Sie, dass in dieser Lösung :aein Label erstellt wird und der aBefehl nicht verwendet wird .

Mehrere Leerzeichen ersetzen

Verwendung tr:$ tr -s ' ' <test.text

Steven D
quelle
8

Wenn ich richtig verstanden habe, impliziert eine leere Zeile zwei aufeinanderfolgende Zeilenumbrüche \n\n.

Wenn ja, wäre eine mögliche Lösung, alle singulären Vorkommen von Zeilenumbrüchen zu beseitigen.

In Perl ist eine Lookahead-Behauptung eine Möglichkeit, dies zu erreichen:

$ perl -0777 -i -pe 's/\n(?=[^\n])//g' test
  • Das -0777Flag schlürft effektiv die gesamte Datei in eine einzige Zeichenfolge
  • -p Weist Perl an, die Zeichenfolge zu drucken, an der standardmäßig gearbeitet wird
  • -i Gibt die direkte Bearbeitung an
  • Der globale Abgleich stellt sicher, dass alle einzelnen Zeilenumbrüche behandelt werden
Zaid
quelle
Ein Problem dabei ist, dass zwischen den Sätzen keine Leerzeichen stehen.
Steven D
6

(Wiederbelebung einer alten Frage)

Dies scheint genau das zu sein, was fmtund parsind für - Absatz Neuformatierung. Wie Sie (und auch wie viele andere Programme) definieren sie Absatzgrenzen als eine (oder mehrere) Leerzeilen. Versuchen Sie, Ihren Text durch eine dieser Zeilen zu leiten.

fmt ist ein Standard-Unix-Dienstprogramm und kann in GNU Coreutils gefunden werden.

parist ein stark verbesserter fmtText von Adam M. Costello, der unter http://www.nicemice.net/par/ zu finden ist (er wurde auch für verschiedene Distributionen, einschließlich Debian, gepackt - ich habe ihn für Debian im Januar 1996 gepackt). obwohl es jetzt einen neuen Betreuer für das pkg gibt.).

cas
quelle
6
sed -e'/./{H;$!d;}' -e'x;s/\n//g'

sedFügt eine Zeile an ein Haltes Leerzeichen an, das mindestens ein einzelnes Zeichen enthält. Es werden sofort dalle gelöscht , mit Ausnahme des letzten. Die einzigen Linien , die können , sind Rohlinge bleiben, und es ist auf diesen Linien , wenn sede xdie Halt und Musterräume und löscht alle kumulierten Änderungen \newline Zeichen.

Wenn Sie möchten, dass Zeilen, die nur <Tabulatoren> oder <Leerzeichen> enthalten , als leer betrachtet werden, ersetzen Sie die /./obige Adresse durch /[^[:blank:]]/. Gehen Sie wie folgt vor, um auch Leerzeichen zu komprimieren:

 sed -e'/./{H;$!d;}'    \
     -e'x;s/\n//g'      \
     -e's/\([[:blank:]]\)*/\1/g'
mikeserv
quelle
5

Nachdem ich Gilles 'Perl- und awk-Kompaktbeispiele gesehen hatte, wollte ich das nicht posten, aber ich hatte die Übung bereits durchgearbeitet, und es ist ein funktionierendes Skript, das hinreichend dokumentiert ist. Dieser Punkt allein kann für einige von Interesse sein .. (mit Kommentaren sed! :)

In diesem Skript werden leere Zeilen auch dann als leer betrachtet, wenn sie Leerzeichen enthalten.
Mehrere Leerzeichen im Text werden zu einem einzigen Leerzeichen zusammengefasst.
Nachgestellte Leerzeichen werden aus den Textzeilen entfernt. Aufeinanderfolgende Leerzeilen werden zu einer einzigen Zeile zusammengefasst. Das Skript lässt die oberen und unteren Leerzeilen intakt.

Für mehr als die einfachsten Skripte kann sed viel einfacher in strukturierter Form als separate Skriptdatei geschrieben werden. Hier ist ein solches Beispiel.

Verwenden des erweiterten Regex-Syntaxaufrufs
: $ sed -rf Skript-Textdatei

  :first-empty-line
  #================
  /^[[:space:]]*$/ { # if pattern-space is empty...
      $q  # last line # flush-quit 
      n   # pattern-flush=nextline-continue

      :subsequent-empty-line
      #=====================
      /^[[:space:]]*$/ { # if pattern-space is empty...
          $d        # last line # pattern-delete-cycle
          N         # pattern+=nl+nextline
          s/.*\n//  # scrap the leading 'blank' line
          t subsequent-empty-line # branch-on-substitute
      }
  }

  :text-line
  #=========
  $q                       # last line # flush-quit 
  s/^(.*)[[:space:]]*/\1/  # trim trailing whitespace
  s/ +/ /g                 # condense mulltiple spaces
  N                        # pattern+=nl+nextline
  /^.*\n[[:space:]]*$/ { # if newly-read line is blank 
      P          # pattern-first-line-print
      s/^.*\n//  # remove the leading 'text' line
      t first-empty-line   # branch-on-substitute
  }
  # read line is text
  s/\n/ /      # replace \n with a space
  t text-line  # branch-on-substitute

Hinweis: flushIn den Kommentaren bedeutet dies: Sende den Pattern-Space an seds interne Standardverarbeitung. Es bedeutet nicht, dass es sich um einen bestimmten Ausdruck handelt. Die Ausgabe ist abhängig von der -nOption von sed . z.B. Der qBefehl bedeutet " Flush" und "Quit ". Vergleichen Sie diese beiden Ausschnitte: echo x |sed -e qdruckt x, echo x |sed -ne qdruckt nichts, während die Verwendung des pBefehls je nach -nOption "x" zweimal oder einmal ausgeben würde.

Peter.O
quelle
+1 für gute Kommentare. Ich habe zu viele Programme ohne Kommentare gesehen.
David Cary
4

Hier ist noch eine andere sedLösung, die alle Zeilen zu sed"hold space" verkettet, so dass wir eine lange Zeichenfolge erhalten, die schließlich für den Mustervergleich in den "pattern space" kopiert wird.

Da Zeilenumbrüche in der letzten langen Zeichenfolge im sed"Musterbereich" erhalten bleiben , können leere Zeilen in Form von doppelten Zeilenumbrüchen [^\n]\n\n[^\n]abgeglichen und geändert werden [^\n]\n[^\n].

Weitere Informationen finden Sie beispielsweise unter sed und Multi-Line Search and Replace .

text='
line 1

line 2
line 3





line 4


line     5



line 6
line 7

line 8
'

# FreeBSD sed
# first sed deletes first / last line if empty and squeezes multiple spaces
printf '%s' "$text" |
sed -e '1{/^$/d;}' -e '${/^$/d;}' -e '/[[:space:]]\{2,\}/s// /g' | 
sed -n -e '1h;1!H;${;g;/\([^[:cntrl:]]\)\n\n\([^[:cntrl:]]\)/s//\1\
\2/g;p;}' |
nl -b a


# GNU sed
# alternative using ...;x;... instead of ...;g;...
# cf. man sed | less -p '\]x'
printf '%s' "$text" |
gsed -e '1{/^$/d;}' -e '${/^$/d;}' -e '/[[:space:]]\{2,\}/s// /g' | 
gsed -E -n '1h;1!H;${;x;/([^\n])\n\n([^\n])/s//\1\
\2/g;p;}' | 
nl -b a


# remove all the single linebreaks but leave the double linebreaks intact
printf '%s' "$text" | 
   sed -n -e '1h;1!H;${;g;/\([^[:cntrl:]]\)\n\([^[:cntrl:]]\)/s//\1 \2/g;p;}' | 
   nl -b a
deso
quelle
3

Das könnte alte Schule sein:

(echo ".pl 1" ; echo ".ll 80" ; echo ".ad l" ; cat your_file) | nroff

Dadurch wird Ihr Text linksbündig ( .ad l) mit einer Zeilenlänge von 80 ( .ll 80) ausgegeben . Die Seitenlängenoption ( .pl) weist die Textverarbeitung an, das Seitenauffüllen für eine Seitenlänge von 1 durchzuführen, sodass kein Seitenauffüllen erfolgt.

Wenn Sie möchten, dass alle Absätze in einer einzigen Zeile stehen, können Sie eine große Zahl verwenden für .ll:

(echo ".pl 1" ; echo ".ll 1000000" ; echo ".ad l" ; cat your_file) | nroff

Mann 7 groff für weitere Formatierungsoptionen.

jfg956
quelle
1

In Emacs verwende ich manchmal Folgendes regex:

^J\([^^J]\) -> \1

Meint:

Ersetzen Sie jede neue Zeile, die von etwas gefolgt wird, das KEINE neue Zeile ist, durch das Ding, das auf die neue Zeile folgt. Auf diese Weise entferne ich alle neuen Zeilen in einem Absatz, behalte aber Absätze (doppelte neue Zeilen)

emacs-user
quelle
0

Es stellt sich heraus, dass mit auto-fill-modeon emacs für meine einfachen Anwendungsfälle mit nur M-q...

Seamus
quelle
Die Details darüber, was auto-fill-modepassiert, hängen davon ab, welchen Hauptmodus Sie aktiviert haben.
dmckee