Comando Linux: come "trovare" solo file di testo?


100

Dopo alcune ricerche da Google, quello che mi viene in mente è:

find my_folder -type f -exec grep -l "needle text" {} \; -exec file {} \; | grep text

che è molto poco pratico e produce testi non necessari come informazioni sul tipo MIME. Qualche soluzione migliore? Ho molte immagini e altri file binari nella stessa cartella con molti file di testo che devo cercare.

Risposte:


184

So che questo è un vecchio thread, ma mi sono imbattuto in esso e ho pensato di condividere il mio metodo che ho trovato essere un modo molto veloce da utilizzare findper trovare solo file non binari:

find . -type f -exec grep -Iq . {} \; -print

L' -Iopzione per grep gli dice di ignorare immediatamente i file binari e l' .opzione insieme a -qfarà corrispondere immediatamente i file di testo in modo che sia molto veloce. Puoi cambiare il -printa -print0per piping in an xargs -0o qualcosa del genere se sei preoccupato per gli spazi (grazie per il suggerimento, @ lucas.werkmeister!)

Anche il primo punto è necessario solo per alcune versioni BSD findcome su OS X, ma non fa male a nulla solo averlo lì tutto il tempo se vuoi metterlo in un alias o qualcosa del genere.

EDIT : come ha sottolineato correttamente @ruslan, il -andpuò essere omesso poiché è implicito.


16
Su Mac OS X, devo cambiarlo in find . -type f -exec grep -Il "" {} \;.
Alec Jacobson

3
Questo è meglio della risposta di peoro perché 1. risponde effettivamente alla domanda 2. Non produce falsi positivi 3. è molto più performante
user123444555621

3
Puoi anche usare find -type f -exec grep -Iq . {} \; -and -printche ha il vantaggio di mantenere i file in find; puoi sostituirlo -printcon un altro -execche viene eseguito solo per file di testo. (Se lasci grepstampare i nomi dei file, non sarai in grado di distinguere i nomi dei file con le nuove righe.)
Lucas Werkmeister,

1
@ NathanS.Watson-Haigh Non dovrebbe, perché dovrebbe corrispondere immediatamente ai file di testo. Hai un caso d'uso specifico che puoi condividere?
crudcore

2
find . -type f -exec grep -Il . {} +è molto più veloce. Lo svantaggio è che non può essere esteso da un altro -execcome suggerito da @ lucas.werkmeister
Henning


10

Perché non è pratico? Se hai bisogno di usarlo spesso e non vuoi digitarlo ogni volta, definisci una funzione bash per esso:

function findTextInAsciiFiles {
    # usage: findTextInAsciiFiles DIRECTORY NEEDLE_TEXT
    find "$1" -type f -exec grep -l "$2" {} \; -exec file {} \; | grep text
}

mettilo nel tuo .bashrce poi esegui:

findTextInAsciiFiles your_folder "needle text"

quando vuoi.


MODIFICA per riflettere la modifica dell'OP:

se vuoi eliminare le informazioni mime puoi semplicemente aggiungere un ulteriore stadio alla pipeline che filtra le informazioni mime. Questo dovrebbe fare il trucco, prendendo solo ciò che viene prima :: cut -d':' -f1:

function findTextInAsciiFiles {
    # usage: findTextInAsciiFiles DIRECTORY NEEDLE_TEXT
    find "$1" -type f -exec grep -l "$2" {} \; -exec file {} \; | grep text | cut -d ':' -f1
}

Non sono sicuro che "testo grep" sia sufficientemente accurato da ottenere esattamente tutti i file di testo - voglio dire, ci sono tipi di file di testo che non hanno "testo" nella stringa della descrizione del tipo MIME?
datasn.io

@ kavoir.com: sì. Dal filemanuale: "Gli utenti dipendono dal fatto che tutti i file leggibili in una directory abbiano la parola" testo "stampata."
peoro

2
Non sarebbe un po 'più intelligente cercare file di testo prima di grepping, invece di grepping e quindi filtrare i file di testo?
utente sconosciuto

/proc/meminfo, /proc/cpuinfoecc. sono file di testo, ma file /proc/meminfodice /proc/meminfo: empty. Mi chiedo se "vuoto" debba essere testato oltre a "testo", ma non sono sicuro che anche altri tipi possano riportare "vuoto".
Timo Kähkönen

"Perché non è pratico?" - "emette testi non necessari". Questa risposta non lo risolve.
user123444555621

4
find . -type f -print0 | xargs -0 file | grep -P text | cut -d: -f1 | xargs grep -Pil "search"

Questo purtroppo non è un risparmio di spazio. Inserirlo nello script bash lo rende un po 'più semplice.

Questo è sicuro per lo spazio:

#!/bin/bash
#if [ ! "$1" ] ; then
    echo "Usage: $0 <search>";
    exit
fi

find . -type f -print0 \
  | xargs -0 file \
  | grep -P text \
  | cut -d: -f1 \
  | xargs -i% grep -Pil "$1" "%"

2
Ci sono un paio di problemi nel tuo script: 1. cosa succede se viene nominato un file binario text.bin? 2. Cosa succede se un nome di file contiene un :?
thkala

