Il doit y avoir un meilleur moyen de remplacer uniquement les retours à la ligne uniques?

27

J'ai l'habitude d'écrire une ligne par phrase car je compile généralement des choses dans LaTex, ou j'écris dans un autre format où les sauts de ligne sont ignorés. J'utilise une ligne vierge pour indiquer le début d'un nouveau paragraphe.

Maintenant, j'ai un fichier écrit dans ce style que je voudrais simplement envoyer en texte brut. Je souhaite supprimer tous les sauts de ligne simples, mais laisser les sauts de ligne doubles intacts. Voici ce que j'ai fait:

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

Cela remplace les lignes vides par du texte qui, je suis sûr, n'apparaît pas dans le fichier: NEWLINEpuis il supprime tous les sauts de ligne avec awk (j'ai trouvé cette astuce sur certains sites Web), puis il remplace le NEWLINEs par les deux sauts de ligne requis .

Cela semble être un long chemin à parcourir pour faire une chose assez simple. Existe-t-il un moyen plus simple? De plus, s'il y avait un moyen de remplacer plusieurs espaces (qui s'introduisent parfois pour une raison quelconque) par des espaces uniques, ce serait bien aussi.

J'utilise emacs, donc s'il y a une astuce spécifique à emacs, c'est bien, mais je préfère voir une version pure sed ou pure awk.

Seamus
la source
Vous vouliez dire ^ $, pas $ ^ dans la première commande sed.
utilisateur inconnu
@user oui, oui je l'ai fait.
Seamus
Un moyen plus facile de supprimer tous les sauts de ligne: tr -d "\n".
jfg956

Réponses:

18

Vous pouvez utiliser awk comme ceci:

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

Ou si vous avez besoin d'une nouvelle ligne supplémentaire à la fin:

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

Ou si vous souhaitez séparer les paragraphes par une nouvelle ligne:

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

Ces commandes awk utilisent des actions protégées par des modèles:

/regex/

ou

END

Une action suivante n'est exécutée que si le motif correspond à la ligne actuelle.

Et les ^$.caractères ont une signification spéciale dans les expressions régulières, où ^correspond le début de la ligne, $la fin et .un caractère arbitraire.

maxschlepzig
la source
C'est bien, même si je préfère garder la ligne vide entre les paragraphes. Je suppose que vous pourriez faire quelque chose comme ça en ajoutant une nouvelle ligne supplémentaire quelque part dans la première commande d'impression? De plus, qu'est-ce qui se /./passe: il semble agir comme et elsepour la /^$/correspondance de chaîne, n'est-ce pas?
Seamus
1
@Seamus, bien sûr - il suffit de remplacer la première impression (mise à jour de la réponse) - /./ correspond à toutes les lignes qui contiennent au moins un caractère, c'est-à-dire le complément du motif / ^ $ / qui correspond uniquement aux lignes vides.
maxschlepzig
9

Utilisez le mode paragraphe Awk ou Perl pour traiter un fichier paragraphe par paragraphe, où les paragraphes sont séparés par des lignes vides.

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
'

Bien sûr, puisque cela n'analyse pas le (La) TeX, il mutilera horriblement les commentaires, les environnements textuels et d'autres syntaxes spéciales. Vous voudrez peut-être examiner DeTeX ou d'autres convertisseurs (La) TeX en texte.

Gilles 'SO- arrête d'être méchant'
la source
8

Solution Sed

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

Notez que dans cette solution :a créez une étiquette et n'utilisez pas la acommande.

Remplacement de plusieurs espaces

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

Steven D
la source
8

Si j'ai bien compris, une ligne vide implique deux sauts de ligne consécutifs, \n\n .

Si c'est le cas, une solution possible serait d'éliminer toutes les occurrences singulières de nouvelles lignes.

En Perl, une affirmation d'anticipation est un moyen d'y parvenir:

$ perl -0777 -i -pe 's/\n(?=[^\n])//g' test
  • Le -0777drapeau déroule efficacement tout le fichier en une seule chaîne
  • -p indique à perl d'imprimer la chaîne sur laquelle il travaille par défaut
  • -i spécifie l'édition sur place
  • La correspondance globale garantit que toutes les occurrences de nouvelle ligne sont traitées
Zaid
la source
Cela pose un problème: il n'y a pas d'espace entre les phrases.
Steven D
6

(raviver une ancienne question)

