Il semble que j'utilise abusivement grep
/egrep
.
J'essayais de rechercher des chaînes sur plusieurs lignes et je n'ai pas trouvé de correspondance alors que je sais que ce que je recherche doit correspondre. À l'origine, je pensais que mes expressions rationnelles étaient incorrectes, mais j'ai finalement lu que ces outils fonctionnent par ligne (également mes expressions régulières étaient si triviales que cela ne pouvait pas être le problème).
Alors, quel outil utiliserait-on pour rechercher des modèles sur plusieurs lignes?
Voici un sed
qui vous donnera un comportement semblable à grep
sur plusieurs lignes:
sed -n '/foo/{:start /bar/!{N;b start};/your_regex/p}' your_file
Comment ça marche
-n
supprime le comportement par défaut de l'impression de chaque ligne/foo/{}
lui demande de faire correspondre foo
et de faire ce qui se trouve à l'intérieur des squigglies avec les lignes correspondantes. Remplacez foo
par la partie de départ du motif.:start
est une étiquette de branchement pour nous aider à continuer à boucler jusqu'à ce que nous trouvions la fin de notre expression régulière./bar/!{}
exécutera le contenu des squigglies sur les lignes qui ne correspondent pas à bar
. Remplacez bar
par la dernière partie du motif.N
ajoute la ligne suivante au tampon actif (sed
appelle cela l'espace modèle)b start
se ramifie inconditionnellement à l'étiquette start
que nous avons créée plus tôt afin de continuer à ajouter la ligne suivante tant que l'espace de motif ne contient pas bar
./your_regex/p
imprime l'espace de motif s'il correspond à your_regex
. Vous devez remplacer your_regex
par l'expression entière que vous souhaitez faire correspondre sur plusieurs lignes.J'utilise généralement un outil appelé pcregrep
qui peut être installé dans la plupart des versions linux en utilisant yum
ou apt
.
Par exemple.
Supposons que vous ayez un fichier nommé testfile
avec du contenu
abc blah
blah blah
def blah
blah blah
Vous pouvez exécuter la commande suivante:
$ pcregrep -M 'abc.*(\n|.)*def' testfile
pour faire correspondre les modèles sur plusieurs lignes.
De plus, vous pouvez faire de même avec sed
.
$ sed -e '/abc/,/def/!d' testfile
Voici une approche plus simple en utilisant Perl:
Perl -e '$f=join("",<>); print $& if $f=~/foo\nbar.*\n/m' file
ou (puisque JosephR a pris la route sed
, je vais voler sans vergogne son suggestion )
Perl -n000e 'print $& while /^foo.*\nbar.*\n/mg' file
$f=join("",<>);
: ceci lit le fichier entier et enregistre son contenu (sauts de ligne et tout) dans la variable $f
. Nous essayons ensuite de faire correspondre foo\nbar.*\n
Et de l'imprimer s'il correspond (la variable spéciale $&
Contient la dernière correspondance trouvée). Le ///m
Est nécessaire pour faire correspondre l'expression régulière entre les nouvelles lignes.
Le -0
Définit le séparateur d'enregistrement d'entrée. La définition de 00
Active le "mode paragraphe" où Perl utilisera des sauts de ligne consécutifs (\n\n
) Comme séparateur d'enregistrement. Dans les cas où il n'y a pas de sauts de ligne consécutifs, le fichier entier est lu (slurped) à la fois.
Ne pas faites cela pour les fichiers volumineux, cela chargera le fichier entier en mémoire et cela peut être un problème.
Une façon de le faire est avec Perl. par exemple. voici le contenu d'un fichier nommé foo
:
foo line 1
bar line 2
foo
foo
foo line 5
foo
bar line 6
Maintenant, voici du Perl qui correspondra à toute ligne commençant par foo suivie par toute ligne commençant par bar:
cat foo | Perl -e 'while(<>){$all .= $_}
while($all =~ /^(foo[^\n]*\nbar[^\n]*\n)/m) {
print $1; $all =~ s/^(foo[^\n]*\nbar[^\n]*\n)//m;
}'
Le Perl, décomposé:
while(<>){$all .= $_}
Ceci charge toute l'entrée standard dans la variable $all
while($all =~
Alors que la variable all
a l'expression régulière .../^(foo[^\n]*\nbar[^\n]*\n)/m
L'expression régulière: foo au début de la ligne, suivie d'un nombre quelconque de caractères non-newline, suivie d'une nouvelle ligne, suivie immédiatement de "bar", et du reste de la ligne contenant une barre . /m
À la fin de l'expression régulière signifie "correspondance sur plusieurs lignes"print $1
Imprime la partie de l'expression régulière qui était entre parenthèses (dans ce cas, l'expression régulière entière)s/^(foo[^\n]*\nbar[^\n]*\n)//m
Efface la première correspondance pour l'expression régulière, afin que nous puissions faire correspondre plusieurs cas de l'expression régulière dans le fichier en questionEt la sortie:
foo line 1
bar line 2
foo
bar line 6
L'alternative grep sift prend en charge la correspondance multiligne (avertissement: je suis l'auteur).
Supposons que testfile
contient:
<book> <title> Lorem Ipsum </title> <description> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididuntunt ut labore et dolore magna aliqua </description> </book>
sift -m '<description>.*?</description>'
(Montrer les lignes contenant la description)
Résultat:
testfile: <description> Lorem ipsum dolor sit amet, consectetur testfile: adipiscing elit, sed do eiusmod tempor incididunt ut testfile: labore et dolore magna aliqua </description>
sift -m '<description>(.*?)</description>' --replace 'description="$1"' --no-filename
(extraire et reformater la description)
Résultat:
description = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
Simplement un grep normal qui supporte Perl-regexp
paramètre P
fera ce travail.
$ echo 'abc blah
blah blah
def blah
blah blah' | grep -oPz '(?s)abc.*?def'
abc blah
blah blah
def
(?s)
appelé modificateur DOTALL qui fait en sorte que le point dans votre expression régulière corresponde non seulement aux caractères mais aussi aux sauts de ligne.
Supposons que nous ayons le fichier test.txt contenant:
blabla
blabla
foo
here
is the
text
to keep between the 2 patterns
bar
blabla
blabla
Le code suivant peut être utilisé:
sed -n '/foo/,/bar/p' test.txt
Pour la sortie suivante:
foo
here
is the
text
to keep between the 2 patterns
bar
J'ai résolu celui-ci pour moi en utilisant grep et l'option -A avec un autre grep.
grep first_line_Word -A 1 testfile | grep second_line_Word
L'option -A 1 imprime 1 ligne après la ligne trouvée. Bien sûr, cela dépend de votre combinaison de fichiers et de mots. Mais pour moi, c'était la solution la plus rapide et la plus fiable.
Si nous voulons obtenir le texte entre les 2 motifs en s'excluant.
Supposons que nous ayons le fichier test.txt contenant:
blabla
blabla
foo
here
is the
text
to keep between the 2 patterns
bar
blabla
blabla
Le code suivant peut être utilisé:
sed -n '/foo/{
n
b gotoloop
:loop
N
:gotoloop
/bar/!{
h
b loop
}
/bar/{
g
p
}
}' test.txt
Pour la sortie suivante:
here
is the
text
to keep between the 2 patterns
Comment ça marche, faisons-le pas à pas
/foo/{
est déclenché lorsque la ligne contient "foo"n
remplacez l'espace de motif par la ligne suivante, c'est-à-dire le mot "ici"b gotoloop
branche sur le label "gotoloop":gotoloop
définit le libellé "gotoloop"/bar/!{
si le motif ne contient pas "bar"h
remplacez l'espace d'attente par un motif, donc "ici" est enregistré dans l'espace d'attenteb loop
branchez-vous sur le libellé "boucle":loop
définit le libellé "boucle"N
ajoute le motif à l'espace d'attente.:gotoloop
Nous sommes maintenant à l'étape 4, et bouclons jusqu'à ce qu'une ligne contienne "bar"/bar/
la boucle est terminée, "bar" a été trouvé, c'est l'espace du motifg
l'espace de motif est remplacé par un espace d'attente qui contient toutes les lignes entre "foo" et "bar" qui ont été enregistrées pendant la boucle principalep
copie l'espace de motif sur la sortie standardTerminé !