Insérer une nouvelle ligne avant chaque ligne correspondant à un model, sauf si la ligne précédente est déjà vide

J'ai besoin d'append une nouvelle ligne avant toute ligne contenant un motif où nous pouvons supposer que le motif est toujours la première string de la ligne courante. Par exemple

This is a pattern This is a pattern 

Je peux append une nouvelle ligne avec la command sed

 sed -i 's/pattern\+/\n&/g' file 

pour get la sortie

 This is a pattern This is a pattern 

Pour éviter que plusieurs nouvelles lignes soient ajoutées (en cas d'exécution multiple), je veux vérifier si la ligne avant que le model soit vide. Je sais que je peux le faire avec

 if [ "$line" == "" ]; then 

Mais comment puis-je déterminer la ligne précédente, d'un model correspondant, en premier lieu?

EDIT: Le motif peut se produire plusieurs fois.

Vous pouvez stocker la ligne précédente dans l'espace de cale:

 sed ' /^pattern/{ x /./ { x s/^/\ / x } x } h' 

Ce serait plus lisible avec awk cependant:

 awk '!previous_empty && /pattern/ {print ""} {previous_empty = $0 == ""; print}' 

Comme les implémentations GNU de sed ont une option -i pour l'édition sur place, l'implémentation GNU de awk as -i inplace pour cela.

Mais comment puis-je déterminer la ligne précédente, d'un model correspondant, en premier lieu?

Hum … peut-être que ça va marcher.

En utilisant grep et le commutateur -B :

  -B num, --before-context=num Print num lines of leading context before each match. See also the -A and -C options. 

Considérez ceci infile:

 cat infile foo bar baz 

Maintenant, si je grep pour la bar , la ligne précédente devrait être vide:

 if [[ $(grep -B 1 'bar' infile | head -1) == "" ]]; then echo "line is empty"; fi line is empty 

Contrairement à l'utilisation de grep sur baz , où la ligne précédente n'est pas vide:

 if [[ $(grep -B 1 'baz' infile | head -1) == "" ]]; then echo "line is empty"; fi <no output> 

Une autre façon avec sed :

 sed -e 'tD' -e '$!N;/.\npattern/s/\n/&&/;:D' -e 'P;D' infile 

Ceci a été expliqué en détail ici : c'est essentiellement un cycle N;P;D où l'on count les nouvelles lignes que nous éditons, donc chaque fois que le script insère un \n ewline il n'exécute que P et D sans N afin de toujours avoir seulement deux lignes dans l'espace du motif.

Je sais que votre question portait initialement sur sed, mais il y a une réponse magnifiquement simple dans vim:

 :g/.\npattern/norm o 

Ou, si vous préférez exécuter cela entièrement à partir de la command line:

 vim file -c "g/.\npattern/norm o" -c "wq" 

La façon dont il fonctionne, c'est qu'il cherche une ligne qui correspond à l'expression rationnelle suivante:

 .\npattern 

qui est n'importe quelle ligne non vide suivie par votre model. Ensuite, pour chaque correspondance, elle applique la command suivante norm o , qui ouvre une nouvelle ligne sous l'location actuel du slider.

Si nous avons gnused (la valeur par défaut dans Linux et beaucoup d'autres, et disponible pour tous)

 sed -zri 's/([^\n]\n)(pattern)/\1\n\2/g' file 

  • ([^\n]\n)(pattern) le motif après une ligne non vide
  • -z sépare les "lignes" par le caractère null (slurp le file)
  • -r avoir des expressions régulières étendues

Utilisation de fonctions spécifiées ex , POSIX uniquement

 printf '%s\n' 0a '' . 1d 'g/pattern/-put | -,.!uniq' x | ex file 

Résumé rapide des commands passées à ex par printf :

 0a - append after line 0 - an empty line . - stop appending 1d - delete line 1 (the new empty line) into the unnamed register (aka buffer) g/pattern/-put | -,.!uniq g/pattern/ - for every line in the file matching "pattern" - - on the *previous* line, put - "put" (linewise append) the contents of the unnamed register | - and also do the following (still part of the g// command) -,. - take the previous and current lines !uniq - and run them through the external command "uniq" (replacing the lines with the output) x - save changes and exit 

ex vaut la peine d'apprendre. 🙂

Supposons que nous recadrions les exigences comme ceci:

  • Nous avons un file de paragraphes, qui sont des régions de lignes non-vides consécutives, séparées par une ou plusieurs lignes vides.
  • Chaque fois que "pattern" apparaît à l'intérieur d'un paragraphe, nous aimerions que cette ligne commence un nouveau paragraphe, sauf lorsque "pattern" apparaît dans la première ligne d'un paragraphe.

  • De plus, cela ne nous dérange pas de normaliser le file pour qu'il y ait exactement une ligne vide entre les paragraphes, et pas de lignes vides égarées au début ou à la fin. *

Si ces conditions sont acceptables, nous pouvons profiter du mode paragraphe Awk (activé une valeur vide dans RS ):

 awk 'BEGIN { RS=""; FS="\n" } { print sep $1; for (i = 2; i <= NF; i++) { if ($i ~ /pat/) print ""; print $i } sep=FS }' 

En mode paragraphe, les loggings sont des paragraphes. Parce que nous utilisons \n comme FS , les champs $1, $2, ... $NF correspondent à des lignes de paragraphes. Par exemple, si NF est 5, nous avons affaire à un paragraphe de cinq lignes. Les nouvelles lignes qui séparent les paragraphes sont supprimées et chaque logging $0 ne contient que les nouvelles lignes intérieures entre les lignes, et la division des champs est effectuée sur celles-ci.

Un paragraphe a au less une ligne, parce que les paragraphes ne peuvent pas être vides: un paragraphe vide ressemble à deux nouvelles lignes consécutives qui font partie de la même séquence de séparation de paragraphe.

Donc, sans vérifier que NF est au less 1, nous imprimons simplement la première ligne de paragraphe avec print sep $1 . La première fois autour, sep est vide donc est sans conséquence; mais après le premier paragraphe, nous définissons sep à une nouvelle ligne, de sorte que l' print sep $1 suivante print sep $1 générera la séparation des paragraphes.

Après avoir imprimé la première ligne, nous parcourons les lignes restantes, le cas échéant, et les imprimons. Ici, nous vérifions si chaque ligne correspond au model. Si c'est le cas, nous émettons une ligne vide supplémentaire avant de l'imprimer.