3

Un altro modo per farlo:

# find . |xargs file {} \; |grep "ASCII text"

Se vuoi anche file vuoti:

#  find . |xargs file {} \; |egrep "ASCII text|empty"

2

Cosa ne pensi di questo:

$ grep -rl "needle text" my_folder | tr '\n' '\0' | xargs -r -0 file | grep -e ':[^:]*text[^:]*$' | grep -v -e 'executable'

Se vuoi i nomi dei file senza i tipi di file, aggiungi semplicemente un sedfiltro finale .

$ grep -rl "needle text" my_folder | tr '\n' '\0' | xargs -r -0 file | grep -e ':[^:]*text[^:]*$' | grep -v -e 'executable' | sed 's|:[^:]*$||'

È possibile filtrare i tipi di file non necessari aggiungendo più -e 'type'opzioni all'ultimo grepcomando.

MODIFICARE:

Se la tua xargsversione supporta l' -dopzione, i comandi sopra diventano più semplici:

$ grep -rl "needle text" my_folder | xargs -d '\n' -r file | grep -e ':[^:]*text[^:]*$' | grep -v -e 'executable' | sed 's|:[^:]*$||'

sciocco me. Non ho notato grep ricorsivo. come ho capito è in realtà abbastanza veloce anche se un po 'limitato in molte applicazioni. +1 per te.
Antti Rytsölä

2

Ecco come l'ho fatto ...

1. creare un piccolo script per verificare se un file è istext di testo semplice:

#!/bin/bash
[[ "$(file -bi $1)" == *"file"* ]]

2. usa Trova come prima

find . -type f -exec istext {} \; -exec grep -nHi mystring {} \;

Immagino tu intenda == *"text"* ]]?
utente sconosciuto

Puoi invece usare l'operatore di corrispondenza `= ~" text "]]`.
utente sconosciuto

2

Ho due problemi con la risposta di histumness:

  • Elenca solo i file di testo. In realtà non li cerca come richiesto. Per cercare effettivamente, usa

    find . -type f -exec grep -Iq . {} \; -and -print0 | xargs -0 grep "needle text"
    
  • Genera un processo grep per ogni file, che è molto lento. Allora è una soluzione migliore

    find . -type f -print0 | xargs -0 grep -IZl . | xargs -0 grep "needle text"
    

    o semplicemente

    find . -type f -print0 | xargs -0 grep -I "needle text"
    

    Questo richiede solo 0,2 secondi rispetto a 4 secondi per la soluzione sopra (2,5 GB di dati / 7700 file), ovvero 20 volte più veloce .

Inoltre, nessuno ha citato ag, Silver Searcher o ack-grep come alternative. Se uno di questi è disponibile, sono alternative molto migliori:

ag -t "needle text"    # Much faster than ack
ack -t "needle text"   # or ack-grep

Come ultima nota, attenzione ai falsi positivi (file binari presi come file di testo). Avevo già un falso positivo usando grep / ag / ack, quindi è meglio elencare i file corrispondenti prima di modificare i file.


1

Sebbene sia una vecchia domanda, penso che queste informazioni qui sotto si aggiungeranno alla qualità delle risposte qui.

Quando ignoro i file con il bit eseguibile impostato, utilizzo semplicemente questo comando:

find . ! -perm -111

Per evitare che entri ricorsivamente in altre directory:

find . -maxdepth 1 ! -perm -111

Non c'è bisogno che le pipe mescolino molti comandi, solo il potente comando semplice find .

  • Disclaimer: non è esattamente quello che ha chiesto OP, perché non controlla se il file è binario o meno. Ad esempio, filtrerà i file di script bash , che sono di per sé testo ma con il bit eseguibile impostato .

Detto questo, spero che questo sia utile a chiunque.


0

Lo faccio in questo modo: 1) poiché ci sono troppi file (~ 30k) per cercare attraverso, generi quotidianamente l'elenco dei file di testo da utilizzare tramite crontab usando il comando seguente:

find /to/src/folder -type f -exec file {} \; | grep text | cut -d: -f1 > ~/.src_list &

2) crea una funzione in .bashrc:

findex() {
    cat ~/.src_list | xargs grep "$*" 2>/dev/null
}

Quindi posso usare il comando seguente per eseguire la ricerca:

findex "needle text"

HTH :)


0

Preferisco xargs

find . -type f | xargs grep -I "needle text"

se i nomi dei tuoi file sono strani, cerca usando le opzioni -0:

find . -type f -print0 | xargs -0 grep -I "needle text"

0
  • bash esempio per cercare il testo "eth0" in / etc in tutti i file text / ascii

grep eth0 $ (trova / etc / -type f -exec file {} \; | egrep -i "text | ascii" | cut -d ':' -f1)


0

Ecco una versione semplificata con spiegazione estesa per principianti come me che stanno cercando di imparare a mettere più di un comando in una riga.

Se dovessi scrivere il problema in passaggi, sarebbe simile a questo:

// For every file in this directory
// Check the filetype
// If it's an ASCII file, then print out the filename

