Une dos cuerdas en una línea con grep


218

Estoy tratando de usar greppara unir líneas que contienen dos cadenas diferentes. He intentado lo siguiente, pero esto coincide con las líneas que contienen cadena1 o cadena2 que no es lo que quiero.

grep 'string1\|string2' filename

Entonces, ¿cómo hago coincidir grepsolo las líneas que contienen ambas cadenas ?


Respuestas:


189

Puedes usar grep 'string1' filename | grep 'string2'

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


55
@AlexanderN, de hecho, no puedo hacer que funcione con multilínea, eso es tan extraño que fue aceptado ...
Aquarius Power

1
No era una pregunta multilínea. Si fuera multilínea, grep -P admite expresiones regulares de estilo Perl ...
Scott Prive

20
Solo funciona cuando tanto 'string1' como 'string2' están en la misma línea. Si desea buscar líneas con 'string1' o 'string2', consulte la respuesta del usuario45949.
lifeson106

10
La primera opción: conectar un grep en un segundo NO produce un resultado OR, produce un resultado AND.
masukomi

1
Yo solíagrep -e "string1" -e "string2"
Ravi Dhoriya ツ

198

Creo que esto es lo que estabas buscando:

grep -E "string1|string2" filename

Creo que respuestas como esta:

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

solo coincida con el caso donde ambos están presentes, no uno u otro o ambos.


14
no grep -e "string1" -e "string2" filenameharía lo mismo?
janosdivenyi

25
así es como grep para string1 O string2. la pregunta indica claramente que están buscando string1 AND string2.
orion elenzil

99
How do I match lines that contains *both* strings?
Estoy

¿Se puede imprimir con una misma línea?
吴毅 凡

1
¿Por qué esta respuesta todavía está aquí? NO es una respuesta a la pregunta.
Prometheus

26

Para buscar archivos que contengan todas las palabras en cualquier orden en cualquier lugar:

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

El primer grep inicia una búsqueda recursiva ( r), ignorando mayúsculas iy minúsculas ( ) y enumerando (imprimiendo) el nombre de los archivos que coinciden ( l) para un término ( 'action'con comillas simples) que aparecen en cualquier parte del archivo.

Los greps posteriores buscan los otros términos, conservan la insensibilidad de mayúsculas y minúsculas y enumeran los archivos coincidentes.

La lista final de archivos que obtendrá serán los que contienen estos términos, en cualquier orden en cualquier parte del archivo.


2
¡Convenido! Solo notaré que tuve que darle a xargs un "-d '\ n'" para manejar nombres de archivos con espacios. Esto funcionó para mí en Linux: grep -ril 'foo' | xargs -d '\n' grep -il 'bar'
Tommy Harris

16

Si usted tiene una grepcon una -Popción para un número limitado perlde expresiones regulares, puede utilizar

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

que tiene la ventaja de trabajar con cadenas superpuestas. Es algo más sencillo usar perlcomo grep, porque puede especificar la lógica y más directamente:

perl -ne 'print if /string1/ && /string2/'

1
La mejor respuesta. Shell es muy fácil y rápido, pero una vez que el patrón se vuelve complejo, debe usar Python o Perl (o Awk). No te golpees la cabeza contra la pared tratando de demostrar que se puede hacer con la cáscara pura (lo que sea que signifique en estos días). Un recordatorio amigos, estas herramientas se pueden usar en la sintaxis de "un revestimiento" que se integran en un script de shell existente.
Scott Prive

12

Tu método era casi bueno, solo faltaba el -w

grep -w 'string1\|string2' filename

1
¡Al menos en OS-X y FreeBSD funciona! Supongo que estás en otra cosa (que el OP no definió; espero que no hayas votado negativamente una respuesta correcta para muchos usuarios excepto tú).
Leo

Estoy en OS-X. ¿Quizás no estoy haciendo esto correctamente? Echa un vistazo a lo que hice: i.imgur.com/PFVlVAG.png
Ariel

