grep para múltiples cadenas en un archivo en diferentes líneas (es decir, archivo completo, no búsqueda basada en líneas)?

85

Quiero grep para los archivos que contienen las palabras Dansk, Svenskao Norsken cualquier línea, con un código de retorno utilizable (ya que realmente solo me gusta tener la información de que las cadenas están contenidas, mi línea única va un poco más allá de esto).

Tengo muchos archivos con líneas como esta:

Disc Title: unknown
Title: 01, Length: 01:33:37.000 Chapters: 33, Cells: 31, Audio streams: 04, Subpictures: 20
        Subtitle: 01, Language: ar - Arabic, Content: Undefined, Stream id: 0x20, 
        Subtitle: 02, Language: bg - Bulgarian, Content: Undefined, Stream id: 0x21, 
        Subtitle: 03, Language: cs - Czech, Content: Undefined, Stream id: 0x22, 
        Subtitle: 04, Language: da - Dansk, Content: Undefined, Stream id: 0x23, 
        Subtitle: 05, Language: de - Deutsch, Content: Undefined, Stream id: 0x24, 
(...)

Aquí está el pseudocódigo de lo que quiero:

for all files in directory;
 if file contains "Dansk" AND "Norsk" AND "Svenska" then
 then echo the filename
end

¿Cuál es la mejor manera de hacer esto? ¿Se puede hacer en una línea?

cristiano
fuente

Respuestas:

89

Puedes usar:

grep -l Dansk * | xargs grep -l Norsk | xargs grep -l Svenska

Si también desea buscar en archivos ocultos:

grep -l Dansk .* | xargs grep -l Norsk | xargs grep -l Svenska
vmpstr
fuente
Solución inteligente; una cosa a tener en cuenta (en términos generales; no es relevante para lo que pedía el OP) es que el código de salida general será 0 incluso en caso de falla (conceptual). Por lo tanto, si estuviera interesado en determinar el fracaso frente al éxito, tendría que examinar si la salida estándar está vacía o no, o emplear el enfoque de @ EddSteel en su lugar.
mklement0
@mklement: en Bash, la PIPESTATUSmatriz contiene los valores de salida de los miembros de una canalización.
Pausado hasta nuevo aviso.
@DennisWilliamson Es bueno saberlo, gracias. Otra opción es convertir la pipefailopción shell en (temporalmente):shopt -so pipefail
mklement0
4
Es posible que desee utilizar grep -Zy xargs -0si sus nombres de archivo pueden contener espacios.
Ben Challenor
1
Esto puede provocar errores de "Lista de argumentos demasiado larga" si tiene muchos archivos.
AnnanFay
23

Otra forma más usando bash y grep:

Para un solo archivo 'test.txt':

  grep -q Dansk test.txt && grep -q Norsk test.txt && grep -l Svenska test.txt

Se imprimirá test.txtsi el archivo contiene los tres (en cualquier combinación). Los dos primeros greps no imprimen nada ( -q) y el último solo imprime el archivo si los otros dos han pasado.

Si desea hacerlo para cada archivo del directorio:

   para f en *; hacer grep -q Dansk $ f && grep -q Norsk $ f && grep -l Svenska $ f; hecho
Acero Edd
fuente
pero luego no es necesario ejecutar grep 3 veces.
Kurumi
1
Sé que puedes combinar patrones con -e, pero no pude ver una forma de hacer una conjunción solo en grep.
Edd Steel
1
Excelente; re for f ...: use "$f"(entre comillas dobles) en lugar de solo $fasegurarse de que los nombres de archivo con espacios incrustados, etc. se manejen correctamente.
mklement0
La ventaja de este enfoque sobre @ vmpstr es que el código de salida refleja correctamente si todos los términos de búsqueda se encontraron o no.
mklement0
19
grep –irl word1 * | grep –il word2 `cat -` | grep –il word3 `cat -`
  • -i hace que la búsqueda no distinga entre mayúsculas y minúsculas
  • -r hace que la búsqueda de archivos sea recursiva a través de carpetas
  • -l canaliza la lista de archivos con la palabra encontrada
  • cat - hace que el siguiente grep revise los archivos pasados ​​a su lista.
Gerry
fuente
1
esta es la respuesta más simple y directa, ¡muy útil gracias!
majick
9

Cómo hacer grep para múltiples cadenas en un archivo en diferentes líneas (use el símbolo de tubería):

for file in *;do 
   test $(grep -E 'Dansk|Norsk|Svenska' $file | wc -l) -ge 3 && echo $file
done

Notas:

  1. Si usa comillas dobles ""con su grep, tendrá que escapar de la tubería de esta manera: \|para buscar Dansk, Norsk y Svenska.

  2. Asume que una línea tiene un solo idioma.

Tutorial: http://www.cyberciti.biz/faq/howto-use-grep-command-in-linux-unix/

Damodharan R
fuente
¿No fallaría eso si Dansk Norsk y Svenska aparecieran todos en la misma línea?
vmpstr
Sí, fallaría en ese caso. Supuse que los idiomas aparecen uno por línea.
Damodharan R
También archivaría si solo tuviera Norsk, pero en tres líneas diferentes.
Benjamin W.
6

Puedes hacer esto muy fácilmente con ack :

ack -l 'cats' | ack -xl 'dogs'
  • -l: devuelve una lista de archivos
  • -x: toma los archivos de STDIN (la búsqueda anterior) y busca solo esos archivos

Y puede seguir canalizando hasta que obtenga solo los archivos que desea.

Ben Johnson
fuente
Cuando intento esto, dice Unknown option: x. ¿Existe alguna versión de ack que admita esta bandera x?
Hassan
4
awk '/Dansk/{a=1}/Norsk/{b=1}/Svenska/{c=1}END{ if (a && b && c) print "0" }' 

