grep per più stringhe in file su righe diverse (es. intero file, non ricerca basata su riga)?


85

Voglio grep per i file contenenti le parole Dansk, Svenskao Norsksu qualsiasi riga, con un codice di ritorno utilizzabile (dato che mi piace solo avere le informazioni che le stringhe sono contenute, il mio one-liner va un po 'oltre questo).

Ho molti file con righe come questa:

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, 
(...)

Ecco lo pseudocodice di quello che voglio:

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

Qual è il modo migliore per farlo? Può essere fatto su una riga?

Risposte:


89

Puoi usare:

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

Se vuoi trovare anche nei file nascosti:

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

Soluzione intelligente; una cosa da notare (in generale; non rilevante per ciò che l'OP stava chiedendo) è che il codice di uscita complessivo sarà 0 anche in caso di fallimento (concettuale). Pertanto, se fossi interessato a determinare il fallimento rispetto al successo, dovresti esaminare se l'output stdout è vuoto o meno, oppure utilizzare invece l'approccio di @ EddSteel.
mklement0

@mklement: in Bash, l' PIPESTATUSarray contiene i valori di uscita dei membri di una pipeline.
Dennis Williamson

@DennisWilliamson Buono a sapersi, grazie. Un'altra opzione è attivare l' pipefailopzione della shell (temporaneamente):shopt -so pipefail
mklement0

4
Potresti voler usare grep -Ze xargs -0se i nomi dei tuoi file possono contenere spazi.
Ben Challenor

1
Ciò può causare errori "Elenco argomenti troppo lungo" se si dispone di molti file.
AnnanFay

23

Ancora un altro modo usando solo bash e grep:

Per un singolo file "test.txt":

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

Verrà stampato test.txtse e solo se il file contiene tutti e tre (in qualsiasi combinazione). I primi due grep non stampano nulla ( -q) e l'ultimo stampa il file solo se gli altri due sono passati.

Se vuoi farlo per ogni file nella directory:

   per f in *; do grep -q Dansk $ f && grep -q Norsk $ f && grep -l Svenska $ f; fatto

ma poi non è necessario eseguire grep 3 volte.
kurumi

1
So che puoi combinare modelli con -e, ma non riuscivo a vedere un modo per creare una congiunzione solo in grep.
Edd Steel

1
Grande; ri for f ...: utilizzare "$f"(virgolette doppie) piuttosto che solo $fper garantire che i nomi di file con spazi incorporati, ecc. siano gestiti correttamente.
mklement0

Il vantaggio di questo approccio rispetto a @ vmpstr è che il codice di uscita riflette correttamente se tutti i termini di ricerca sono stati trovati o meno.
mklement0

19
grep –irl word1 * | grep –il word2 `cat -` | grep –il word3 `cat -`
  • -i rende insensibile al maiuscolo / minuscolo di ricerca
  • -r rende la ricerca di file ricorsiva attraverso le cartelle
  • -l reindirizza l'elenco dei file con la parola trovata
  • cat - fa sì che il prossimo grep esamini i file passati all'elenco.

1
questa è la risposta più semplice e immediata, molto utile grazie!
Majick

9

Come eseguire il grep per più stringhe in file su righe diverse (utilizzare il simbolo pipe):

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

Appunti:

  1. Se usi le virgolette doppie ""con il tuo grep, dovrai uscire dalla pipe in questo modo: \|per cercare Dansk, Norsk e Svenska.

  2. Presuppone che una riga abbia una sola lingua.

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


Non fallirebbe se Dansk Norsk e Svenska appaiano tutti sulla stessa linea?
vmpstr

Sì, in quel caso fallirebbe. Ho pensato che le lingue appaiano una per riga.
Damodharan R

File anche se avessi solo Norsk, ma su tre righe diverse.
Benjamin W.

6

Puoi farlo molto facilmente con ack :

ack -l 'cats' | ack -xl 'dogs'
  • -l: restituisce un elenco di file
  • -x: prendi i file da STDIN (la ricerca precedente) e cerca solo quei file

E puoi continuare a eseguire il piping finché non ottieni solo i file che desideri.


Quando provo questo, dice Unknown option: x. Esiste una certa versione di ack che supporta questo flag x?
Hassan

4
awk '/Dansk/{a=1}/Norsk/{b=1}/Svenska/{c=1}END{ if (a && b && c) print "0" }' 

puoi quindi catturare il valore restituito con la shell

se hai Ruby (1.9+)

ruby -0777 -ne 'print if /Dansk/ and /Norsk/ and /Svenka/' file

1
nella tua clausola awk END, probabilmente vuoi if (a && b && c) {exit 0} else {exit 1}exit !(a && b && c)
:,

la tua soluzione di rubino non sembra giusta. che stamperà solo i paragrafi che contengono tutte le parole di ricerca. la domanda è: il file (nel suo insieme) contiene tutte le parole, anche se non compaiono tutte nello stesso paragrafo.
glenn jackman

Grazie. modificato se è necessario l'intero file, è necessario utilizzare -0777
kurumi

4

Questo ricerca più parole in più file:

egrep 'abc|xyz' file1 file2 ..filen 

2
Oltre a trovare file che hanno entrambe le stringhe, questo troverà anche file che hanno solo 'abc' O 'xyz'. Penso che OP chiedesse file che contengono "abc" E "xyz".
Chris Warth

3

Semplicemente:

grep 'word1\|word2\|word3' *

vedi questo post per maggiori informazioni


Vorrei aggiungere la -lbandiera, ma a parte questo, questa risposta mi sembra la più semplice, a meno che non mi manchi qualcosa.
xdhmoore

Sì, è anche più efficiente poiché non elabori tutti i dati all'interno di più pipe e filtri
moshe beeri

3
La domanda chiede di un'espressione che restituisce file contenenti tutti e tre i termini; restituisce righe (invece di nomi di file) contenenti uno qualsiasi dei tre (invece di tutti e tre).
Benjamin W.

2

Questa è una fusione delle risposte di glenn jackman e kurumi che consente un numero arbitrario di espressioni regolari invece di un numero arbitrario di parole fisse o un insieme fisso di espressioni regolari.

#!/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
    }
}