Per raggiungere questo obiettivo, possiamo utilizzare tre comandi UNIX: find, file, e grep.

find controllerà ogni file nella directory.

fileci darà il tipo di file. Nel nostro caso, stiamo cercando un ritorno di "testo ASCII"

grep cercherà la parola chiave "ASCII" nell'output di file

Quindi come possiamo metterli insieme in una singola riga? Ci sono molti modi per farlo, ma trovo che farlo nell'ordine del nostro pseudo-codice abbia più senso (specialmente per un principiante come me).

find ./ -exec file {} ";" | grep 'ASCII'

Sembra complicato, ma non male quando lo analizziamo:

find ./= cerca in tutti i file in questa directory. Il findcomando stampa il nome del file di qualsiasi file che corrisponde all '"espressione", o qualsiasi cosa venga dopo il percorso, che nel nostro caso è la directory corrente o./

La cosa più importante da capire è che tutto ciò che segue quel primo bit verrà valutato come Vero o Falso. Se True, il nome del file verrà stampato. In caso contrario, il comando prosegue.

-exec= questo flag è un'opzione all'interno del comando find che ci permette di usare il risultato di qualche altro comando come espressione di ricerca. È come chiamare una funzione all'interno di una funzione.

file {}= il comando che viene chiamato all'interno di find. Il filecomando restituisce una stringa che ti dice il tipo di file di un file. Regolarmente, sarebbe simile a questa: file mytextfile.txt. Nel nostro caso, vogliamo che utilizzi qualsiasi file venga esaminato dal findcomando, quindi inseriamo le parentesi graffe {}per agire come una variabile o un parametro vuoto. In altre parole, stiamo solo chiedendo al sistema di emettere una stringa per ogni file nella directory.

";"= questo è richiesto da finded è il segno di punteggiatura alla fine del nostro -execcomando. Vedere il manuale per 'trovare' maggiori spiegazioni se ne avete bisogno eseguendo man find.

| grep 'ASCII'= |è una pipe. Pipe prende l'output di tutto ciò che è a sinistra e lo usa come input per ciò che è a destra. Prende l'output del findcomando (una stringa che è il tipo di file di un singolo file) e lo verifica per vedere se contiene la stringa 'ASCII'. Se lo fa, restituisce vero.

ORA, l'espressione a destra di find ./restituirà true quando il grepcomando restituirà true. Ecco.


0

Se sei interessato a trovare qualsiasi tipo di file in base ai loro byte magici usando la fantastica fileutility combinata con la potenza di find, questo può tornare utile:

$ # Let's make some test files
$ mkdir ASCII-finder
$ cd ASCII-finder
$ dd if=/dev/urandom of=binary.file bs=1M count=1
1+0 records in
1+0 records out
1048576 bytes (1.0 MB, 1.0 MiB) copied, 0.009023 s, 116 MB/s
$ file binary.file
binary.file: data
$ echo 123 > text.txt
$ # Let the magic begin
$ find -type f -print0 | \
    xargs -0 -I @@ bash -c 'file "$@" | grep ASCII &>/dev/null && echo "file is ASCII: $@"' -- @@

Produzione:

file is ASCII: ./text.txt

Legenda: $è il prompt della shell interattivo in cui inseriamo i nostri comandi

Puoi modificare la parte dopo &&per chiamare qualche altro script o fare anche altre cose in linea, cioè se quel file contiene una data stringa, cat l'intero file o cercare una stringa secondaria in esso.

Spiegazione:

  • find elementi che sono file
  • Trasforma xargsogni elemento in una riga in un bash comando / script di riga
  • filecontrolla il tipo di file per magic byte, grepcontrolla se ASCII esiste, in tal caso, quindi dopo &&l'esecuzione del prossimo comando.
  • findstampa i risultati nullseparati, questo è utile per eseguire l'escape di nomi di file con spazi e metacaratteri.
  • xargs, usando l' -0opzione, li legge nullseparati, -I @@ prende ogni record e usa come parametro / argomenti posizionali per bash script.
  • --for bashassicura che tutto ciò che viene dopo è un argomento anche se inizia con un -simile -cche potrebbe altrimenti essere interpretato come opzione bash

Se devi trovare tipi diversi da ASCII, sostituisci semplicemente grep ASCIIcon un altro tipo, comegrep "PDF document, version 1.4"


-1
find . -type f | xargs file | grep "ASCII text" | awk -F: '{print $1}'

Usa il comando find per elencare tutti i file, usa il comando file per verificare che siano testo (non tar, key), infine usa il comando awk per filtrare e stampare il risultato.


-4

Cosa ne pensi di questo

 find . -type f|xargs grep "needle text"

Questo non sembra"needle text"
peoro

@Navi: l'esempio OP fornito trova solo file contenenti"needl text"
peoro

3
@Navi: ora non cerca più file di testo: se un file binario lo contiene "needle text"verrebbe trovato
peoro

Perché ti sto ascoltando?
Navi

1
@Navi: il tuo one-liner non controlla i tipi di file e ha anche grossi problemi con gli spazi bianchi nei nomi dei file ...
thkala
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.