Czy istnieje wygodny sposób klasyfikowania plików jako „binarne” lub „tekstowe”?

35

Standardowe narzędzia Unix, jak grepi diffkorzystać z niektórych heurystyki do klasyfikowania plików jako „tekst” lub „binarny”. (Na przykład grepdane wyjściowe mogą zawierać wiersze podobne Binary file frobozz matches.)

Czy istnieje wygodny test, który można zastosować w zshskrypcie, aby przeprowadzić podobną klasyfikację „tekstową / binarną”? (Inne niż coś takiego grep '' somefile | grep -q Binary.)

(Zdaję sobie sprawę, że każdy taki test byłby koniecznie heurystyczny, a zatem niedoskonały).

kjo
źródło
10
filejest standardowym narzędziem i może uruchamiać magię plików w celu określania typów plików zgodnie z jego najlepszymi możliwościami. Potrafi rozpoznać większość formatów tekstowych i robi całkiem przyzwoitą robotę na formatach binarnych. Jeśli wszystko, co próbujesz zrobić, to dowiedzieć się, czy plik jest tekstowy, czy nie,
oto
@Bratchley: niektóre wersje filezostaną wydrukowane, np. shell scriptDla niektórych plików chciałbym sklasyfikować jako „tekst”. Czy istnieje sposób, fileaby wydrukować tylko textlub binary?
kjo
1
@don_crissti To pytanie dotyczy kogoś, kto próbuje nakłonić ludzi do debugowania jego skryptu bash. Wykrywanie tekstu jest dokładnie tym, co powinien zrobić skrypt. W końcu mieli problem z jednym ze swoich cutpoleceń.
Bratchley,
1
@don_crissti Fakt, że odpowiedź na pytanie A działa na pytanie B, nie zawsze sprawia, że ​​A jest duplikatem B. Rozważ kogoś, kto szuka sposobu na klasyfikację plików jako tekstowe lub binarne. Co jest bardziej przydatne: pytanie „debuguj mój skrypt”, które zawiera ukrytą odpowiedź wśród innych odpowiedzi specyficznych dla tego skryptu, lub ogólne „jak zaklasyfikować pliki jako tekstowe lub binarne?”?
Gilles „SO- przestań być zły”
1
@Gilles - zależy od tego, jak to czytasz. Widzę tam pytanie jako typowy przypadek problemu XY: OP chce sprawdzić, czy plik jest plikiem tekstowym - i uważa, że rozwiązaniem jest fileprzesyłanie potokowe cut- jasne, że brakuje miejsca, które powoduje awarię i które spowodowało większość ludzi zwraca się do litery Y zamiast litery X, ale komentarze i odpowiedzi Stéphane pokazują właściwy sposób ustalenia, czy plik jest tekstowy, czy nie.
don_crissti 10.04.16

Odpowiedzi:

27

Jeśli poprosisz fileo tylko typu MIME dostaniesz wiele różnych te, jak text/x-shellscripti application/x-executableetc, ale mogę sobie wyobrazić, jeśli po prostu sprawdzić na „tekst” części należy uzyskać dobre wyniki. Np. (W -bprzypadku braku pliku wyjściowego):

