Faire correspondre deux chaînes en une seule ligne avec grep

218

J'essaie d'utiliser greppour faire correspondre les lignes qui contiennent deux chaînes différentes. J'ai essayé ce qui suit mais cela correspond aux lignes qui contiennent soit string1 ou string2 qui ne correspondent pas à ce que je veux.

grep 'string1\|string2' filename

Alors, comment puis-je faire correspondre grepuniquement les lignes contenant les deux chaînes ?

Hearsaxas
la source
1
Connexe: unix.stackexchange.com/questions/37313/…
AlikElzin-kilaka

Réponses:

189

Vous pouvez utiliser grep 'string1' filename | grep 'string2'

Ou, grep 'string1.*string2\|string2.*string1' filename

dhérosaurus
la source
5
@AlexanderN en effet, je ne peux pas le faire fonctionner avec multiligne, c'est tellement bizarre qu'il a été accepté ..
Aquarius Power
1
Ce n'était pas une question multiligne. S'il était multiligne, grep -P prend en charge l'expression régulière de style Perl ...
Scott Prive
20
Fonctionne uniquement lorsque 'string1' ET 'string2' sont sur la même ligne. Si vous voulez trouver des lignes avec 'string1' ou 'string2', voir la réponse de user45949.
lifeson106
10
la première option: canaliser un grep dans un second ne produit PAS de résultat OU, il produit un résultat ET.
masukomi
1
J'ai utiliségrep -e "string1" -e "string2"
Ravi Dhoriya ツ
198

Je pense que c'est ce que vous cherchiez:

grep -E "string1|string2" filename

Je pense que des réponses comme celle-ci:

grep 'string1.*string2\|string2.*string1' filename

correspond uniquement au cas où les deux sont présents, pas l'un ou l'autre ou les deux.

user45949
la source
14
ne ferait pas grep -e "string1" -e "string2" filenamela même chose?
janosdivenyi
25
voici comment grep pour string1 OU string2. la question indique clairement qu'ils recherchent string1 ET string2.
orion elenzil
9
Je suis sûr que la question est assez précise:How do I match lines that contains *both* strings?
r0estir0bbe
Peut-il imprimer avec une même ligne?
吴毅 凡
1
Pourquoi cette réponse est-elle toujours là? Ce n'est PAS une réponse à la question.
Prometheus
26

Pour rechercher des fichiers contenant tous les mots dans n'importe quel ordre, n'importe où:

grep -ril \'action\' | xargs grep -il \'model\' | xargs grep -il \'view_type\'

Le premier grep lance une recherche récursive ( r), ignorant la casse ( i) et listant (imprimant) le nom des fichiers qui correspondent ( l) pour un terme ( 'action'avec les guillemets simples) se trouvant n'importe où dans le fichier.

Les greps suivants recherchent les autres termes, conservent l'insensibilité à la casse et répertorient les fichiers correspondants.

La liste finale des fichiers que vous obtiendrez sera celle qui contient ces termes, dans n'importe quel ordre n'importe où dans le fichier.

Kinjal Dixit
la source
2
D'accord! Je vais juste noter que j'ai dû donner à xargs un "-d '\ n'" pour gérer les noms de fichiers avec des espaces. Cela a fonctionné pour moi sur Linux: grep -ril 'foo' | xargs -d '\n' grep -il 'bar'
Tommy Harris
16

Si vous avez un grepavec une -Poption pour une perlexpression rationnelle limitée , vous pouvez utiliser

grep -P '(?=.*string1)(?=.*string2)'

ce qui a l'avantage de travailler avec des chaînes qui se chevauchent. C'est un peu plus simple d'utiliser perlas grep, car vous pouvez spécifier la logique et plus directement:

perl -ne 'print if /string1/ && /string2/'
tchrist
la source
1
Meilleure réponse. Shell est très facile et rapide, mais une fois que le motif devient complexe, vous devez utiliser Python ou Perl (ou Awk). Ne vous frappez pas la tête contre le mur en essayant de prouver que cela peut être fait en coquille pure (quoi que cela signifie de nos jours). Un rappel des gens, ces outils peuvent être utilisés dans la syntaxe "one liner" qui sont incorporés dibble dans un script shell existant.
Scott Prive
12

Votre méthode était presque bonne, il ne manquait que le -w

