Comment utiliser sed / grep pour extraire du texte entre deux mots?

134

J'essaie de sortir une chaîne qui contient tout entre deux mots d'une chaîne:

contribution:

"Here is a String"

production:

"is a"

En utilisant:

sed -n '/Here/,/String/p'

inclut les points de terminaison, mais je ne souhaite pas les inclure.

user1190650
la source
8
Quel devrait être le résultat si l'entrée est Here is a Here String? Ou I Hereby Dub Thee Sir Stringy?
ghoti
5
FYI. Votre commande signifie imprimer tout ce qui se trouve entre la ligne contenant le mot Here et la ligne contenant le mot String - pas ce que vous voulez.
Hai Vu
L'autre sedFAQ courante est "comment extraire du texte entre des lignes particulières"; c'est stackoverflow.com/questions/16643288/…
tripleee

Réponses:

109
sed -e 's/Here\(.*\)String/\1/'
Brian Campbell
la source
2
Merci! Et si je voulais tout trouver entre "one is" et "String" dans "Here is a one is a String"? (sed -e 's / one est (. *) String / \ 1 /'?
user1190650
5
@ user1190650 Cela fonctionnerait si vous voulez voir le "Voici un" également. Vous pouvez le tester: echo "Here is a one is a String" | sed -e 's/one is\(.*\)String/\1/'. Si vous voulez juste la partie entre « est » et « String », alors vous devez faire la regex correspondre à la ligne entière: sed -e 's/.*one is\(.*\)String.*/\1/'. Dans sed, s/pattern/replacement/dites "remplacez" remplacement "par" motif "sur chaque ligne". Cela ne changera que tout ce qui correspond à "pattern", donc si vous voulez qu'il remplace la ligne entière, vous devez faire correspondre "pattern" à toute la ligne.
Brian Campbell
9
Cela s'arrête lorsque l'entrée estHere is a String Here is a String
Jay D
1
Ce serait formidable de voir la solution pour un cas: "Voici une chaîne de bla bla Voici 1 une chaîne de bla bla Voici 2 une chaîne de blash blash" La sortie ne doit prendre que la première sous-chaîne entre Here et String "
Jay D
1
@JayD sed ne prend pas en charge la correspondance non gourmande, voir cette question pour quelques alternatives recommandées.
Brian Campbell
180

GNU grep peut également prendre en charge les anticipations et les rétrospectives positives et négatives: pour votre cas, la commande serait:

echo "Here is a string" | grep -o -P '(?<=Here).*(?=string)'

S'il existe plusieurs occurrences de Hereet string, vous pouvez choisir si vous voulez faire correspondre le premier Hereet le dernier stringou les faire correspondre individuellement. En termes de regex, il est appelé correspondance gourmande (premier cas) ou correspondance non gourmande (deuxième cas)

$ echo 'Here is a string, and Here is another string.' | grep -oP '(?<=Here).*(?=string)' # Greedy match
 is a string, and Here is another 
$ echo 'Here is a string, and Here is another string.' | grep -oP '(?<=Here).*?(?=string)' # Non-greedy match (Notice the '?' after '*' in .*)
 is a 
 is another 
anishsane
la source
31
Notez que l' -Poption de GNU grep n'existe pas dans le grepBSD inclus dans *, ni dans ceux qui sont fournis avec n'importe quel SVR4 (Solaris, etc.). Dans FreeBSD, vous pouvez installer le devel/pcreport qui inclut pcregrep, qui prend en charge PCRE (et look-ahead / behind). Les anciennes versions d'OSX utilisaient GNU grep, mais dans OSX Mavericks, -Pest dérivé de la version de FreeBSD, qui n'inclut pas l'option.
ghoti
1
Bonjour, Comment extraire uniquement un contenu distinct?
Durgesh Suthar
4
Cela ne fonctionne pas car si votre chaîne de fin "chaîne" apparaît plus d'une fois, elle obtiendra la dernière occurrence, pas l' occurrence suivante .
Buttle Butkus
6
Dans le cas de Here is a string a string, les deux " is a " et " is a string a "sont des réponses valides (ignorez les guillemets), conformément aux exigences de la question. Cela dépend de vous lequel de ceux-ci vous voulez et la réponse peut être différente en conséquence. Quoi qu'il en soit, pour votre condition, cela fonctionnera:echo "Here is a string a string" | grep -o -P '(?<=Here).*?(?=string)'
anishsane
2
@BND, vous devez activer la fonction de recherche multiligne de pcregrep . echo $'Here is \na string' | grep -zoP '(?<=Here)(?s).*(?=string)'
anishsane
58

La réponse acceptée ne supprime pas le texte qui pourrait être avant Hereou après String. Cette volonté:

sed -e 's/.*Here\(.*\)String.*/\1/'

La principale différence est l'ajout de .*immédiatement avant Hereet après String.

rouleur
la source
Votre réponse est prometteuse. Un problème cependant. Comment puis-je l'extraire dans la première chaîne vue s'il y a plusieurs chaînes dans la même ligne? Merci
Mian Asbat Ahmad
@MianAsbatAhmad Vous voudriez rendre le *quantificateur, entre Hereet String, non gourmand (ou paresseux). Cependant, le type de regex utilisé par sed ne prend pas en charge les quantificateurs paresseux ( ?immédiatement après .*) selon cette question Stackoverflow. Habituellement, pour implémenter un quantificateur paresseux, vous correspondriez simplement à tout sauf le jeton que vous ne vouliez pas faire correspondre, mais dans ce cas, il n'y a pas qu'un seul jeton, mais plutôt une chaîne entière String.
wheeler
Merci, j'ai eu la réponse en utilisant awk, stackoverflow.com/questions/51041463/…
Mian Asbat Ahmad
Malheureusement, cela ne fonctionne pas si la chaîne a des sauts de ligne
Witalo Benicio
Ce n'est pas censé le faire. .ne correspond pas aux sauts de ligne. Si vous souhaitez faire correspondre les sauts de ligne, vous pouvez les remplacer .par quelque chose comme [\s\s].
wheeler
35

Vous pouvez supprimer des chaînes dans Bash seul:

$ foo="Here is a String"
$ foo=${foo##*Here }
$ echo "$foo"
is a String
$ foo=${foo%% String*}
$ echo "$foo"
is a
$

Et si vous avez un grep GNU qui inclut PCRE , vous pouvez utiliser une assertion de largeur nulle:

$ echo "Here is a String" | grep -Po '(?<=(Here )).*(?= String)'
is a
Ghoti
la source
pourquoi cette méthode est-elle si lente? lorsque vous supprimez une grande page html en utilisant cette méthode, cela prend environ 10 secondes.
Adam Johns
@AdamJohns, quelle méthode? Le PCRE? PCRE est assez complexe à analyser, mais 10 secondes semblent extrêmes. Si vous êtes inquiet, je vous recommande de poser une question avec un exemple de code et de voir ce que disent les experts.
ghoti
Je pense que c'était si lent pour moi car il contenait la source d'un très gros fichier html dans une variable. Lorsque j'ai écrit le contenu dans un fichier, puis analysé le fichier, la vitesse a considérablement augmenté.
Adam Johns
22

Grâce à GNU awk,

$ echo "Here is a string" | awk -v FS="(Here|string)" '{print $2}'
 is a 

grep avec le paramètre -P( perl-regexp ) prend en charge \K, ce qui aide à supprimer les caractères précédemment correspondants. Dans notre cas, la chaîne précédemment correspondante était Heredonc supprimée de la sortie finale.

$ echo "Here is a string" | grep -oP 'Here\K.*(?=string)'
 is a 
$ echo "Here is a string" | grep -oP 'Here\K(?:(?!string).)*'
 is a 

Si vous voulez que la sortie soit, is avous pouvez essayer ce qui suit,

$ echo "Here is a string" | grep -oP 'Here\s*\K.*(?=\s+string)'
is a
$ echo "Here is a string" | grep -oP 'Here\s*\K(?:(?!\s+string).)*'
is a
Avinash Raj
la source
Cela ne fonctionne pas pour :, echo "Here is a string dfdsf Here is a string" | awk -v FS="(Here|string)" '{print $2}'il revient uniquement is aau lieu de devrait être is a is a@Avinash Raj
alper
20

Si vous avez un long fichier avec de nombreuses occurrences multilignes, il est utile de commencer par imprimer des lignes numériques:

cat -n file | sed -n '/Here/,/String/p'
alémol
la source
3
Merci! C'est la seule solution qui a fonctionné dans mon cas (fichier texte sur plusieurs lignes, plutôt qu'une seule chaîne sans saut de ligne). Évidemment, pour l'avoir sans numérotation des lignes, l' -noption dans catdoit être omise.
Jeffrey Lebowski
... auquel cas catpeut être entièrement omis; sedsait lire un fichier ou une entrée standard.
tripleee
9

Cela pourrait fonctionner pour vous (GNU sed):

sed '/Here/!d;s//&\n/;s/.*\n//;:a;/String/bb;$!{n;ba};:b;s//\n&/;P;D' file 

Ceci présente chaque représentation de texte entre deux marqueurs (dans ce cas Hereet String) sur une nouvelle ligne et préserve les nouvelles lignes dans le texte.

potong
la source
7

Toutes les solutions ci-dessus présentent des lacunes où la dernière chaîne de recherche est répétée ailleurs dans la chaîne. J'ai trouvé préférable d'écrire une fonction bash.

    function str_str {
      local str
      str="${1#*${2}}"
      str="${str%%$3*}"
      echo -n "$str"
    }

    # test it ...
    mystr="this is a string"
    str_str "$mystr" "this " " string"
Gary Dean
la source
6

Vous pouvez utiliser deux commandes s

$ echo "Here is a String" | sed 's/.*Here//; s/String.*//'
 is a 

Fonctionne aussi

$ echo "Here is a StringHere is a String" | sed 's/.*Here//; s/String.*//'
 is a

$ echo "Here is a StringHere is a StringHere is a StringHere is a String" | sed 's/.*Here//; s/String.*//'
 is a 
Ivan
la source
6

Pour comprendre la sedcommande, nous devons la construire étape par étape.

Voici votre texte original

user@linux:~$ echo "Here is a String"
Here is a String
user@linux:~$ 

Essayons de supprimer la Herechaîne avec l' soption ubstition danssed

user@linux:~$ echo "Here is a String" | sed 's/Here //'
is a String
user@linux:~$ 

À ce stade, je crois que vous seriez en mesure d'éliminer Stringaussi bien

user@linux:~$ echo "Here is a String" | sed 's/String//'
Here is a
user@linux:~$ 

Mais ce n'est pas la sortie souhaitée.

Pour combiner deux commandes sed, utilisez l' -eoption

user@linux:~$ echo "Here is a String" | sed -e 's/Here //' -e 's/String//'
is a
user@linux:~$ 

J'espère que cela t'aides

Sabrina
la source
4

Vous pouvez utiliser \1(voir http://www.grymoire.com/Unix/Sed.html#uh-4 ):

echo "Hello is a String" | sed 's/Hello\(.*\)String/\1/g'

Le contenu entre crochets sera stocké sous forme de \1.

mvairavan
la source
Cela supprime les chaînes au lieu de produire quelque chose entre les deux. Essayez de supprimer "Hello" avec "is" dans la commande sed et il affichera "Hello a"
Jonathan
1

Problème. Mes messages Claws Mail stockés sont emballés comme suit et j'essaie d'extraire les lignes d'objet:

Subject: [SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key molecular
 link in major cell growth pathway: Findings point to new potential
 therapeutic target in pancreatic cancer [mTORC1 Activator SLC38A9 Is
 Required to Efflux Essential Amino Acids from Lysosomes and Use Protein as
 a Nutrient] [Re: Nutrient sensor in key growth-regulating metabolic pathway
 identified [Lysosomal amino acid transporter SLC38A9 signals arginine
 sufficiency to mTORC1]]
Message-ID: <20171019190902.18741771@VictoriasJourney.com>

Par A2 dans ce fil, comment utiliser sed / grep pour extraire du texte entre deux mots? la première expression, ci-dessous, "fonctionne" tant que le texte correspondant ne contient pas de nouvelle ligne:

grep -o -P '(?<=Subject: ).*(?=molecular)' corpus/01

[SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key

Cependant, malgré avoir essayé de nombreuses variantes ( .+?; /s; ...), je n'ai pas pu les faire fonctionner:

grep -o -P '(?<=Subject: ).*(?=link)' corpus/01
grep -o -P '(?<=Subject: ).*(?=therapeutic)' corpus/01
etc.

Solution 1.

Par Extraire du texte entre deux chaînes sur des lignes différentes

sed -n '/Subject: /{:a;N;/Message-ID:/!ba; s/\n/ /g; s/\s\s*/ /g; s/.*Subject: \|Message-ID:.*//g;p}' corpus/01

qui donne

[SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key molecular link in major cell growth pathway: Findings point to new potential therapeutic target in pancreatic cancer [mTORC1 Activator SLC38A9 Is Required to Efflux Essential Amino Acids from Lysosomes and Use Protein as a Nutrient] [Re: Nutrient sensor in key growth-regulating metabolic pathway identified [Lysosomal amino acid transporter SLC38A9 signals arginine sufficiency to mTORC1]]                              

Solution 2. *

Par Comment puis - je remplacer un saut de ligne (\ n) en utilisant sed?

sed ':a;N;$!ba;s/\n/ /g' corpus/01

remplacera les nouvelles lignes par un espace.

Enchaîner cela avec A2 dans Comment utiliser sed / grep pour extraire du texte entre deux mots? , on a:

sed ':a;N;$!ba;s/\n/ /g' corpus/01 | grep -o -P '(?<=Subject: ).*(?=Message-ID:)'

qui donne

[SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key molecular  link in major cell growth pathway: Findings point to new potential  therapeutic target in pancreatic cancer [mTORC1 Activator SLC38A9 Is  Required to Efflux Essential Amino Acids from Lysosomes and Use Protein as  a Nutrient] [Re: Nutrient sensor in key growth-regulating metabolic pathway  identified [Lysosomal amino acid transporter SLC38A9 signals arginine  sufficiency to mTORC1]] 

Cette variante supprime les doubles espaces:

sed ':a;N;$!ba;s/\n/ /g; s/\s\s*/ /g' corpus/01 | grep -o -P '(?<=Subject: ).*(?=Message-ID:)'

donnant

[SLC38A9 lysosomal arginine sensor; mTORC1 pathway] Key molecular link in major cell growth pathway: Findings point to new potential therapeutic target in pancreatic cancer [mTORC1 Activator SLC38A9 Is Required to Efflux Essential Amino Acids from Lysosomes and Use Protein as a Nutrient] [Re: Nutrient sensor in key growth-regulating metabolic pathway identified [Lysosomal amino acid transporter SLC38A9 signals arginine sufficiency to mTORC1]]
Victoria Stuart
la source
1
belle aventure :))
Alexandru-Mihai Manolescu