file -b --mime-type filename | sed 's|/.*||'
meuh
źródło
24
Wystarczy pamiętać, w zależności od file, które można pominąć niektóre formaty tekstowe: application/xml(i podobnie jak RSS), application/ecmascript, application/json, image/svg+xml, ... Można by mieć do białej listy tych.
Boldewyn
@Boldewyn wow, ładne przykłady! Prawdopodobnie lepszą odpowiedzią jest po prostu zaakceptowanie dowolnego pliku, który ma tylko znaki drukowalne, ale jakoś poradzi sobie z utf-8 i podobnymi problemami z kodowaniem.
Meuh
Tak, to jest sedno mojej odpowiedzi poniżej. Jedyny problem polega na tym, że to rozwiązanie musi przejrzeć cały plik ...
Boldewyn,
7
@Boldewyn Zasadniczo application/*typy nie są przeznaczone do spożycia przez ludzi, nawet jeśli mogą być oparte na tekście w celu ułatwienia programowania i debugowania. Dlatego istnieje zarówno a, jak text/xmli an application/xml. Zatem pytanie, czy uznać je za tekst, zależy od potrzeb PO.
Tobia,
3
Lubcut -d/ -f1
Stéphane Chazelas,
20

Innym podejściem byłoby użycie isutf8z kolekcji moreutils .

Wychodzi z 0, jeśli plik jest poprawny UTF-8 lub ASCII, lub zwarciami, drukuje komunikat o błędzie (cisza z -q) i kończy z 1 w przeciwnym razie.

Wander Nauta
źródło
5
Niezła sugestia. Właśnie zauważyłem, że podanie katalogu jako arg powoduje, że zwraca on 0. Wolałbym przynajmniej 1. Ale potem, śmieci, śmieci.
Meuh
13

Jeśli podoba ci się heurystyka używana przez GNU grep, możesz jej użyć:

isbinary() {
  LC_MESSAGES=C grep -Hm1 '^' < "${1-$REPLY}" | grep -q '^Binary'
}

To wyszukuje NUL bajtów pierwszy bufor odczytu z pliku (kilka kilo bajtów dla zwykłego pliku, ale może być o wiele mniej do rury lub gniazda lub niektórych urządzeń takich jak /dev/random). W ustawieniach regionalnych UTF-8 oznacza to również sekwencje bajtów, które nie tworzą prawidłowych znaków UTF-8. Zakłada LC_ALLsię, że nie jest ustawiony na coś, co nie jest językiem angielskim.

${1-$REPLY}Forma pozwala na użycie go jako zshkwalifikator glob:

ls -ld -- *(.+isbinary)

wyświetli listę plików binarnych .

Stéphane Chazelas
źródło
7

Możesz spróbować ustalić, czy iconvmożna odczytać plik. Jest to mniej wydajne niż file(co odczytuje tylko kilka bajtów od początku), ale daje bardziej wiarygodne wyniki:

ENCODING=utf-8
if iconv --from-code="$ENCODING" --to-code="$ENCODING" your_file.ext > /dev/null 2>&1; then
    echo text
else
    echo binary
fi

iconvZasadniczo czyni to brak operacji, ale jeśli napotka niepoprawne dane (niepoprawne UTF-8 w tym przykładzie), zablokuje się i zakończy działanie.

Boldewyn
źródło
4
Używanie -fi -tzamiast długich opcji GNU uczyniłoby go bardziej przenośnym. Pamiętaj, że wywoła „binarne” pliki, których nie może otworzyć. Nazwie puste pliki „tekstem”.
Stéphane Chazelas,
Zgoda. Użyłem długich formularzy do dokumentacji ad hoc dla osób, które nie wiedzą iconv. Ale -fi -tzwykle są lepsze.
Boldewyn
7

Możesz napisać skrypt, który wywołuje file, i użyć instrukcji case, aby sprawdzić przypadki, które Cię interesują.

Na przykład

#!/bin/sh
case $(file "$1") in
(*script*|*\ text|*\ text\ *)
    echo text
    ;;
(*)
    echo binary
    ;;
esac

choć oczywiście może być wiele specjalnych przypadków, które są interesujące. Po sprawdzeniu stringskopii libmagicwidzę około 200 przypadków, np.

Konqueror cookie text
Korn shell script text executable
LaTeX 2e document text
LaTeX document text
Linux Software Map entry text
Linux Software Map entry text (new format)
Linux kernel symbol map text
Lisp/Scheme program text
Lua script text executable
LyX document text
M3U playlist text
M4 macro processor script text

Niektórzy używają ciągu „tekst” jako części innego typu, np.

SoftQuad troff Context intermediate   
SoftQuad troff Context intermediate for AT&T 495 laser printer
SoftQuad troff Context intermediate for HP LaserJet

podobnie scriptmoże być częścią słowa, ale w tym przypadku nie widzę problemów. Ale skrypt powinien sprawdzać "text"jako słowo , a nie podciąg .

Dla przypomnienia, filewyjście nie używa dokładnego opisu, który zawsze zawierałby „skrypt” lub „tekst”. Szczególne przypadki są czymś do rozważenia. Kontynuacja skomentowała, że --mime-typedziała, podczas gdy to podejście nie, w przypadku .svgplików. Jednak w teście widzę te wyniki dla plików svg:

$ ls -l *.svg
-r--r--r-- 1 tom users  6679 Jul 26  2012 pumpkin_48x48.svg
-r--r--r-- 1 tom users 17372 Jul 30  2012 sink_48x48.svg
-r--r--r-- 1 tom users  5929 Jul 25  2012 vile_48x48.svg
-r--r--r-- 1 tom users  3553 Jul 28  2012 vile-mini.svg
$ file *.svg
pumpkin_48x48.svg: SVG Scalable Vector Graphics image
sink_48x48.svg:    SVG Scalable Vector Graphics image
vile-mini.svg:     SVG Scalable Vector Graphics image
vile_48x48.svg:    SVG Scalable Vector Graphics image
$ file --mime-type *.svg
pumpkin_48x48.svg: image/svg+xml
sink_48x48.svg:    image/svg+xml
vile-mini.svg:     image/svg+xml
vile_48x48.svg:    image/svg+xml

które wybrałem po zobaczeniu tysiąca plików, które pokazują tylko 6 z „tekstem” na wyjściu typu mime. Prawdopodobnie dopasowanie „xml” na końcu danych wyjściowych typu mime może być bardziej użyteczne, powiedzmy, niż dopasowanie „SVG”, ale użycie skryptu, aby to zrobić , spowoduje powrót do podanej tutaj sugestii.

Dane wyjściowe filewymagają dostrajania w obu scenariuszach i nie są w 100% niezawodne (jest mylone przez kilka moich skryptów Perla, nazywając je „danymi”).

Istnieje więcej niż jedna implementacja file. Ten najczęściej używany działa w libmagic, który może być używany z różnych programów (być może nie bezpośrednio z zsh, choć pythonmoże).

Zgodnie z tabelą porównawczą testu plików dla powłok, Perla, Ruby i Pythona , Perl ma -Topcję, za pomocą której może dostarczyć te informacje. Ale nie zawiera żadnej porównywalnej funkcji zsh.

Dalsza lektura:

Thomas Dickey
źródło
Niestety filedane wyjściowe GNU dla plików svg: SVG Scalable Vector Graphics imagenie zawierają słowa tekst. Myślałem, że takie podejście byłoby lepsze niż zaakceptowana odpowiedź sprawdzania typu MIME, ale nadal nie ma niektórych typów.
Peter Cordes,
Nadal tęskni za typem MIME; dla pliku svg xterm dostaję image/svg+xml. Właściwie - właśnie sprawdziłem 1000 plików tak samo, tylko 6 pojawiło się jako „tekst” według samego typu MIME. Będę trzymać się skryptu, który przynajmniej można sprawić, by działał w razie potrzeby.
Thomas Dickey,
3

filema opcję, --mime-encodingktóra próbuje wykryć kodowanie pliku.

 $file --mime-encoding Documents/poster2.pdf 
Documents/poster2.pdf: binary
 $file --mime-encoding projects/linux/history-torvalds/Makefile 
projects/linux/history-torvalds/Makefile: us-ascii
 $file --mime-encoding graphe.tex 
Dgraphe.tex: us-ascii
 $file --mime-encoding software.tex 
software.tex: utf-8

Możesz użyć file --mime-encoding | grep binarydo wykrycia, czy plik jest plikiem binarnym. Działa niezawodnie, chociaż może zostać pomylony przez pojedynczy nieprawidłowy znak w długim pliku tekstowym.

Na przykład alias catdo następującego skryptu powłoki, aby uniknąć zniszczenia terminala przez nieumyślne otwarcie pliku binarnego:

#! /bin/sh -

[ ! -t 1 ] && exec /bin/cat "$@"
for i
do
    if file --mime-encoding -- "$i" | grep -q binary
    then
        hexdump -C -- "$i"
    else
        /bin/cat -- "$i"
    fi
done
lgeorget
źródło
3

Kategorie są dowolne. Zanim odpowiesz, jak dokonać klasyfikacji, potrzebujesz (ścisłej) definicji. Aby mieć definicję, potrzebujesz celu .

Co chcesz zrobić z tą klasyfikacją?

  • Jeśli chcesz wybrać ascii / binary na FTP, ważne jest, aby nie przesyłać pliku binarnego jako ascii (w przeciwnym razie zostanie uszkodzony). Powinieneś więc sprawdzić, czy plik to zwykły tekst, HTML, RTF i kilka innych. Ale w razie wątpliwości wybierz opcję binarną. A może chcesz również przetestować, czy plik ma tylko taki podzbiór, jak 0x0A, 0x0D i 0x20-0x7F.
  • Jeśli chcesz przenieść plik w jakimś protokole (POP3, SMTP), musisz przetestować, aby wybrać, czy kodować w base64, czy po prostu zwykły. W takim przypadku powinieneś sprawdzić, czy nie ma obsługiwanych znaków.
  • Każdy inny przypadek… może mieć dowolną inną definicję.
ESL
źródło
3
perl -e'chomp(my$f=<>);print "binary$/" if -B $f;print "text$/" if -T _'

zrobię to. Zobacz dokumentację -Bi-T (wyszukaj ciąg na tej stronie The -T and -B switches work as follows).

msh210
źródło
perl -le 'print -B $ARGV[0] ? "binary" : "text"' --może być jaśniejsze. Lub nawetperl -le 'print -B $_ ? "binary" : "text", @ARGV > 1 ? "\t$_" : "" for @ARGV' --
jrw32982 obsługuje Monikę
1

Przyczyniłem się do https://github.com/audreyr/binaryornot Nie ma jeszcze opakowania linii poleceń (ale), ale jest to prosta biblioteka Pythona wystarczająco łatwa do wywołania nawet z poziomu CLI. Używa dość wydajnej heurystyki, aby ustalić, czy plik jest tekstowy czy binarny.

Philippe Ombredanne
źródło
1

Teraz ta odpowiedź jest już trochę stara, ale myślę, że mój przyjaciel nauczył mnie wielkiego „hacka”, aby to zrobić.

Używasz diffpolecenia i porównujesz plik z testowym plikiem tekstowym:

$ diff filetocheck testfile.txt

Teraz, jeśli filetocheckjest to plik binarny, wynikiem byłoby:

Binary files filetocheck and testfile.txt differ

W ten sposób możesz wykorzystać diffpolecenie i np. Napisać funkcję sprawdzającą skrypt.

użytkownik3019105
źródło