web-dev-qa-db-fra.com

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

J'essaie d'utiliser grep pour faire correspondre les lignes contenant deux chaînes différentes. J'ai essayé ce qui suit, mais cela correspond aux lignes qui contiennent soit string1 o string2 ce qui n'est pas ce que je veux.

grep 'string1\|string2' filename

Alors, comment faire correspondre avec grep uniquement les lignes contenant les deux chaînes ?

197
hearsaxas

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

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

171
dheerosaur

Je pense que c'est ce que vous cherchiez:

grep -E "string1|string2" filename

Je pense que cela répond comme ceci:

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

ne correspond que dans le cas où les deux sont présents, pas l'un ou l'autre ou les deux.

195
user45949

Donnez-lui simplement plusieurs options -e.

 -e pattern, --regexp=pattern
         Specify a pattern used during the search of the input: an input
         line is selected if it matches any of the specified patterns.
         This option is most useful when multiple -e options are used to
         specify multiple patterns, or when a pattern begins with a dash
         (`-').

Ainsi, la commande devient:

grep -e "string1" -e "string2" filename

Remarque: J'ai cité ci-dessus le manuel de la version BSD, mais il semble que ce soit identique sous Linux .

44
Tony

Pour rechercher des fichiers contenant tous les mots dans n’importe quel ordre:

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

Le premier grep lance une recherche récursive (r) en ignorant la casse (i) et en listant (en imprimant) le nom des fichiers qui correspondent (l) à un terme ( 'action' avec les guillemets simples) survenant 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 ceux qui contiennent ces termes, dans n'importe quel ordre, dans le fichier.

26
Kinjal Dixit

Si vous avez une grep avec une option -P pour une regex Perl limitée, vous pouvez utiliser

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

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

Perl -ne 'print if /string1/ && /string2/'
14
tchrist

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

grep -w 'string1\|string2' filename
12
Leo

Vous pouvez essayer quelque chose comme ça:

(pattern1.*pattern2|pattern2.*pattern1)
7
Dorn

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

grep 'string1' filename | grep 'string2'

qui dirigera les résultats de la première commande vers le second grep. Cela devrait vous donner que des lignes qui correspondent à la fois.

6
martineno

Et comme les gens suggéraient Perl et python et les scripts Shell compliqués, voici une approche simple awk:

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

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

3
tink

Lignes trouvées commençant seulement par 6 espaces et finies 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
2
Cristian

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

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

en awk ce serait:

awk '/R1/ && /R2/'

mais que se passe-t-il si R2 chevauche 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 rechercher des lignes contenant the et heat:

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

Vous devriez utiliser 2 greps et un tuyau pour cela:

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

et bien sûr, si vous aviez réellement exigé qu'ils soient séparés, vous pouvez toujours écrire dans awk la même expression rationnelle que celle utilisée dans grep. Il existe des solutions alternatives à awk qui n'impliquent pas de répéter les expressions rationnelles dans toutes les séquences possibles.

En mettant cela de côté, si vous vouliez étendre votre solution aux 3 expressions rationnelles R1, R2 et R3. En grep, ce serait 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

dans awk ce serait le concis, évident, simple, efficace:

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

Maintenant, 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 simplement pas faire cela dans un appel à grep, vous devez soit écrire du code pour échapper à tous 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 utilisez 2 greps et un pipe:

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

encore une fois, ce sont de mauvais choix alors qu'avec awk, vous utilisez simplement un opérateur de chaîne de caractères au lieu d'un opérateur de regexp

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

Maintenant, si vous vouliez faire correspondre deux expressions rationnelles dans un paragraphe plutôt que dans une ligne? Ne peut être fait dans grep, trivial dans awk:

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

Que diriez-vous de tout un fichier? Encore une fois, cela ne peut pas être fait dans grep et trivial dans awk (cette fois, j’utilise GNU awk pour une RS à caractères multiples, mais ce n’est pas beaucoup plus de code dans n'importe quelle awk ou vous pouvez choisir un contrôle- vous ne saurez pas que le RS fera la même chose):

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

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

2
Ed Morton

Disons que nous devons trouver le nombre de mots multiples dans un fichier de test de fichier. Il y a deux façons de s'y prendre

1) Utiliser la commande grep avec le motif correspondant à l'expression rationnelle

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

2) Utiliser la commande egrep

egrep -c 'DOG|CAT' testfile 

Avec egrep, vous n'avez pas à vous soucier de l'expression, mais séparez les mots à l'aide d'un séparateur de tuyau.

2
Amit Singh
grep ‘string1\|string2’ FILENAME 

GNU grep version 3.1

2
tilikoom

git grep

Voici la syntaxe utilisant git grep avec plusieurs modèles:

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

Vous pouvez également combiner des motifs avec des expressions booléennes telles que _--and_, _--or_ et _--not_.

Consultez _man git-grep_ pour obtenir de l'aide.


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

_--no-index_ Recherche dans le répertoire actuel les fichiers qui ne sont pas gérés par Git.

_-l_/_--files-with-matches_/_--name-only_ Affiche uniquement les noms de fichiers.

_-e_ Le paramètre suivant est le motif. La valeur par défaut consiste à utiliser une expression rationnelle de base.

Autres paramètres à prendre en compte:

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

_-q_/_--quiet_/_--silent_ Ne pas afficher les lignes correspondantes; quitter avec le statut 0 quand il y a une correspondance.

Pour changer le type de motif, vous pouvez également utiliser _-G_/_--basic-regexp_ (par défaut), _-F_/_--fixed-strings_, _-E_/_--extended-regexp_, _-P_/_--Perl-regexp_, _-f file_ et autres.

Apparenté, relié, connexe:

Pour l'opération OU, voir:

1
kenorb
grep -i -w 'string1\|string2' filename

Cela fonctionne pour la correspondance exacte de mots et la correspondance de mots insensibles à la casse, car -i est utilisé

1
Saurabh

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

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

Puis recherchez en utilisant -f

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

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

1
James

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!

0
Aquarius Power

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 voulez.

0
ruanhao

Vous devriez avoir grep comme ceci:

$ grep 'string1' file | grep 'string2'
0
Raghuram