Cela semble être exactement ce que fmtet parsont pour - reformatage paragraphe. Comme vous (et aussi comme de nombreux programmes), ils définissent les limites des paragraphes comme une (ou plusieurs) lignes vides. Essayez de passer votre texte à travers l'un d'eux.

fmt est un utilitaire Unix standard et peut être trouvé dans GNU Coreutils.

parest un fmttexte considérablement amélioré écrit par Adam M. Costello qui peut être trouvé à http://www.nicemice.net/par/ (il a également été empaqueté pour plusieurs distributions, y compris debian - je l'ai empaqueté pour debian en janvier 1996, bien qu'il y ait maintenant un nouveau mainteneur pour le paquet.).

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

sedajoutera n'importe quelle ligne à l' Hancien espace qui contient au moins un seul caractère. Il supprime immédiatement dtous ceux à l'exception peut-être du dernier. Les seules lignes qui peuvent rester sont des blancs, et c'est sur ces lignes que sede xchange les espaces de maintien et de motif et supprime tous les accumulés\n caractères de ligne électronique .

Si vous souhaitez que les lignes contenant uniquement <tabs> ou <spaces> soient considérées comme vides, remplacez l' /./adresse ci-dessus par /[^[:blank:]]/. Pour compresser également les espaces, procédez comme suit:

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

Après avoir vu les exemples compacts de perl et awk de Gilles, j'étais réticent à poster ceci, mais j'avais déjà fait l'exercice, et c'est un script qui fonctionne, qui est raisonnablement documenté; ce point à lui seul peut intéresser certains .. (séduit avec commentaires! :)

Ce script considère les lignes vides comme vides même si elles contiennent des espaces.
Plusieurs espaces dans le texte sont condensés en un seul espace.
Les espaces de fin sont supprimés des lignes de texte. Les lignes vierges consécutives sont regroupées en une seule ligne. Le script laisse les lignes vierges supérieure et inférieure intactes.

Pour quoi que ce soit de plus que les scripts les plus triviaux, sed peut être écrit beaucoup plus facilement sous une forme structurée, en tant que fichier de script séparé. Voici un tel exemple.

en utilisant la syntaxe regex étendue
appel: $ sed -rf fichier texte du script

  :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

Remarque: flushdans les commentaires, cela signifie: envoyer l'espace de motif à la gestion interne de la sortie standard de sed. Cela ne signifie pas une impression définitive à stdout. La sortie dépend de l' -noption de sed . par exemple. la qcommande signifie vider et quitter ... Comparez ces deux extraits: echo x |sed -e qimprime x, echo x |sed -ne qn'imprime rien, alors que l'utilisation de la pcommande afficherait 'x' deux ou une fois, selon l' -noption.

Peter.O
la source
+1 pour les bons commentaires. J'ai vu trop de programmes sans aucun commentaire.
David Cary
4

Voici encore une autre sedsolution qui concatène toutes les lignes dans l sed'"espace d'attente" de sorte que nous obtenions une longue chaîne qui sera finalement copiée dans "l'espace du motif" pour la correspondance des motifs.

Comme les sauts de ligne seront conservés dans la chaîne longue finale sedde l '"espace de motif", les lignes vides en termes de doubles sauts de ligne [^\n]\n\n[^\n]peuvent être mises en correspondance et modifiées pour[^\n]\n[^\n] .

Pour plus d'informations, voir, par exemple, sed et Recherche et remplacement multiligne .

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
la source
3

Cela pourrait être de la vieille école:

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

Cela produira votre texte aligné à gauche ( .ad l), avec une longueur de ligne de 80 ( .ll 80). L'option de longueur de page ( .pl) indique au processeur de texte d'effectuer un remplissage de page pour une longueur de page de 1, donc aucun remplissage de page.

Si vous souhaitez que tous vos paragraphes sur une seule ligne, vous pouvez utiliser un grand nombre pour .ll:

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

man 7 groff pour plus d'options de formatage.

jfg956
la source
1

Dans Emacs, j'utilise parfois ceci regex:

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

Veux dire:

remplacer chaque nouvelle ligne qui est suivie par quelque chose qui n'est PAS une nouvelle ligne avec seulement la chose, qui a suivi la nouvelle ligne De cette façon, je me débarrasse de toutes les nouvelles lignes dans un paragraphe mais garde les paragraphes

emacs-user
la source
0

Il s'avère que avec auto-fill-modeon, emacs fait un très bon travail pour mes cas d'utilisation simples avec juste M-q...

Seamus
la source
Les détails de ce auto-fill-modequi dépend du mode principal que vous avez activé.
dmckee