1
Impar. Esperaba que la diferencia radicara en no grepping en el archivo, pero, si canalizo mi método con su ls, obtengo un resultado que no: imgur.com/8eTt3Ak.png - Ambos en ambos OS-X 10.9.5 ( "grep (BSD grep) 2.5.1-FreeBSD") y FreeBSD 10 ("grep (GNU grep) 2.5.1-FreeBSD"). Tengo curiosidad por saber cuál es tu grep -V.
Leo

1
Sus ejemplos me están funcionando: i.imgur.com/K8LM69O.png Entonces la diferencia es que este método no recoge subcadenas, tienen que ser cadenas completas por sí mismas. Supongo que necesitarás construir expresiones regulares dentro del grep para buscar subcadenas. Algo como esto:grep -w 'regexp1\|regexp2' filename
Ariel

2
OP muestra un ejemplo haciendo coincidir string1 o string2 y pregunta cómo hacer coincidir las líneas que contienen ambas cadenas. Este ejemplo todavía produce OR.
gustafbstrom

7

El |operador en una expresión regular significa o. Es decir, ya sea string1 o string2 coincidirán. Podrías hacerlo:

grep 'string1' filename | grep 'string2'

que canalizará los resultados del primer comando al segundo grep. Eso debería darte solo líneas que coincidan con ambas.


1
Sus declaraciones son verdaderas, pero no responda la pregunta de OP
Ben Wheeler

Esto responde la pregunta y así es como la mayoría de la gente la escribe.
Peter K

7

Podrías probar algo como esto:

(pattern1.*pattern2|pattern2.*pattern1)

4

Y como la gente sugirió perl y python, y scripts de shell enrevesados, aquí hay un enfoque awk simple :

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

Después de mirar los comentarios a la respuesta aceptada: no, esto no hace líneas múltiples; pero eso tampoco es lo que pidió el autor de la pregunta.


3

No intentes usar grep para esto, usa awk en su lugar. Para unir 2 expresiones regulares R1 y R2 en grep, pensarías que sería:

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

mientras que en awk sería:

awk '/R1/ && /R2/'

pero ¿qué R2pasa si se superpone o es un subconjunto de R1? Ese comando grep simplemente no funcionaría mientras que el comando awk sí. Digamos que desea encontrar líneas que contengan they heat:

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

Tendría que usar 2 greps y una tubería para eso:

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

y, por supuesto, si realmente hubiera requerido que estuvieran separados, siempre puede escribir en awk la misma expresión regular que usó en grep y hay soluciones alternativas awk que no implican repetir las expresiones regulares en cada secuencia posible.

Dejando eso de lado, ¿y si quisieras extender tu solución para que coincida con 3 expresiones regulares R1, R2 y R3? En resumen, esa sería una de estas malas elecciones:

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

mientras que en awk sería conciso, obvio, simple y eficiente:

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

Ahora, ¿qué pasa si realmente quisiera hacer coincidir las cadenas literales S1 y S2 en lugar de las expresiones regulares R1 y R2? Simplemente no puede hacer eso en una llamada a grep, debe escribir el código para escapar de todos los metacares RE antes de llamar a grep:

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

o de nuevo use 2 greps y una pipa:

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

que de nuevo son malas elecciones, mientras que con awk simplemente usa un operador de cadena en lugar del operador regexp:

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

Ahora, ¿qué pasaría si quisieras unir 2 expresiones regulares en un párrafo en lugar de una línea? No se puede hacer en grep, trivial en awk:

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

¿Qué tal en todo un archivo? Una vez más, no se puede hacer en grep y trivial en awk (esta vez estoy usando GNU awk para RS de múltiples caracteres por concisión, pero no es mucho más código en ningún awk o puedes elegir un control-char que sabes que no estar en la entrada para que el RS haga lo mismo):

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

Entonces, si desea encontrar múltiples expresiones regulares o cadenas en una línea, párrafo o archivo, no use grep, use awk.


¿Es awk '/R1/ && /R2/'insensible a mayúsculas y minúsculas?
Prometeo

@Hashim - no. Para que sea insensible a mayúsculas y minúsculas con GNU awk que harías awk -v IGNORECASE=1 '/R1/ && /R2/'y con cualquier awkawk '{x=toupper($0)} x~/R1/ && x~/R2/'
Ed Morton

