Musi być lepszy sposób na zastąpienie tylko pojedynczych znaków nowej linii?

27

Mam w zwyczaju pisać jedną linię na zdanie, ponieważ zwykle kompiluję różne rzeczy do LaTexa lub piszę w innym formacie, w którym łamanie linii jest ignorowane. Używam pustej linii, aby wskazać początek nowego akapitu.

Teraz mam plik napisany w tym stylu, który chciałbym wysłać jako zwykły tekst. Chcę usunąć wszystkie pojedyncze łamanie linii, ale pozostawić nienaruszone podwójne łamanie linii. Oto co zrobiłem:

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

Zastępuje to puste wiersze pewnym tekstem, który, jak jestem pewien, nie pojawia się w pliku: NEWLINEa następnie usuwa wszystkie podziały wiersza za pomocą awk (znalazłem tę sztuczkę na niektórych stronach internetowych), a następnie zastępuje NEWLINEs wymaganymi dwoma podziałami linii .

Wydaje się, że to długa, kręta droga do zrobienia całkiem prostej rzeczy. Czy istnieje prostszy sposób? Ponadto, gdyby istniał sposób na zastąpienie wielu spacji (które z jakiegoś powodu czasem się wkradają) pojedynczymi spacjami, to też byłoby dobrze.

Używam emacsa, więc jeśli jest jakaś sztuczka specyficzna dla emacsa, to dobrze, ale wolałbym zobaczyć wersję w wersji sed lub czystej awk.

Seamus
źródło
Miałeś na myśli ^ $, a nie $ ^ w pierwszym poleceniu sed.
użytkownik nieznany
@ użytkownik tak, tak zrobiłem.
Seamus
Łatwiejszy sposób, aby usunąć wszelkie podziały wiersza: tr -d "\n".
jfg956,

Odpowiedzi:

18

Możesz użyć awk w następujący sposób:

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

Lub jeśli potrzebujesz dodatkowej nowej linii na końcu:

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

Lub jeśli chcesz oddzielić akapity nowym znakiem:

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

Te polecenia awk wykorzystują działania chronione przez wzorce:

/regex/

lub

END

Następujące działanie jest wykonywane tylko wtedy, gdy wzorzec pasuje do bieżącej linii.

A ^$.znaki mają specjalne znaczenie w wyrażeniach regularnych, gdzie ^pasują do początku linii, $końca i .dowolnego znaku.

maxschlepzig
źródło
To dobrze, chociaż wolałbym zachować pustą linię między akapitami. Zakładam, że możesz zrobić coś takiego, dodając dodatkową nową linię gdzieś w pierwszym poleceniu drukowania? Co się /./dzieje: wydaje się, że działa tak, jak elsew przypadku /^$/dopasowania ciągu, prawda?
Seamus
1
@Seamus, jasne - po prostu zastąp pierwszy wydruk (zaktualizuj odpowiedź) - /./ dopasowuje wszystkie linie, które mają co najmniej jeden znak długości, tj. Uzupełnienie wzorca / ^ $ /, które pasuje tylko do pustych linii.
maxschlepzig
9

Użyj trybu akapitowego Awk lub Perla do przetwarzania pliku akapit po akapicie, gdzie akapity są oddzielone pustymi liniami.

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
'

Oczywiście, ponieważ nie analizuje to (La) TeXa, okropnie okaleczy komentarze, dosłowne środowiska i inną specjalną składnię. Możesz zajrzeć do DeTeXa lub innych (La) konwerterów TeX-to-text.

Gilles „SO- przestań być zły”
źródło
8

Sed Solution

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

Zauważ, że w tym rozwiązaniu :ajest tworzenie etykiety i nie używanie apolecenia.

Zamiana wielu spacji

Użyj tr:$ tr -s ' ' <test.text

Steven D.
źródło
8

Jeśli ja zrozumiałem, pusta linia oznacza dwa kolejne znaki nowej linii, \n\n.

Jeśli tak, jednym z możliwych rozwiązań byłoby wyeliminowanie wszystkich pojedynczych wystąpień nowych linii.

W Perlu jednym ze sposobów osiągnięcia tego jest stwierdzenie z wyprzedzeniem:

$ perl -0777 -i -pe 's/\n(?=[^\n])//g' test
  • -0777Flag skutecznie slurps całego pliku w jeden ciąg
  • -p każe perlowi wydrukować ciąg, na którym domyślnie działa
  • -i określa edycję w miejscu
  • Globalne dopasowanie zapewnia, że ​​wszystkie pojedyncze wystąpienia nowego wiersza są obsługiwane
Zaid
źródło
Problem polega na tym, że między zdaniami nie ma spacji.
Steven D
6

(ożywiając starożytne pytanie)

