Comando Linux: Como 'encontrar' apenas arquivos de texto?


100

Depois de algumas pesquisas no Google, o que descobri é:

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

que é muito inconveniente e produz textos desnecessários, como informações de tipo MIME. Alguma solução melhor? Tenho muitas imagens e outros arquivos binários na mesma pasta com muitos arquivos de texto que preciso pesquisar.

Respostas:


184

Eu sei que este é um tópico antigo, mas me deparei com ele e pensei em compartilhar meu método, que descobri ser uma maneira muito rápida de usar findpara localizar apenas arquivos não binários:

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

A -Iopção de grep diz a ele para ignorar imediatamente os arquivos binários e a .opção junto com o -qfará com que ele corresponda imediatamente aos arquivos de texto, de forma que seja muito rápido. Você pode mudar o -printpara um -print0para tubulações em xargs -0ou algo se estiver preocupado com os espaços (obrigado pela dica, @ lucas.werkmeister!)

Além disso, o primeiro ponto só é necessário para certas versões do BSD find, como no OS X, mas não atrapalha nada apenas tê-lo lá o tempo todo se você quiser colocar isso em um alias ou algo assim.

EDITAR : Como @ruslan corretamente apontou, o -andpode ser omitido uma vez que está implícito.


16
No Mac OS X, preciso mudar isso para find . -type f -exec grep -Il "" {} \;.
Alec Jacobson

3
Isso é melhor do que a resposta de peoro porque 1. ele realmente responde à pergunta 2. Não produz falsos positivos 3. tem muito mais desempenho
usuário123444555621

3
Você também pode usar o find -type f -exec grep -Iq . {} \; -and -printque tem a vantagem de manter os arquivos armazenados find; você pode substituir -printpor outro -execque só é executado para arquivos de texto. (Se você deixar grepimprimir os nomes dos arquivos, não será possível distinguir os nomes dos arquivos com novas linhas neles.)
Lucas Werkmeister

1
@ NathanS.Watson-Haigh Não deveria, porque deveria combinar arquivos de texto imediatamente. Você tem um caso de uso específico para compartilhar?
crudcore

2
find . -type f -exec grep -Il . {} +é muito mais rápido. A desvantagem é que não pode ser estendido por outra pessoa, -execconforme sugeriu @ lucas.werkmeister
Henning


10

Por que não é prático? Se você precisa usá-lo com frequência e não deseja digitá-lo todas as vezes, basta definir uma função bash para ele:

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

coloque-o no seu .bashrce depois execute:

findTextInAsciiFiles your_folder "needle text"

quando você quiser.


EDITAR para refletir a edição do OP:

se você quiser cortar as informações de mímica, pode simplesmente adicionar mais um estágio ao pipeline que filtra as informações de mímica. Isso deve fazer o truque, tomando apenas o que vem antes :: 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
}

Não tenho certeza se "texto grep" é preciso o suficiente para obter exatamente todos os arquivos de texto - quero dizer, há algum tipo de arquivo de texto que não tenha 'texto' na string de sua descrição de tipo MIME?
datasn.io

@ kavoir.com: sim. Do filemanual: "Os usuários dependem de saber que todos os arquivos legíveis em um diretório têm a palavra 'texto' impressa."
peoro

2
Não seria um pouco mais inteligente pesquisar arquivos de texto antes de fazer o grep, em vez de fazer o grep e filtrar os arquivos de texto?
usuário desconhecido

/proc/meminfo, /proc/cpuinfoetc. são arquivos de texto, mas file /proc/meminfodiz /proc/meminfo: empty. Eu me pergunto se 'vazio' deve ser testado além de 'texto', mas não tenho certeza se outros tipos podem relatar 'vazio'.
Timo Kähkönen

"Por que não é prático?" - "emite textos desnecessários". Esta resposta não resolve isso.
user123444555621

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

Infelizmente, isso não é economia de espaço. Colocar isso no script bash torna isso um pouco mais fácil.

