web-dev-qa-db-fra.com

Supprimer la gamme de lignes ci-dessus modèle avec SED (ou awk)

J'ai le code suivant qui supprimera les lignes avec le motif banana et 2 lignes après:

sed '/banana/I,+2 d' file

Jusqu'ici tout va bien! Mais j'en ai besoin pour retirer 2 lignes avantbanana, mais je ne peux pas l'obtenir avec un "signe moins" ou quoi que ce soit (semblable à ce que grep -v -B2 banana file devrait faire mais pas):

teresaejunior@localhost ~ > LC_ALL=C sed '-2,/banana/I d' file
sed: invalid option -- '2'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,-2 d' file
sed: -e expression #1, char 16: unexpected `,'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,2- d' file
sed: -e expression #1, char 17: unknown command: `-'
28
Teresa e Junior

Sed ne backtrack pas: une fois que cela a traité une ligne, c'est fait. Donc, "trouver une ligne et imprimer les lignes précédentes" ne va pas fonctionner comme cela, contrairement à "trouver une ligne et imprimer les lignes N SUIVIES", faciles à greffer.

Si le fichier n'est pas trop long, puisque vous semblez être ok avec GNU Extensions, vous pouvez utiliser tac pour inverser les lignes du fichier.

tac | sed '/banana/I,+2 d' | tac

Un autre angle d'attaque consiste à maintenir une fenêtre coulissante dans un outil comme Awk. S'adapter à partir de Y a-t-il une alternative aux commutateurs de Grep's -B -C (à imprimer peu de lignes avant et après)? (Avertissement: Minimalement testé):

#!/bin/sh
{ "exec" "awk" "-f" "$0" "$@"; } # -*-awk-*-
# The array h contains the history of lines that are eligible for being "before" lines.
# The variable skip contains the number of lines to skip.
skip { --skip }
match($0, pattern) { skip = before + after }
NR > before && !skip { print NR h[NR-before] }
{ delete h[NR-before]; h[NR] = $0 }
END { if (!skip) {for (i=NR-before+1; i<=NR; i++) print h[i]} }

Utilisation: /path/to/script -v pattern='banana' -v before=2

Ceci est assez facile avec ex ou vim -e

    vim -e - $file <<@@@
g/banana/.-2,.d
wq
@@@

L'expression se lit comme suit: Pour chaque ligne contenant de la banane dans la plage de la ligne actuelle -2 à la ligne de courant, supprimez.

Ce qui est cool, c'est que la plage peut également contenir des recherches en arrière et en avant, par exemple, cela supprimera toutes les sections du fichier commençant par une ligne contenant Apple et se terminant par une ligne contenant une ligne et contenant une ligne avec banane:

    vim -e - $file <<@@@
g/banana/?apple?,/orange/d
wq
@@@

Notez également que jusqu'à dix commandes Vim/EX peuvent être soumises à l'aide de l'option de commande en ligne "-c". Voir la page man.

vim -e -c 'g/banana/.-2,.d' -c 'wq' $yourfilename

et

ex -c 'g/banana/?apple?,/orange/d' -c 'wq' $yourfilename 
19
Justin Rowe

Vous pouvez le faire assez simplement avec sed:

printf %s\\n    1 2 3 4match 5match 6 \
                7match 8 9 10 11match |
sed -e'1N;$!N;/\n.*match/!P;D'

Je ne sais pas pourquoi quiconque dirait autrement, mais à trouver une ligne et imprimer des lignes précédentessed intègre la primitive intégrée Print qui écrit uniquement jusqu'à la première \newline caractère dans l'espace motif. Le complémentaire Delete primitive supprime le même segment d'espace de motif avant de recycler récursivement le script avec ce qui reste. Et pour l'arrondir, il existe une primitive pour ajouter la ligne d'entrée Next à l'espace de motif suivant un indiqué \newline caractère.

De sorte qu'une ligne de sed devrait être tout ce dont vous avez besoin. Vous venez de remplacer match avec tout ce que votre Regexp est et que vous êtes doré. Cela devrait être une solution très rapide aussi.

Notez également qu'il comptera correctement A match précédant immédiatement après un autre match comme un déclencheur à la sortie périmée pour les deux lignes précédentes - et silence son imprimer aussi:


1
7match
8
11match

Afin de travailler pour un nombre de lignes -arbitraire, tout ce que vous avez à faire est d'obtenir un plomb.

Donc:

    printf %s\\n     1 2 3 4 5 6 7match     \
                     8match 9match 10match  \
                     11match 12 13 14 15 16 \
                     17 18 19 20match       |
    sed -e:b -e'$!{N;2,5bb' -e\} -e'/\n.*match/!P;D'

1
11match
12
13
14
20match

... Supprime les 5 lignes précédant n'importe quel match.

7
mikeserv

Utilisation de la "fenêtre coulissante" dans Perl:

Perl -ne 'Push @lines, $_;
          splice @lines, 0, 3 if /banana/;
          print shift @lines if @lines > 2
          }{ print @lines;'
7
choroba

À l'aide de man 1 ed:

str='
1
2
3
banana
4
5
6
banana
8
9
10
'

# using Bash
cat <<-'EOF' | ed -s <(echo "$str")  | sed -e '1{/^$/d;}' -e '2{/^$/d;}'
H
0i


.
,g/banana/km\
'm-2,'md
,p
q
EOF
1
larz