Eseguilo in questo modo:

./multigrep.awk Dansk Norsk Svenska 'Language: .. - A.*c' dvdfile.dat

2

Ecco cosa ha funzionato bene per me:

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

Se volessi solo trovare file .sh con questi tre, avrei potuto usare:

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

1

Espandendo la risposta awk di @ kurumi, ecco una funzione 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 $?
}

Utilizzo:

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

1

L'ho fatto con due passaggi. Crea un elenco di file csv in un file Con l'aiuto dei commenti di questa pagina ho eseguito due passaggi senza script per ottenere ciò di cui avevo bisogno. Basta digitare nel terminale:

$ 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`

ha fatto esattamente quello che mi serviva: stampare nomi di file contenenti tutte e tre le parole.

Fai anche attenzione ai simboli come `' "


1

Se hai bisogno solo di due termini di ricerca, probabilmente l'approccio più leggibile è eseguire ciascuna ricerca e intersecare i risultati:

 comm -12 <(grep -rl word1 . | sort) <(grep -rl word2 . | sort)

1

Se hai installato git

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

--No-index cerca i file nella directory corrente che non è gestita da Git. Quindi questo comando funzionerà in qualsiasi directory indipendentemente dal fatto che si tratti di un repository git o meno.


0

Oggi ho avuto questo problema e tutte le battute qui non sono riuscite perché i file contenevano spazi nei nomi.

Questo è quello che ho pensato che ha funzionato:

grep -ril <WORD1> | sed 's/.*/"&"/' | xargs grep -il <WORD2>
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.