Gibt es eine bequeme Möglichkeit, Dateien als "binär" oder "Text" zu klassifizieren?

35

Standard-Unix-Dienstprogramme mögen grepund diffverwenden eine Heuristik, um Dateien als "Text" oder "Binär" zu klassifizieren. (Eg grep's Ausgabe kann Zeilen wie enthalten Binary file frobozz matches.)

Gibt es einen bequemen Test, den man in einem zshSkript anwenden kann , um eine ähnliche "Text / Binär" -Klassifizierung durchzuführen? (Anders als so etwas grep '' somefile | grep -q Binary.)

(Mir ist klar, dass ein solcher Test notwendigerweise heuristisch und daher unvollkommen wäre.)

kjo
quelle
10
fileist ein Standard-Dienstprogramm und kann die Dateimagie nach besten Kräften durchlaufen, um Dateitypen zu bestimmen. Es kann die meisten Textformate erkennen und leistet bei Binärformaten ziemlich gute Arbeit. Wenn Sie nur herausfinden
möchten
@Bratchley: Einige Versionen von filewerden gedruckt, z. B. shell scriptfür einige Dateien, die ich als "Text" klassifizieren möchte. Gibt es eine Möglichkeit, fileeinfach zu drucken textoder binary?
kjo
1
@don_crissti Bei dieser Frage geht es um jemanden, der versucht, Leute dazu zu bringen, sein Bash-Skript zu debuggen. Das Erkennen von Text ist genau das, was das Skript tun soll. Sie hatten schließlich ein Problem mit einem ihrer cutBefehle.
Bratchley
1
@don_crissti Die Tatsache, dass es eine Antwort auf Frage A gibt, die für Frage B funktioniert, macht A nicht immer zu einem Duplikat von B. Betrachten Sie jemanden, der nach einer Möglichkeit sucht, Dateien als Text oder Binärdateien zu klassifizieren. Was ist nützlicher: eine Frage zum Debuggen meines Skripts, in der eine allgemeine Antwort unter anderen Antworten vergraben ist, die für dieses Skript spezifisch sind, oder eine allgemeine Frage zum Klassifizieren von Feldern als Text oder Binärdatei?
Gilles 'SO- hör auf böse zu sein'
1
@ Gilles - hängt davon ab, wie Sie es lesen. Ich sehe die Frage dort tatsächlich als typischen Fall eines XY-Problems: OP dort möchte prüfen, ob es sich bei einer Datei um eine Textdatei handelt - und denkt, dass die Weiterleitung der fileAusgabe zur cutLösung ist - sicher, dass es einen fehlenden Platz gibt, der zum Fehlschlagen führt, und das hat dazu geführt Die meisten Leute dort sprechen das Y anstelle des X an, aber Stéphanes Kommentare und Antworten zeigen die richtige Art und Weise, um festzustellen, ob es sich bei der Datei um Text handelt oder nicht.
don_crissti

Antworten:

27

Wenn Sie fragen , fileum nur den MIME-Typ Sie viele verschiedene, wie zu bekommen text/x-shellscript, und application/x-executableetc, aber ich glaube , wenn Sie nur für den „Text“ Teil überprüfen sollten Sie gute Ergebnisse erzielen. ZB ( -bfür keinen Dateinamen in der Ausgabe):

file -b --mime-type filename | sed 's|/.*||'
meuh
quelle
24
Denken Sie daran, je nach file, dass Sie vielleicht einige Textformate verpassen: application/xml(und ähnlich wie RSS) application/ecmascript, application/json, image/svg+xml, ... Sie müssten die weiße Liste diejenigen.
Boldewyn
@Boldewyn wow, schöne Beispiele! Eine wahrscheinlich bessere Antwort ist es, nur jede Datei zu akzeptieren, die nur druckbare Zeichen enthält, aber irgendwie auch mit utf-8 und ähnlichen Codierungsproblemen fertig zu werden.
Meuh
Ja, das ist der Kern meiner Antwort unten. Das einzige Problem ist, dass diese Lösung die gesamte Datei betrachten muss ...
Boldewyn
7
@Boldewyn Im Prinzip sind application/*Typen nicht für den menschlichen Verzehr bestimmt, auch wenn sie möglicherweise textbasiert sind, um die Entwicklung und das Debuggen zu erleichtern. Deshalb gibt es sowohl ein text/xmlals auch ein application/xml. Die Frage, ob sie als Text betrachtet werden sollen, hängt also von den Bedürfnissen des OP ab.
Tobia
3
Odercut -d/ -f1
Stéphane Chazelas
20

Ein anderer Ansatz wäre die Verwendung isutf8aus der moreutils- Sammlung.

Es wird mit 0 beendet, wenn die Datei UTF-8 oder ASCII gültig ist, oder es werden Kurzschlüsse ausgegeben, eine Fehlermeldung ausgegeben (Stille mit -q) und ansonsten mit 1 beendet.

Wandern Nauta
quelle
5
Netter Vorschlag. Mir ist gerade aufgefallen, dass ein Verzeichnis mit arg den Wert 0 hat. Ich hätte mindestens 1 vorgezogen. Aber dann Müll rein, Müll raus.
Meuh
13

Wenn Ihnen die von GNU verwendete Heuristik gefällt grep, können Sie sie verwenden:

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

Es sucht nach NUL-Bytes im ersten Puffer, der aus der Datei gelesen wird (einige Kilobytes für eine reguläre Datei, aber viel weniger für eine Pipe oder einen Socket oder ähnliche Geräte /dev/random). In UTF-8-Ländereinstellungen werden auch Byte-Sequenzen markiert, die keine gültigen UTF-8-Zeichen bilden. Es wird davon LC_ALLausgegangen, dass die Sprache nicht Englisch ist.

Mit dem ${1-$REPLY}Formular können Sie es als zshGlob-Qualifikationsmerkmal verwenden:

ls -ld -- *(.+isbinary)

würde die Binärdateien auflisten.

Stéphane Chazelas
quelle
7

Sie können versuchen, festzustellen, ob iconvdie Datei gelesen werden kann. Dies ist weniger performant als file(das liest nur ein paar Bytes von Anfang an), liefert aber zuverlässigere Ergebnisse:

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

Dies ist im iconvGrunde genommen ein No-Op, aber wenn es auf ungültige Daten stößt (ungültiges UTF-8 in diesem Beispiel), wird es gesperrt und beendet.

Boldewyn
quelle
4
Die Verwendung von -fund -tanstelle der GNU long-Optionen würde es portabler machen. Beachten Sie, dass es die Dateien "binär" nennt, die es nicht öffnen kann. Leere Dateien werden als "Text" bezeichnet.
Stéphane Chazelas
Einverstanden. Ich habe die langen Formulare für Ad-hoc-Dokumentationen verwendet, für Leute, die es nicht wissen iconv. Aber -fund -tsind meistens besser.
Boldewyn
7

Sie können ein Skript schreiben, das aufruft file, und mithilfe einer case-Anweisung prüfen, an welchen Fällen Sie interessiert sind.

Beispielsweise

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

Natürlich kann es viele Sonderfälle geben, die von Interesse sind. Wenn ich nur stringseine Kopie von ansehe libmagic, sehe ich ungefähr 200 Fälle, zB

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

Einige verwenden die Zeichenfolge "Text" als Teil eines anderen Typs, z.

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

scriptkönnte ebenfalls Teil eines Wortes sein, aber ich sehe in diesem Fall keine Probleme. Ein Skript sollte jedoch nach "text"einem Wort suchen , nicht nach einer Teilzeichenfolge .

Zur Erinnerung, die fileAusgabe verwendet keine genaue Beschreibung, die immer "Skript" oder "Text" enthalten würde. Sonderfälle sind zu berücksichtigen. Ein Follow-up kommentierte, dass der --mime-typeAnsatz zwar nicht funktioniert, für .svgDateien. In einem Test sehe ich jedoch die folgenden Ergebnisse für svg-Dateien:

$ 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

Die ich nach dem Betrachten von tausend Dateien ausgewählt habe, zeigen nur 6 mit "Text" in der MIME-Typ-Ausgabe. Argumentieren, am Ende der MIME-Typ Ausgabe nützlicher sein könnte , die „xml“ passend, sagt, als matching „SVG“, aber mit einem Skript zu tun , dass bringen Sie zurück auf den Vorschlag hier gemacht.

Die Ausgabe von fileerfordert einige Anpassungen in beiden Szenarien und ist nicht 100% zuverlässig (es wird von mehreren meiner Perl-Skripten verwirrt und nennt sie "Daten").

Es gibt mehr als eine Implementierung von file. Das am häufigsten verwendete erledigt seine Arbeit in libmagic, das von verschiedenen Programmen aus verwendet werden kann (vielleicht nicht direkt von zsh, obwohl pythonkann).

Gemäß der Vergleichstabelle für Dateitests für Shell, Perl, Ruby und Python verfügt Perl über eine -TOption, mit der diese Informationen bereitgestellt werden können. Es listet aber keine vergleichbare Funktion für zsh.

Weitere Lektüre:

Thomas Dickey
quelle
Leider enthält die fileAusgabe von GNU für svg-Dateien: den SVG Scalable Vector Graphics imageWorttext nicht. Ich dachte, dieser Ansatz wäre besser als die akzeptierte Antwort, den MIME-Typ zu überprüfen, aber es fehlen immer noch einige Typen.
Peter Cordes
Es fehlt immer noch, mit dem Mimetyp; für xterms svg datei bekomme ich image/svg+xml. Eigentlich - habe gerade eine 1000-Datei gleich überprüft, nur 6 kamen als "Text" laut MIME-Typ alleine heraus. Ich halte mich an ein Skript, das zumindest nach Bedarf zum Laufen gebracht werden kann.
Thomas Dickey
3

filehat eine Option --mime-encoding, die versucht, die Codierung einer Datei zu erkennen.

 $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

Sie können file --mime-encoding | grep binaryfeststellen, ob es sich bei einer Datei um eine Binärdatei handelt. Es funktioniert zuverlässig, obwohl es durch ein einzelnes ungültiges Zeichen in einer langen Textdatei verwirrt werden kann.

Ich habe beispielsweise einen Alias catfür das folgende Shell-Skript, um zu vermeiden, dass mein Terminal versehentlich durch Öffnen einer Binärdatei zerstört wird:

#! /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
quelle
3

Kategorien sind beliebig. Bevor Sie eine Klassifizierung vornehmen, benötigen Sie eine (strenge) Definition. Um eine Definition zu haben, brauchen Sie einen Zweck .

Also, was willst du mit dieser Klassifizierung machen?

  • Wenn Sie unter FTP ASCII / Binary auswählen möchten, ist es wichtig, dass Sie eine Binärdatei nicht als ASCII übertragen (sonst wird sie beschädigt). Sie sollten also testen, ob es sich bei der Datei um reine Texte, HTML, RTF und einige andere handelt. Im Zweifelsfall wählen Sie binär. Und vielleicht möchten Sie auch testen, ob die Datei nur eine Teilmenge wie 0x0A, 0x0D und 0x20-0x7F enthält.
  • Wenn Sie die Datei in einem Protokoll (POP3, SMTP) übertragen möchten, müssen Sie testen, ob die Codierung in base64 oder einfach nur normal erfolgt. In diesem Fall sollten Sie testen, ob nicht unterstützte Zeichen vorhanden sind.
  • Jeder andere Fall ... kann eine andere Definition haben.
ESL
quelle
3
perl -e'chomp(my$f=<>);print "binary$/" if -B $f;print "text$/" if -T _'

werde es tun. Siehe Dokumentation für -Bund-T (suchen Sie auf dieser Seite nach der Zeichenfolge The -T and -B switches work as follows).

msh210
quelle
perl -le 'print -B $ARGV[0] ? "binary" : "text"' --könnte klarer sein. Oder sogarperl -le 'print -B $_ ? "binary" : "text", @ARGV > 1 ? "\t$_" : "" for @ARGV' --
jrw32982 unterstützt Monica am
1

Ich habe zu https://github.com/audreyr/binaryornot beigetragen. Es gibt (noch) keinen Befehlszeilen-Wrapper, aber dies ist eine einfache Python-Bibliothek, die auch von der CLI aus leicht aufzurufen ist. Es wird eine ziemlich effiziente Heuristik verwendet, um festzustellen, ob es sich bei einer Datei um Text oder um eine Binärdatei handelt.

Philippe Ombredanne
quelle
1

Ich habe jetzt diese Antwort ein bisschen alt, aber ich denke, mein Freund hat mir einen tollen "Hack" beigebracht, um dies zu tun.

Sie verwenden den diffBefehl und überprüfen Ihre Datei anhand einer Testtextdatei:

$ diff filetocheck testfile.txt

Wenn filetocheckes sich nun um eine Binärdatei handelt, wäre die Ausgabe:

Binary files filetocheck and testfile.txt differ

Auf diese Weise können Sie den diffBefehl nutzen und z. B. eine Funktion schreiben, die das Einchecken eines Skripts vornimmt.

user3019105
quelle