Comment obtenir toutes les lignes entre la première et la dernière occurrence de motifs?


8

Comment puis-je découper un fichier (flux d'entrée bien) afin que je ne reçoive que les lignes allant de la première occurrence de motif fooà la dernière occurrence de motif bar?

Par exemple, considérez l'entrée suivante:

A line
like
foo
this 
foo
bar
something
something else
foo
bar
and
the
rest

J'attends cette sortie:

foo
this 
foo
bar
something
something else
foo
bar

3
Flux en un seul passage ou un fichier? C'est beaucoup plus facile à faire lorsque l'accès aléatoire est autorisé. Avec un fichier, vous trouverez simplement le premier fooet le dernier baret imprimez tout le reste , le cas échéant. Avec un flux, vous devrez lire jusqu'au premier fooet mettre en mémoire tampon toutes les lignes suivantes jusqu'à EOF, en vidant le tampon chaque fois que a barest vu. Cela pourrait signifier la mise en mémoire tampon de l'ensemble du flux en mémoire.
jw013

Réponses:


6
sed -n '/foo/{:a;N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba};'

La correspondance des motifs sed /first/,/second/lit les lignes une par une. Lorsqu'une ligne correspond à /first/elle, elle s'en souvient et attend avec impatience la première correspondance pour le /second/motif. En même temps, il applique toutes les activités spécifiées pour ce modèle. Après ce processus recommence encore et encore jusqu'à la fin du fichier.

Ce n'est pas ce dont nous avons besoin. Nous devons rechercher la dernière correspondance de /second/motif. Par conséquent, nous construisons une construction qui ne recherche que la première entrée /foo/. Une fois trouvé, le cycle acommence. Nous ajoutons une nouvelle ligne au tampon de correspondance avec Net vérifions si elle correspond au modèle /bar/. Si c'est le cas, nous l'imprimons et effaçons le tampon de correspondance et sautons janyway au début du cycle avec ba.

Nous devons également supprimer le symbole de nouvelle ligne après le nettoyage du tampon /^\n/s/^\n//. Je suis sûr qu'il existe une bien meilleure solution, malheureusement, cela ne m'est pas venu à l'esprit.

J'espère que tout est clair.


1
Ça marche! Ce serait super cool si vous pouviez nous expliquer la construction d'une telle commande. Je me sentirais stupide simplement copier / coller à partir d'un site Web en ligne;)
rahmu

1
Désolé, je n'ai pas posté l'explication avec la réponse. Maintenant, c'est dans la poste.
précipiter le

Dans certaines sedversions, par exemple BSD sed (qui se trouve sur Mac), les balises doivent être suivies d'une nouvelle ligne ou d'une fin de chaîne, donc le réglage suivant est nécessaire: sed -n -e '/foo/{:a' -e 'N;/^\n/s/^\n//;/bar/{p;s/.*//;};ba' -e '};' Cela fonctionne également sur GNU sed, donc je pense que cette modification (plusieurs -earguments mettre fin à un argument après chaque nom de branche) est une bonne habitude portable à prendre lorsque vous utilisez des branches dans sed.
Wildcard

4

Je le ferais avec une petite doublure Perl.

cat <<EOF | perl -ne 'BEGIN { $/ = undef; } print $1 if(/(foo.*bar)/s)'
A line
like
foo
this 
foo
bar
something
something else
foo
bar
and
the
rest
EOF

les rendements

foo
this 
foo
bar
something
something else
foo
bar

3
S'il s'agissait de code-golf, vous pourriez utiliser à la Eplace eet -00777au lieu du $/bit (voir perlrun (1)). Ce qui le raccourcirait en:, perl -0777 -nE 'say /(foo.*bar)/s'toujours en quelque sorte lisible.
Thor

1
Je ne connaissais pas ces drapeaux! Je suis sûr que -0[octal]je trouverai surtout sa voie dans mon flux de travail! Merci pour cela
user1146332

3

Voici une solution GNU sed à deux passes qui ne nécessite pas beaucoup de mémoire:

< infile                                     \
| sed -n '/foo/ { =; :a; z; N; /bar/=; ba }' \
| sed -n '1p; $p'                            \
| tr '\n' ' '                                \
| sed 's/ /,/; s/ /p/'                       \
| sed -n -f - infile

Explication

  • La première sedinvocation passe infile et trouve la première occurrence de fooet toutes les occurrences suivantes de bar.
  • Ces adresses sont ensuite façonnées en un nouveau sedscript avec deux invocations de sedet une tr. La sortie du troisième sedest [start_address],[end_address]p, sans les crochets.
  • L'invocation finale de sedpasse à infilenouveau, imprimant les adresses trouvées et tout le reste.