grep -w 'string1\|string2' filename
Leo
la source
1
Au moins sur OS-X et FreeBSD, cela fonctionne! Je suppose que vous êtes sur quelque chose d'autre (que l'OP n'a pas défini - j'espère que vous n'avez pas dévalorisé une réponse correcte à de nombreux utilisateurs sauf vous).
Leo
Je suis sur OS-X. Peut-être que je ne fais pas cela correctement? Jetez un œil à ce que j'ai fait: i.imgur.com/PFVlVAG.png
Ariel
1
Impair. Je m'attendais à ce que la différence réside dans le fait de ne pas entrer dans le fichier, mais, si je canalise ma méthode avec votre ls, j'obtiens un résultat que vous n'avez pas: imgur.com/8eTt3Ak.png - Les deux sur OS-X 10.9.5 ( "grep (BSD grep) 2.5.1-FreeBSD") et FreeBSD 10 ("grep (GNU grep) 2.5.1-FreeBSD"). Je suis curieux de savoir ce que tu grep -Ves.
Leo
1
Vos exemples fonctionnent pour moi: i.imgur.com/K8LM69O.png Donc la différence est que cette méthode ne capte pas les sous-chaînes, elles doivent être des chaînes complètes par elles-mêmes. Je suppose que vous devrez construire des expressions rationnelles dans le grep pour rechercher des sous-chaînes. Quelque chose comme ça:grep -w 'regexp1\|regexp2' filename
Ariel
2
OP montre un exemple en faisant correspondre chaîne1 ou chaîne2 et demande comment faire correspondre les lignes contenant les deux chaînes. Cet exemple donne toujours OR.
gustafbstrom
7

L' |opérateur dans une expression régulière signifie ou. C'est-à-dire que string1 ou string2 correspondra. Vous pourriez faire:

grep 'string1' filename | grep 'string2'

qui dirigera les résultats de la première commande vers le deuxième grep. Cela ne devrait vous donner que des lignes qui correspondent aux deux.

martineno
la source
1
Vos déclarations sont vraies, mais ne répondez pas à la question OP
Ben Wheeler
Cela répond à la question et c'est bien ainsi que la plupart des gens l'écrivent.
Peter K
7

Vous pouvez essayer quelque chose comme ceci:

(pattern1.*pattern2|pattern2.*pattern1)
Dorn
la source
4

Et comme les gens ont suggéré perl et python, et des scripts shell compliqués, voici une approche awk simple :

awk '/string1/ && /string2/' filename

Après avoir regardé les commentaires de la réponse acceptée: non, cela ne fait pas plusieurs lignes; mais ce n'est pas non plus ce que l'auteur de la question a demandé.

tink
la source
3

N'essayez pas d'utiliser grep pour cela, utilisez plutôt awk. Pour faire correspondre 2 expressions rationnelles R1 et R2 dans grep, vous penseriez que ce serait:

grep 'R1.*R2|R2.*R1'

alors qu'en awk ce serait:

awk '/R1/ && /R2/'

mais que se passe-t-il si R2chevauche ou est un sous-ensemble de R1? Cette commande grep ne fonctionnerait tout simplement pas alors que la commande awk le ferait. Disons que vous voulez trouver des lignes qui contiennent theet heat:

$ echo 'theatre' | grep 'the.*heat|heat.*the'
$ echo 'theatre' | awk '/the/ && /heat/'
theatre

Il faudrait utiliser 2 greps et un pipe pour ça:

$ echo 'theatre' | grep 'the' | grep 'heat'
theatre

et bien sûr, si vous aviez réellement demandé qu'ils soient séparés, vous pouvez toujours écrire dans awk la même expression régulière que celle utilisée dans grep et il existe des solutions alternatives à awk qui n'impliquent pas de répéter les expressions rationnelles dans chaque séquence possible.

Mis à part cela, que se passe-t-il si vous souhaitez étendre votre solution pour correspondre à 3 regexps R1, R2 et R3. En grep, ce serait l'un de ces mauvais choix:

grep 'R1.*R2.*R3|R1.*R3.*R2|R2.*R1.*R3|R2.*R3.*R1|R3.*R1.*R2|R3.*R2.*R1' file
grep R1 file | grep R2 | grep R3

alors qu'en awk, ce serait concis, évident, simple, efficace:

awk '/R1/ && /R2/ && /R3/'

Maintenant, que se passe-t-il si vous vouliez réellement faire correspondre les chaînes littérales S1 et S2 au lieu des expressions rationnelles R1 et R2? Vous ne pouvez tout simplement pas faire cela en un seul appel à grep, vous devez soit écrire du code pour échapper à toutes les métachars RE avant d'appeler grep:

S1=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<< 'R1')
S2=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<< 'R2')
grep 'S1.*S2|S2.*S1'

ou encore utiliser 2 greps et un pipe:

grep -F 'S1' file | grep -F 'S2'

qui sont encore de mauvais choix alors qu'avec awk vous utilisez simplement un opérateur de chaîne au lieu de l'opérateur regexp:

awk 'index($0,S1) && index($0.S2)'

Maintenant, que se passe-t-il si vous souhaitez faire correspondre 2 expressions rationnelles dans un paragraphe plutôt qu'une ligne? Ne peut pas être fait dans grep, trivial dans awk:

awk -v RS='' '/R1/ && /R2/'

Que diriez-vous de l'ensemble d'un fichier? Encore une fois, cela ne peut pas être fait en grep et trivial en awk (cette fois, j'utilise GNU awk pour RS multi-caractères pour la concision, mais ce n'est pas beaucoup plus de code dans n'importe quel awk ou vous pouvez choisir un contrôle-caractère que vous ne savez pas être en entrée pour que le RS fasse de même):

awk -v RS='^$' '/R1/ && /R2/'

Donc - si vous voulez trouver plusieurs expressions rationnelles ou chaînes dans une ligne ou un paragraphe ou un fichier, n'utilisez pas grep, utilisez awk.

Ed Morton
la source
Est awk '/R1/ && /R2/'-il insensible à la casse?
Prometheus
@Hashim - non. Pour le rendre insensible à la casse avec GNU awk que vous feriez awk -v IGNORECASE=1 '/R1/ && /R2/'et avec n'importe quel awkawk '{x=toupper($0)} x~/R1/ && x~/R2/'
Ed Morton
3
grep string1\|string2 FILENAME 

GNU grep version 3.1

tilikoom
la source
2

Lignes trouvées commençant seulement par 6 espaces et se terminant par:

 cat my_file.txt | grep
 -e '^      .*(\.c$|\.cpp$|\.h$|\.log$|\.out$)' # .c or .cpp or .h or .log or .out
 -e '^      .*[0-9]\{5,9\}$' # numers between 5 and 9 digist
 > nolog.txt
Cristian
la source
2

Disons que nous devons trouver le nombre de mots multiples dans un fichier de test de fichier. Il y a deux façons de procéder

1) Utilisez la commande grep avec le modèle de correspondance d'expression régulière

grep -c '\<\(DOG\|CAT\)\>' testfile

2) Utilisez la commande egrep

egrep -c 'DOG|CAT' testfile 

Avec egrep, vous n'avez pas à vous soucier de l'expression et juste séparer les mots par un séparateur de tuyau.

Amit Singh
la source
2

git grep

Voici la syntaxe à utiliser git grepavec plusieurs modèles:

git grep --all-match --no-index -l -e string1 -e string2 -e string3 file

Vous pouvez également combiner des modèles avec des expressions booléennes telles que --and, --oret --not.

Cherchez man git-grepde l'aide.


--all-matchLorsque vous donnez plusieurs expressions de modèle, cet indicateur est spécifié pour limiter la correspondance aux fichiers dont les lignes correspondent à toutes .

--no-index Recherchez des fichiers dans le répertoire en cours qui ne sont pas gérés par Git.

-l/ --files-with-matches/ --name-onlyAfficher uniquement les noms des fichiers.

-eLe paramètre suivant est le motif. La valeur par défaut consiste à utiliser l'expression rationnelle de base.

Autres paramètres à considérer:

--threads Nombre de threads de travail grep à utiliser.

-q/ --quiet/ --silentNe pas sortir de lignes correspondantes; quitter avec le statut 0 en cas de correspondance.

Pour modifier le type de modèle, vous pouvez également utiliser -G/ --basic-regexp(par défaut), -F/ --fixed-strings, -E/ --extended-regexp, -P/ --perl-regexp, -f fileet d' autres.

En relation:

Pour l' opération OR , voir:

Kenorb
la source
2
Toujours pensé que "git grep" ne peut être exécuté qu'à l'intérieur d'un référentiel git. Je n'étais pas au courant de l'option --no-index. Merci de l'avoir signalé!
Kamaraju Kusumanchi
1

Placez les chaînes que vous souhaitez grep dans un fichier

echo who    > find.txt
echo Roger >> find.txt
echo [44][0-9]{9,} >> find.txt

Recherchez ensuite à l'aide de -f

grep -f find.txt BIG_FILE_TO_SEARCH.txt 
Tim Seed
la source
1
grep '(string1.*string2 | string2.*string1)' filename