Este é um espaço seguro:

#!/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
Existem alguns problemas em seu script: 1. e se um arquivo binário for nomeado text.bin? 2. E se um nome de arquivo contiver um :?
thkala de

3

Outra maneira de fazer isso:

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

Se você quiser arquivos vazios também:

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

2

Que tal agora:

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

Se você quiser os nomes dos arquivos sem os tipos de arquivos, basta adicionar um sedfiltro final .

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

Você pode filtrar tipos de arquivo desnecessários adicionando mais -e 'type'opções ao último grepcomando.

EDITAR:

Se sua xargsversão suportar a -dopção, os comandos acima se tornarão mais simples:

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

boba eu. Não notei grep recursivo. como eu entendi, é realmente muito rápido, embora um pouco limitado em muitas aplicações. 1 para você.
Antti Rytsölä

2

Veja como eu fiz ...

1 faça um pequeno script para testar se um arquivo é texto simples istext:

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

2 use encontrar como antes

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

Eu acho que você quer dizer == *"text"* ]]?
usuário desconhecido

Você pode usar o operador de correspondência `= ~" texto "]]` em vez disso.
usuário desconhecido

2

Tenho dois problemas com a resposta da histum:

  • Ele lista apenas arquivos de texto. Na verdade, ele não os pesquisa conforme solicitado. Para pesquisar, use

    find . -type f -exec grep -Iq . {} \; -and -print0 | xargs -0 grep "needle text"
    
  • Ele gera um processo grep para cada arquivo, que é muito lento. A melhor solução é então

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

    ou simplesmente

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

    Isso leva apenas 0,2s em comparação com 4s para a solução acima (2,5 GB de dados / arquivos 7700), ou seja, 20x mais rápido .

Além disso, ninguém citou ag, o Silver Searcher ou ack-grep ¸as alternativas. Se um deles estiver disponível, eles são alternativas muito melhores:

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

Como última nota, tome cuidado com os falsos positivos (arquivos binários tomados como arquivos de texto). Eu já tinha falsos positivos usando grep / ag / ack, então é melhor listar os arquivos correspondentes antes de editar os arquivos.


1

Embora seja uma pergunta antiga, acho que as informações a seguir irão aumentar a qualidade das respostas aqui.

Ao ignorar arquivos com o conjunto de bits executáveis , apenas uso este comando:

find . ! -perm -111

Para evitar que ele recursivamente entre em outros diretórios:

find . -maxdepth 1 ! -perm -111

Não há necessidade de tubos para misturar muitos comandos, apenas o poderoso comando simples find .

  • Disclaimer: não é exatamente o que o OP pediu, pois não verifica se o arquivo é binário ou não. Ele irá, por exemplo, filtrar os arquivos de script bash , que são textos , mas têm o bit executável definido .

Dito isso, espero que isso seja útil para alguém.


0

Eu faço desta forma: 1) como há muitos arquivos (~ 30k) para pesquisar, eu gero a lista de arquivos de texto diariamente para uso via crontab usando o comando abaixo:

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

2) crie uma função em .bashrc:

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

Então posso usar o comando abaixo para fazer a pesquisa:

findex "needle text"

HTH :)


0

Eu prefiro xargs

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

se seus nomes de arquivo são estranhos, procure usando as opções -0:

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

0
  • exemplo bash para pesquisar o texto "eth0" em / etc em todos os arquivos texto / ascii

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


0

Esta é uma versão simplificada com explicação estendida para iniciantes como eu que estão tentando aprender como colocar mais de um comando em uma linha.

Se você escrevesse o problema em etapas, seria assim:

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

Para isso, podemos usar três comandos UNIX: find, file, e grep.

find irá verificar todos os arquivos no diretório.

filenos dará o tipo de arquivo. Em nosso caso, estamos procurando um retorno de 'texto ASCII'

grep irá procurar a palavra-chave 'ASCII' na saída de file