Wydaje się, że jest to dokładnie to, co jest fmti parjest - przeformatowanie akapitu. Podobnie jak Ty (a także wiele programów), definiują granice akapitów jako jedną (lub więcej) pustych linii. Spróbuj przesłać tekst jednym z nich.

fmt jest standardowym narzędziem uniksowym i można go znaleźć w GNU Coreutils.

parjest znacznie ulepszonym, fmtnapisanym przez Adama M. Costello, który można znaleźć na stronie http://www.nicemice.net/par/ (został również spakowany dla kilku dystrybucji, w tym debian - spakowałem go dla debiana w styczniu 1996 roku, chociaż jest teraz nowy opiekun pakietu pkg).

cas
źródło
6
sed -e'/./{H;$!d;}' -e'x;s/\n//g'

seddoda dowolny wiersz do Hstarej spacji, która zawiera co najmniej jeden znak. Zaraz potem dusuwa wszystkie te, z wyjątkiem chyba ostatniego. Jedynymi liniami, które mogą pozostać, są puste i znajdują się na tych liniach, gdy sede xzmienia przestrzenie wstrzymania i wzoru oraz usuwa wszystkie zgromadzone \nznaki ewline.

Jeśli chcesz, aby wiersze zawierające tylko <tabs> lub <spaces> były uważane za puste, zamień /./powyższy adres na /[^[:blank:]]/. Aby również wycisnąć spacje, wykonaj:

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

Po obejrzeniu zwięzłych przykładów perla i awk Gillesa, nie chciałem tego publikować, ale już przeszedłem ćwiczenie i jest to działający skrypt, który jest właściwie udokumentowany; ten punkt może być interesujący dla niektórych (sed z komentarzami! :)

Ten skrypt uważa puste linie za puste, nawet jeśli zawierają spacje.
Wiele spacji w tekście jest skondensowanych do pojedynczej spacji.
Końcowe białe znaki są usuwane z linii tekstu. Kolejne puste linie są zwinięte do pojedynczej linii. Skrypt pozostawia nienaruszone górne i dolne puste wiersze.

W przypadku czegokolwiek więcej niż najbardziej trywialnych skryptów, sed może być napisany o wiele łatwiej w formie ustrukturyzowanej, jako osobny plik skryptu. Oto taki przykład.

używając rozszerzonego
wywołania wyrażenia regularnego : $ sed -rf plik tekstowy skryptu

  :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

Uwaga: flushw komentarzach oznacza: wyślij przestrzeń wzorców do wewnętrznej obsługi standardowej sed. Nie oznacza to wyraźnego wydruku na standardowe wyjście. Wydajność zależy od -nopcji sed . na przykład. qśrodki dowodzenia równo i rzucić ... Porównaj te dwa fragmenty: echo x |sed -e qdrukuje x, echo x |sed -ne qnie drukuje, natomiast za pomocą ppolecenia byłoby wydrukować „x” dwa razy lub raz, w zależności od -nopcji.

Peter.O
źródło
+1 za dobre komentarze. Widziałem zbyt wiele programów bez komentarzy.
David Cary
4

Oto jeszcze jedno sedrozwiązanie, które łączy wszystkie linie w sed„przestrzeń wstrzymania”, dzięki czemu otrzymujemy jeden długi ciąg znaków, który ostatecznie jest kopiowany do „przestrzeni wzorów” w celu dopasowania wzorca.

Ponieważ znaki nowej linii zostaną zachowane w końcowym długim łańcuchu w sed„przestrzeni wzorów”, puste linie pod względem podwójnych podziałów linii [^\n]\n\n[^\n]można dopasowywać i modyfikować [^\n]\n[^\n].

Aby uzyskać więcej informacji, zobacz na przykład sed i 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
źródło
3

Może to być stara szkoła:

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

Spowoduje to wyświetlenie tekstu wyrównanego do lewej ( .ad l), o długości linii 80 ( .ll 80). Opcja długości strony ( .pl) nakazuje procesorowi tekstu wypełnienie strony dla długości strony 1, więc brak dopełniania strony.

Jeśli chcesz wszystkie akapity w jednym wierszu, możesz użyć dużej liczby do .ll:

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

man 7 groff po więcej opcji formatowania.

jfg956
źródło
1

W Emacsie czasami używam tego regex:

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

Znaczy:

zamień każdą nową linię, po której następuje, coś, co NIE jest nową linią, tylko rzeczą, która następuje po nowej linii W ten sposób pozbywam się wszystkich nowych linii w akapicie, ale zachowuję akapity (podwójne nowe linie)

emacs-user
źródło
0

Okazuje się, że z auto-fill-modewłączoną emacs wykonuje całkiem niezłą robotę w moich prostych przypadkach użycia z M-q...

Seamus
źródło
Szczegóły tego, co auto-fill-modezależy od tego, jaki tryb główny masz aktywny.
dmckee,