3
grep string1\|string2 FILENAME 

GNU grep versión 3.1


2

Líneas encontradas que solo comienzan con 6 espacios y terminan con:

 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

Digamos que necesitamos encontrar el recuento de varias palabras en un archivo de prueba. Hay dos formas de hacerlo

1) Use el comando grep con el patrón de coincidencia de expresiones regulares

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

2) Use el comando egrep

egrep -c 'DOG|CAT' testfile 

Con egrep no necesita preocuparse por la expresión y solo separar las palabras mediante un separador de tubería.


2

git grep

Aquí está la sintaxis git grepcon múltiples patrones:

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

También puede combinar patrones con expresiones booleanas como --and, --ory --not.

Busca man git-grepayuda.


--all-matchAl dar múltiples expresiones de patrón, este indicador se especifica para limitar la coincidencia a los archivos que tienen líneas para que coincidan con todos ellos .

--no-index Buscar archivos en el directorio actual que no es administrado por Git.

-l/ --files-with-matches/ --name-onlyMostrar solo los nombres de los archivos.

-eEl siguiente parámetro es el patrón. El valor predeterminado es usar regexp básico.

Otros parámetros a considerar:

--threads Número de hilos de trabajo grep para usar.

-q/ --quiet/ --silentNo generar líneas coincidentes; salir con el estado 0 cuando hay una coincidencia.

Para cambiar el tipo de patrón, también se puede usar -G/ --basic-regexp(por defecto), -F/ --fixed-strings, -E/ --extended-regexp, -P/ --perl-regexp, -f filey otros.

Relacionado:

Para la operación OR , ver:


2
Siempre pensé que "git grep" solo se puede ejecutar dentro de un repositorio git. No conocía la opción --no-index. ¡Gracias por mencionarlo!
Kamaraju Kusumanchi 01 de

1

Coloque las cadenas que desea grep en un archivo

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

Luego busque usando -f

grep -f find.txt BIG_FILE_TO_SEARCH.txt 

1
grep '(string1.*string2 | string2.*string1)' filename

obtendrá línea con string1 y string2 en cualquier orden


¿De qué manera es diferente de al menos las dos respuestas principales?
luk2302

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

Esto funciona para la coincidencia exacta de palabras y las palabras que no distinguen entre mayúsculas y minúsculas, para eso se usa -i


0

para partido multilínea:

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

o

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

¡solo necesitamos eliminar el carácter de nueva línea y funciona!


0

Deberías haberlo hecho grepasí:

$ grep 'string1' file | grep 'string2'

1
Esto realiza un AND lógico. OP quiere un OR lógico.
Ben Wheeler

1
@BenWheeler: De la pregunta: "Entonces, ¿cómo puedo hacer coincidir con grep solo las líneas que contienen ambas cadenas?"
Erik I

0

A menudo me encuentro con el mismo problema que el tuyo, y acabo de escribir una secuencia de comandos:

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
}

Uso:

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

Puede ponerlo en .bashrc si lo desea.


0

Cuando las dos cadenas están en secuencia, coloque un patrón en el grepcomando:

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

Ejemplo si las siguientes líneas están contenidas en un archivo llamado Dockerfile:

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

Para obtener la línea que contiene las cadenas: FROM pythony as build-pythonluego use:

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

Luego, la salida mostrará solo la línea que contiene ambas cadenas :

FROM python:3.8 as build-python

-2

ripgrep

Aquí está el ejemplo usando rg:

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

Es una de las herramientas de grepping más rápidas, ya que está construida sobre el motor regex de Rust que utiliza autómatas finitos, SIMD y optimizaciones literales agresivas para que la búsqueda sea muy rápida.

Úselo, especialmente cuando trabaja con datos de gran tamaño.

Consulte también la solicitud de funciones relacionadas en GH-875 .


1
Esta respuesta no es del todo correcta. Los grupos de captura nombrados son innecesarios, y esto no maneja el caso cuando string2aparece antes string1. La solución más simple a este problema es rg string1 file.txt | rg string2.
BurntSushi5
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.