2

Si le fichier d'entrée tient confortablement en mémoire, restez simple .

Si le fichier d'entrée est énorme, vous pouvez l'utiliser csplitpour le diviser en morceaux au premier fooet à tous les suivants, barpuis assembler les morceaux. Les morceaux sont appelés piece-000000000, piece-000000001etc. Choisissez un préfixe (ici piece-) qui ne se heurtera pas aux autres fichiers existants.

csplit -f piece- -n 9 - '%foo%' '/bar/' '{*}' <input-file

(Sur les systèmes non Linux, vous devrez utiliser un grand nombre à l'intérieur des accolades, par exemple {999999999}, et passer l' -koption. Ce nombre est le nombre de barpièces.)

Vous pouvez assembler toutes les pièces avec cat piece-*, mais cela vous donnera tout après le premier foo. Retirez donc cette dernière pièce en premier. Étant donné que les noms de fichiers produits par csplitne contiennent aucun caractère spécial, vous pouvez les retravailler sans prendre de précaution particulière, par exemple avec

rm $(echo piece-* | sed 's/.* //')

ou équivalent

rm $(ls piece-* | tail -n 1)

Vous pouvez maintenant joindre toutes les pièces et supprimer les fichiers temporaires:

cat piece-* >output
rm piece-*

Si vous souhaitez supprimer les morceaux au fur et à mesure qu'ils sont concaténés pour économiser de l'espace disque, faites-le en boucle:

mv piece-000000000 output
for x in piece-?????????; do
  cat "$x" >>output; rm "$x"
done

1

Voici une autre façon avec sed:

sed '/foo/,$!d;H;/bar/!d;s/.*//;x;s/\n//' infile

Il ajoute chaque ligne de la /foo/,$plage (les lignes !ne faisant pas partie de cette plage sont dsupprimées) à l' Hancien espace. Les lignes qui ne correspondent pas barsont ensuite supprimées. Sur les lignes qui correspondent, l'espace de motif est vidé, e xchangé avec l'espace de maintien et la ligne vide de tête dans l'espace de motif est supprimée.

Avec une entrée énorme et peu d'occurrences, barcela devrait être (beaucoup) plus rapide que de tirer chaque ligne dans l'espace de motif et, à chaque fois, de vérifier l'espace de motif bar.
Expliqué:

sed '/foo/,$!d                     # delete line if not in this range
H                                  # append to hold space
/bar/!d                            # if it doesn't match bar, delete 
s/.*//                             # otherwise empty pattern space and
x                                  # exchange hold buffer w. pattern space then
s/\n//                             # remove the leading newline
' infile

Bien sûr, s'il s'agit d'un fichier (et qu'il tient en mémoire), vous pouvez simplement exécuter:

 ed -s infile<<'IN'
.t.
/foo/,?bar?p
q
IN

car ed peut rechercher en avant et en arrière.
Vous pouvez même lire une sortie de commande dans le tampon de texte si votre shell prend en charge la substitution de processus:

printf '%s\n' .t. /foo/,?bar?p q | ed -s <(your command)

ou si ce n'est pas le cas, avec gnu ed:

printf '%s\n' .t. /foo/,?bar?p q | ed -s '!your command'

0

En utilisant n'importe quel awk dans n'importe quel shell sur n'importe quel système UNIX et sans lire le fichier entier ou le flux d'entrée en mémoire à la fois:

$ awk '
    f {
        rec = rec $0 ORS
        if (/bar/) {
            printf "%s", rec
            rec = ""
        }
        next
    }
    /foo/ { f=1; rec=$0 ORS }
' file
foo
this
foo
bar
something
something else
foo
bar

0

Grep pourrait le faire aussi (enfin, GNU grep):

<infile grep -ozP '(?s)foo.*bar' | tr '\0' '\n'

<infile grep -ozP '        #  call grep to print only the matching section (`-o`)
                           #  use NUL for delimiter (`-z`) (read the whole file).
                           #  And using pcre regex.
(?s)foo.*bar               #  Allow the dot (`.`) to also match newlines.
' | tr '\0' '\n'           #  Restore the NULs to newlines.

Pour l'apport du corps de la question:

$ <infile grep -ozP '(?s)foo.*bar' | tr '\0' '\n'
foo
this 
foo
bar
something
something else
foo
bar
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.