luego puede capturar el valor de retorno con el shell

si tienes Ruby (1.9+)

ruby -0777 -ne 'print if /Dansk/ and /Norsk/ and /Svenka/' file
Kurumi
fuente
1
en su cláusula awk END, probablemente quiera:, if (a && b && c) {exit 0} else {exit 1}o más lacónicamenteexit !(a && b && c)
Glenn Jackman
su solución de rubí no se ve bien. que solo imprimirá párrafos que contengan todas las palabras de búsqueda. la pregunta es: ¿el archivo (como un todo) contiene todas las palabras, incluso si no aparecen todas en el mismo párrafo?
Glenn Jackman
Gracias. cambiado si se necesita todo el archivo, entonces debe usar -0777
kurumi
4

Esto busca varias palabras en varios archivos:

egrep 'abc|xyz' file1 file2 ..filen 
Sarath Chandra
fuente
2
Además de buscar archivos que tengan ambas cadenas, también encontrará archivos que tengan 'abc' O 'xyz' solo. Creo que OP estaba solicitando archivos que contengan 'abc' Y 'xyz'.
Chris Warth
3

Simplemente:

grep 'word1\|word2\|word3' *

ver esta publicación para más información

Moshe Beeri
fuente
Agregaría la -lbandera, pero aparte de eso, esta respuesta me parece la más sencilla, a menos que me falte algo.
xdhmoore
Sí, también es más eficiente ya que no procesa todos los datos dentro de múltiples tuberías y filtros
moshe beeri
3
La pregunta se refiere a una expresión que devuelve archivos que contienen los tres términos; esto devuelve líneas (en lugar de nombres de archivo) que contienen cualquiera de los tres (en lugar de los tres).
Benjamin W.
2

Esta es una combinación de las respuestas de glenn jackman y kurumi que permite un número arbitrario de expresiones regulares en lugar de un número arbitrario de palabras fijas o un conjunto fijo de expresiones regulares.

#!/usr/bin/awk -f
# by Dennis Williamson - 2011-01-25

BEGIN {
    for (i=ARGC-2; i>=1; i--) {
        patterns[ARGV[i]] = 0;
        delete ARGV[i];
    }
}

{
    for (p in patterns)
        if ($0 ~ p)
            matches[p] = 1
            # print    # the matching line could be printed
}

END {
    for (p in patterns) {
        if (matches[p] != 1)
            exit 1
    }
}

Ejecútelo así:

./multigrep.awk Dansk Norsk Svenska 'Language: .. - A.*c' dvdfile.dat
Pausado hasta nuevo aviso.
fuente
2

Esto es lo que funcionó bien para mí:

find . -path '*/.svn' -prune -o -type f -exec gawk '/Dansk/{a=1}/Norsk/{b=1}/Svenska/{c=1}END{ if (a && b && c) print FILENAME }' {} \;
./path/to/file1.sh
./another/path/to/file2.txt
./blah/foo.php

Si solo quisiera encontrar archivos .sh con estos tres, entonces podría haber usado:

find . -path '*/.svn' -prune -o -type f -name "*.sh" -exec gawk '/Dansk/{a=1}/Norsk/{b=1}/Svenska/{c=1}END{ if (a && b && c) print FILENAME }' {} \;
./path/to/file1.sh
Nick Henry
fuente
1

Ampliando la respuesta awk de @ kurumi, aquí hay una función bash:

all_word_search() {
    gawk '
        BEGIN {
            for (i=ARGC-2; i>=1; i--) {
                search_terms[ARGV[i]] = 0;
                ARGV[i] = ARGV[i+1];
                delete ARGV[i+1];
            }
        }
        {
            for (i=1;i<=NF; i++) 
                if ($i in search_terms) 
                    search_terms[$1] = 1
        }
        END {
            for (word in search_terms) 
                if (search_terms[word] == 0) 
                    exit 1
        }
    ' "$@"
    return $?
}

Uso:

if all_word_search Dansk Norsk Svenska filename; then
    echo "all words found"
else
    echo "not all words found"
fi
Glenn Jackman
fuente
1

Hice eso con dos pasos. Haga una lista de archivos csv en un solo archivo Con la ayuda de los comentarios de esta página, hice dos pasos sin guiones para obtener lo que necesitaba. Simplemente escriba en la terminal:

$ find /csv/file/dir -name '*.csv' > csv_list.txt
$ grep -q Svenska `cat csv_list.txt` && grep -q Norsk `cat csv_list.txt` && grep -l Dansk `cat csv_list.txt`

Hizo exactamente lo que necesitaba: imprimir los nombres de los archivos que contienen las tres palabras.

También tenga en cuenta los símbolos como `' "

Simas
fuente
1

Si solo necesita dos términos de búsqueda, posiblemente el enfoque más legible es ejecutar cada búsqueda e intersecar los resultados:

 comm -12 <(grep -rl word1 . | sort) <(grep -rl word2 . | sort)
Ankur Dave
fuente
1

Si tienes git instalado

git grep -l --all-match --no-index -e Dansk -e Norsk -e Svenska

--No-index busca archivos en el directorio actual que no está administrado por Git. Entonces, este comando funcionará en cualquier directorio, independientemente de si es un repositorio de git o no.

Kamaraju Kusumanchi
fuente
0

Tuve este problema hoy, y todas las frases ingeniosas aquí me fallaron porque los archivos contenían espacios en los nombres.

Esto es lo que se me ocurrió que funcionó:

grep -ril <WORD1> | sed 's/.*/"&"/' | xargs grep -il <WORD2>
Giusti
fuente