obtiendra la ligne avec string1 et string2 dans n'importe quel ordre

James
la source
En quoi est-ce différent des deux premières réponses au moins?
luk2302
1
grep -i -w 'string1\|string2' filename

Cela fonctionne pour une correspondance exacte des mots et des mots insensibles à la casse, pour que -i soit utilisé

Saurabh
la source
0

pour une correspondance multiligne:

echo -e "test1\ntest2\ntest3" |tr -d '\n' |grep "test1.*test3"

ou

echo -e "test1\ntest5\ntest3" >tst.txt
cat tst.txt |tr -d '\n' |grep "test1.*test3\|test3.*test1"

nous avons juste besoin de supprimer le caractère de nouvelle ligne et cela fonctionne!

Puissance du Verseau
la source
0

Vous devriez avoir grepcomme ça:

$ grep 'string1' file | grep 'string2'
Raghuram
la source
1
Cela effectue un ET logique. OP veut un OU logique.
Ben Wheeler
1
@BenWheeler: De la question: "Alors, comment puis-je faire correspondre avec grep uniquement les lignes qui contiennent les deux chaînes?"
Erik I
0

Je rencontre souvent le même problème que le vôtre, et je viens d'écrire un morceau de script:

function m() { # m means 'multi pattern grep'

    function _usage() {
    echo "usage: COMMAND [-inH] -p<pattern1> -p<pattern2> <filename>"
    echo "-i : ignore case"
    echo "-n : show line number"
    echo "-H : show filename"
    echo "-h : show header"
    echo "-p : specify pattern"
    }

    declare -a patterns
    # it is important to declare OPTIND as local
    local ignorecase_flag  filename linum header_flag colon result OPTIND

    while getopts "iHhnp:" opt; do
    case $opt in
        i)
        ignorecase_flag=true ;;
        H)
        filename="FILENAME," ;;
        n)
        linum="NR," ;;
        p)
        patterns+=( "$OPTARG" ) ;;
        h)
        header_flag=true ;;
        \?)
        _usage
        return ;;
    esac
    done

    if [[ -n $filename || -n $linum ]]; then
    colon="\":\","
    fi

    shift $(( $OPTIND - 1 ))

    if [[ $ignorecase_flag == true ]]; then
    for s in "${patterns[@]}"; do
            result+=" && s~/${s,,}/"
    done
    result=${result# && }
    result="{s=tolower(\$0)} $result"
    else
    for s in "${patterns[@]}"; do
            result="$result && /$s/"
    done
    result=${result# && }
    fi

    result+=" { print "$filename$linum$colon"\$0 }"

    if [[ ! -t 0 ]]; then       # pipe case
    cat - | awk "${result}"
    else
    for f in "$@"; do
        [[ $header_flag == true ]] && echo "########## $f ##########"
        awk "${result}" $f
    done
    fi
}

Usage:

echo "a b c" | m -p A 
echo "a b c" | m -i -p A # a b c

Vous pouvez le mettre en .bashrc si vous le souhaitez.

ruanhao
la source
0

Lorsque les deux chaînes sont en séquence, mettez un motif entre les deux sur la grepcommande:

$ grep -E "string1(?.*)string2" file

Exemple si les lignes suivantes sont contenues dans un fichier nommé Dockerfile:

FROM python:3.8 as build-python
FROM python:3.8-slim

Pour obtenir la ligne qui contient les chaînes: FROM pythonet as build-pythonpuis utilisez:

$ grep -E "FROM python:(?.*) as build-python" Dockerfile

Ensuite, la sortie n'affichera que la ligne qui contient les deux chaînes :

FROM python:3.8 as build-python
Chetabahana
la source
-2

ripgrep

Voici l'exemple utilisant rg:

rg -N '(?P<p1>.*string1.*)(?P<p2>.*string2.*)' file.txt

C'est l'un des outils les plus rapides, car il est construit sur le moteur regex de Rust qui utilise des automates finis, SIMD et des optimisations littérales agressives pour rendre la recherche très rapide.

Utilisez-le, surtout lorsque vous travaillez avec des données volumineuses.

Voir également la demande de fonctionnalité associée au GH-875 .

Kenorb
la source
1
Cette réponse n'est pas tout à fait correcte. Les groupes de capture nommés ne sont pas nécessaires, et cela ne gère pas le cas quand string2apparaît avant string1. La solution la plus simple à ce problème est rg string1 file.txt | rg string2.
BurntSushi5