Então, como podemos amarrá-los em uma única linha? Existem várias maneiras de fazer isso, mas acho que fazê-lo na ordem de nosso pseudocódigo faz mais sentido (especialmente para um iniciante como eu).

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

Parece complicado, mas não é ruim quando o dividimos:

find ./= examine cada arquivo neste diretório. O findcomando imprime o nome do arquivo de qualquer arquivo que corresponda à 'expressão', ou o que vier depois do caminho, que no nosso caso é o diretório atual ou./

A coisa mais importante a entender é que tudo após o primeiro bit será avaliado como verdadeiro ou falso. Se for True, o nome do arquivo será impresso. Se não, o comando segue em frente.

-exec= este sinalizador é uma opção dentro do comando find que nos permite usar o resultado de algum outro comando como a expressão de pesquisa. É como chamar uma função dentro de uma função.

file {}= o comando sendo chamado dentro de find. O filecomando retorna uma string que informa o tipo de arquivo de um arquivo. Regularmente, ele ficaria assim: file mytextfile.txt. Em nosso caso, queremos que ele use qualquer arquivo que esteja sendo examinado pelo findcomando, então colocamos as chaves {}para atuar como uma variável vazia ou parâmetro. Em outras palavras, estamos apenas pedindo que o sistema produza uma string para cada arquivo no diretório.

";"= isso é exigido por finde é a marca de pontuação no final do nosso -execcomando. Consulte o manual para 'encontrar' para obter mais explicações se precisar executando man find.

| grep 'ASCII'= |é um tubo. Pipe pega a saída de tudo o que está à esquerda e a usa como entrada para o que está à direita. Ele pega a saída do findcomando (uma string que é o tipo de arquivo de um único arquivo) e a testa para ver se contém a string 'ASCII'. Em caso afirmativo, ele retorna verdadeiro.

AGORA, a expressão à direita de find ./retornará verdadeiro quando o grepcomando retornar verdadeiro. Voila.


0

Se você estiver interessado em encontrar qualquer tipo de arquivo por seus bytes mágicos, usando o incrível fileutilitário combinado com o poder do find, isso pode ser útil:

$ # 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: $@"' -- @@

Resultado:

file is ASCII: ./text.txt

Legenda: $é o prompt de shell interativo onde inserimos nossos comandos

Você pode modificar a parte depois &&de chamar algum outro script ou fazer outras coisas inline também, ou seja, se aquele arquivo contém uma determinada string, procure o arquivo inteiro ou procure por uma string secundária nele.

Explicação:

  • find itens que são arquivos
  • Faça xargsalimentar cada item como uma linha em um bash comando / script do liner
  • fileverifica o tipo de arquivo por byte mágico, grepverifica se ASCII existe, em caso afirmativo, após &&a execução do próximo comando.
  • findimprime os resultados nullseparados, isso é bom para evitar nomes de arquivos com espaços e metacaracteres.
  • xargs, usando a -0opção, lê-os nullseparadamente, -I @@ pega cada registro e usa como parâmetro / args posicional para o script bash.
  • --pois bashgarante que tudo o que vier depois é um argumento mesmo que comece com -like, o -cque poderia ser interpretado como uma opção bash

Se você precisar encontrar tipos diferentes de ASCII, simplesmente substitua grep ASCIIpor outro tipo, comogrep "PDF document, version 1.4"


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

Use o comando find para listar todos os arquivos, use o comando file para verificar se são texto (não tar, chave), finalmente use o comando awk para filtrar e imprimir o resultado.


-4

Que tal agora

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

Isto não procura"needle text"
peoro

@Navi: o exemplo de OP fornecido encontra apenas arquivos contendo"needl text"
peoro

3
@Navi: agora não procura mais por arquivos de texto: se um arquivo binário contiver "needle text"seria encontrado
peoro

Por que estou te ouvindo?
Navi

1
@Navi: seu one-liner não verifica os tipos de arquivo e também tem grandes problemas com espaços em branco em nomes de arquivos ...
thkala
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.