Polecenie Linuksa: jak „znaleźć” tylko pliki tekstowe?

100

Po kilku wyszukiwaniach w Google wymyśliłem:

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

co jest bardzo nieporęczne i generuje niepotrzebne teksty, takie jak informacje typu MIME. Jakieś lepsze rozwiązania? Mam wiele obrazów i innych plików binarnych w tym samym folderze z dużą ilością plików tekstowych, które muszę przeszukać.

datasn.io
źródło

Odpowiedzi:

184

Wiem, że to stary wątek, ale natknąłem się na niego i pomyślałem, że udostępnię moją metodę, która okazała się bardzo szybkim sposobem findznajdowania tylko plików niebinarnych:

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

-IOpcja grep mówi to natychmiast ignorować pliki binarne i .opcji wraz z -quczyni go natychmiast dopasować pliki tekstowe tak to idzie bardzo szybko. Jeśli martwisz się o spacje, możesz zmienić na -printa, -print0aby rurować w xargs -0coś lub coś takiego (dzięki za wskazówkę, @ lucas.werkmeister!)

Również pierwsza kropka jest konieczna tylko dla niektórych wersji BSD, findtakich jak na OS X, ale nic nie szkodzi, po prostu posiadanie jej przez cały czas, jeśli chcesz umieścić to w aliasie lub coś w tym rodzaju.

EDYCJA : Jak poprawnie zauważył @ruslan, -andmożna pominąć, ponieważ jest to zasugerowane.

crudcore
źródło
16
W systemie Mac OS X muszę to zmienić na find . -type f -exec grep -Il "" {} \;.
Alec Jacobson,
3
To jest lepsze niż odpowiedź peoro, ponieważ 1. faktycznie odpowiada na pytanie 2. Nie daje fałszywych trafień 3. Jest o wiele bardziej wydajne
użytkownik123444555621
3
Możesz także użyć, find -type f -exec grep -Iq . {} \; -and -printktóry ma tę zaletę, że przechowuje pliki find; możesz zastąpić -printinnym, -execktóry jest uruchamiany tylko dla plików tekstowych. (Jeśli pozwolisz grepwydrukować nazwy plików, nie będziesz w stanie rozróżnić nazw plików zawierających nowe linie.)
Lucas Werkmeister
1
@ NathanS.Watson-Haigh Nie powinno, ponieważ powinno natychmiast dopasowywać pliki tekstowe. Czy masz konkretny przypadek użycia, którym możesz się podzielić?
crudcore
2
find . -type f -exec grep -Il . {} +jest znacznie szybszy. Wadą jest to, że nie można go przedłużyć o inny, -execjak sugerował @ lucas.werkmeister
Henning
11

Na podstawie tego pytania SO :

grep -rIl "needle text" my_folder

crayzeewulf
źródło
Dzięki, -Ito ratuje życie.
Dominique
10

Dlaczego jest to nieporęczne? Jeśli potrzebujesz go często i nie chcesz wpisywać go za każdym razem, po prostu zdefiniuj dla niego funkcję bash:

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

włóż go do swojego .bashrci po prostu uruchom:

findTextInAsciiFiles your_folder "needle text"

kiedykolwiek chcesz.


EDYTUJ, aby odzwierciedlić edycję OP:

jeśli chcesz wyciąć informacje mime, możesz po prostu dodać kolejny etap do potoku, który odfiltrowuje informacje mime. To powinno załatwić sprawę, biorąc tylko to, co jest przed :: 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
}
peoro
źródło
Nie jestem pewien, czy „tekst grep” jest wystarczająco dokładny, aby uzyskać dokładnie wszystkie pliki tekstowe - to znaczy, czy są jakieś typy plików tekstowych, które nie mają „tekstu” w ciągu opisu typu MIME?
datasn.io
@ kavoir.com: tak. Z filepodręcznika: „Użytkownicy polegają na tym, że wiedzą, że wszystkie czytelne pliki w katalogu mają wydrukowane słowo 'tekst'."
peoro,
2
Czy nie byłoby nieco sprytniej szukać plików tekstowych przed grepowaniem, zamiast grepowania, a następnie odfiltrowywania plików tekstowych?
użytkownik nieznany
/proc/meminfo, /proc/cpuinfoItd. Są to pliki tekstowe, ale file /proc/meminfomówi /proc/meminfo: empty. Zastanawiam się, czy „pusty” powinien być testowany oprócz „tekstu”, ale nie jestem pewien, czy inne typy mogą również zgłaszać „puste”.
Timo Kähkönen
"Dlaczego to jest nieporęczne?" - „wyświetla niepotrzebne teksty”. Ta odpowiedź tego nie satysfakcjonuje.
user123444555621
4
find . -type f -print0 | xargs -0 file | grep -P text | cut -d: -f1 | xargs grep -Pil "search"

Niestety nie jest to oszczędność miejsca. Umieszczenie tego w skrypcie bash sprawia, że ​​jest to trochę łatwiejsze.

To jest bezpieczne dla przestrzeni:

#!/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" "%"
Antti Rytsölä
źródło
2
W skrypcie występuje kilka problemów: 1. co się stanie, jeśli plik binarny zostanie nazwany text.bin? 2. Co się stanie, jeśli nazwa pliku zawiera :?
thkala
3

Inny sposób na zrobienie tego:

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

Jeśli chcesz również puste pliki:

#  find . |xargs file {} \; |egrep "ASCII text|empty"
IT Guy
źródło
2

Co powiesz na to:

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

Jeśli chcesz, aby nazwy plików nie zawierały typów plików, po prostu dodaj ostatni sedfiltr.

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

Możesz odfiltrować niepotrzebne typy plików, dodając więcej -e 'type'opcji do ostatniego greppolecenia.

EDYTOWAĆ:

Jeśli twoja xargswersja obsługuje tę -dopcję, powyższe polecenia stają się prostsze:

$ grep -rl "needle text" my_folder | xargs -d '\n' -r file | grep -e ':[^:]*text[^:]*$' | grep -v -e 'executable' | sed 's|:[^:]*$||'
thkala
źródło
głupi ja. Nie zauważyłem rekurencyjnego grep. jak rozumiem, jest dość szybki, chociaż w wielu aplikacjach jest nieco ograniczony. +1 dla Ciebie.
Antti Rytsölä
2

Oto, jak to zrobiłem ...

1. zrób mały skrypt do sprawdzenia, czy plik jest zwykłym tekstem istext:

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

2. użyj find jak poprzednio

find . -type f -exec istext {} \; -exec grep -nHi mystring {} \;
Robert
źródło
Chyba masz na myśli == *"text"* ]]?
użytkownik nieznany
Zamiast tego możesz użyć operatora dopasowania `= ~" tekst "]]`.
użytkownik nieznany
2

Mam dwa problemy z odpowiedzią na histumność:

  • Zawiera tylko pliki tekstowe. W rzeczywistości nie przeszukuje ich zgodnie z żądaniem. Aby faktycznie wyszukiwać, użyj

    find . -type f -exec grep -Iq . {} \; -and -print0 | xargs -0 grep "needle text"
    
  • Tworzy proces grep dla każdego pliku, który jest bardzo wolny. Wtedy jest lepsze rozwiązanie

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

    lub po prostu

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

    Zajmuje to tylko 0,2 sekundy w porównaniu do 4 sekund w przypadku powyższego rozwiązania (2,5 GB danych / 7700 plików), czyli 20 razy szybciej .

Ponadto nikt nie wymienił ag, Silver Searcher lub ACK-GREP jako alternatyw. Jeśli jeden z nich jest dostępny, są znacznie lepszymi alternatywami:

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

Na koniec uważaj na fałszywe alarmy (pliki binarne traktowane jako pliki tekstowe). Miałem już fałszywy alarm przy użyciu grep / ag / ACK, więc lepiej najpierw wymień pasujące pliki przed ich edycją.

fuujuhi
źródło
1

Chociaż jest to stare pytanie, myślę, że poniższe informacje dodadzą jakości odpowiedzi tutaj.

Ignorując pliki z ustawionym bitem wykonywalnym , po prostu używam tego polecenia:

find . ! -perm -111

Aby zapobiec rekurencyjnemu wchodzeniu do innych katalogów:

find . -maxdepth 1 ! -perm -111

Nie potrzeba potoków do mieszania wielu poleceń, wystarczy potężne polecenie zwykłego wyszukiwania .

  • Zastrzeżenie: nie jest to dokładnie to , o co prosił OP, ponieważ nie sprawdza, czy plik jest binarny, czy nie. Na przykład odfiltruje pliki skryptów bash , które same są tekstem , ale mają ustawiony bit wykonywalny .

To powiedziawszy, mam nadzieję, że jest to przydatne dla każdego.

Dr Beco
źródło
0

Robię to w ten sposób: 1) ponieważ jest zbyt wiele plików (~ 30k) do przeszukiwania, codziennie generuję listę plików tekstowych do użytku przez crontab za pomocą poniższego polecenia:

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

2) utwórz funkcję w .bashrc:

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

Następnie mogę użyć poniższego polecenia, aby przeprowadzić wyszukiwanie:

findex "needle text"

HTH :)

Frank Fang
źródło
0

Wolę xargi

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

jeśli twoje nazwy plików są dziwne, poszukaj opcji -0:

find . -type f -print0 | xargs -0 grep -I "needle text"
dalore
źródło
0
  • przykład basha, aby wyszukać tekst „eth0” w / etc we wszystkich plikach text / ascii

grep eth0 $ (znajdź / etc / -type f -exec plik {} \; | egrep -i "tekst | ascii" | cut -d ':' -f1)

Gabriel G.
źródło
0

Oto uproszczona wersja z rozszerzonym wyjaśnieniem dla początkujących, takich jak ja, którzy próbują nauczyć się umieszczać więcej niż jedno polecenie w jednej linii.

Gdybyś miał opisać problem w krokach, wyglądałoby to tak:

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

Aby to osiągnąć, możemy użyć trzech poleceń UNIX: find, file, i grep.

find sprawdzi każdy plik w katalogu.

filepoda nam typ pliku. W naszym przypadku szukamy zwrotu „tekstu ASCII”

grep będzie szukać słowa kluczowego „ASCII” w danych wyjściowych z file

Jak więc możemy połączyć je w jedną linię? Jest na to wiele sposobów, ale uważam, że robienie tego w kolejności naszego pseudokodu ma największy sens (szczególnie dla początkującego, takiego jak ja).

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

Wygląda na skomplikowane, ale nieźle, kiedy to rozbijemy:

find ./= przejrzyj każdy plik w tym katalogu. W findkomenda odchodzący nazwa pliku z dowolnego pliku, który pasuje do „wyrażenia” lub cokolwiek przyjdzie po ścieżce, która w naszym przypadku jest bieżący katalog lub./

Najważniejszą rzeczą do zrozumienia jest to, że wszystko po tym pierwszym bicie zostanie ocenione jako Prawda lub Fałsz. Jeśli prawda, nazwa pliku zostanie wydrukowana. Jeśli nie, to polecenie przechodzi dalej.

-exec= ta flaga jest opcją w poleceniu find, która pozwala nam użyć wyniku innego polecenia jako wyrażenia wyszukiwania. To jak wywołanie funkcji w funkcji.

file {}= polecenie wywoływane wewnątrz find. filePolecenie zwraca ciąg znaków, który powie Ci filetype pliku. Regularnie, to będzie wyglądać następująco: file mytextfile.txt. W naszym przypadku chcemy, aby używał dowolnego pliku przeglądanego przez findpolecenie, więc wstawiamy nawiasy klamrowe, {}aby działały jako pusta zmienna lub parametr. Innymi słowy, po prostu prosimy system o wypisanie ciągu dla każdego pliku w katalogu.

";"= jest to wymagane przez findi jest znakiem interpunkcyjnym na końcu naszego -execpolecenia. Jeśli potrzebujesz więcej wyjaśnień, skorzystaj z instrukcji „znajdź” man find.

| grep 'ASCII'= |jest rurą. Potok pobiera dane wyjściowe z tego, co jest po lewej stronie i używa ich jako danych wejściowych dla tego, co jest po prawej stronie. Pobiera dane wyjściowe findpolecenia (ciąg, który jest typem pliku pojedynczego pliku) i testuje je, aby sprawdzić, czy zawiera ciąg 'ASCII'. Jeśli tak, zwraca prawdę.

TERAZ, wyrażenie po prawej find ./stronie zwróci wartość true, gdy greppolecenie zwróci wartość true. Voila.

mepler
źródło
0

Jeśli chcesz znaleźć dowolny typ pliku według ich magicznych bajtów, używając niesamowitego filenarzędzia połączonego z mocą find, może się to przydać:

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

Wynik:

file is ASCII: ./text.txt

Legenda: $to interaktywna zachęta powłoki, w której wpisujemy nasze polecenia

Możesz zmodyfikować część po, &&aby wywołać inny skrypt lub wykonać inne czynności w tekście, np. Jeśli ten plik zawiera podany ciąg, wpisz cały plik lub poszukaj w nim dodatkowego ciągu.

Wyjaśnienie:

  • find elementy, które są plikami
  • Utwórz xargskanał każdego elementu jako wiersz w jednym bash poleceniu / skrypcie liniowym
  • filesprawdza typ pliku po magicznym bajcie, grepsprawdza, czy istnieje ASCII, jeśli tak, to po &&wykonaniu następnego polecenia.
  • findwypisuje wyniki nulloddzielone, dobrze jest zmienić nazwy plików ze spacjami i metaznakami.
  • xargs, używając -0opcji, czyta je nulloddzielnie, -I @@ bierze każdy rekord i używa jako parametru pozycyjnego / argumentów do skryptu bash.
  • --for bashzapewnia, że ​​wszystko, co następuje po nim, jest argumentem, nawet jeśli zaczyna się od znaku -like, -cktóry w przeciwnym razie mógłby zostać zinterpretowany jako opcja bash

Jeśli chcesz znaleźć typy inne niż ASCII, po prostu zastąp grep ASCIIje innym typem, na przykładgrep "PDF document, version 1.4"

sdkks
źródło
-1
find . -type f | xargs file | grep "ASCII text" | awk -F: '{print $1}'

Użyj polecenia find, aby wyświetlić listę wszystkich plików, użyj polecenia pliku, aby sprawdzić, czy są tekstem (nie tar, klucz), na koniec użyj polecenia awk, aby przefiltrować i wydrukować wynik.

Roy Zeng
źródło
-4

Co powiesz na to

 find . -type f|xargs grep "needle text"
Navi
źródło
To nie wygląda"needle text"
peoro
@Navi: podany przykład OP wyszukuje tylko pliki zawierające"needl text"
peoro
3
@Navi: teraz nie szuka już plików tekstowych: gdyby plik binarny zawierał "needle text", zostałby znaleziony
peoro
Dlaczego w ogóle cię słucham?
Navi
1
@Navi: twoja jedna linijka nie sprawdza typów plików, a także ma poważne problemy z białymi
znakami