Existe-t-il un moyen pratique de classer les fichiers en tant que «binaires» ou «texte»?

35

Les utilitaires Unix standard aiment grepet diffutilisent une heuristique pour classer les fichiers en "texte" ou "binaire". ( grepLa sortie de Eg peut inclure des lignes telles que Binary file frobozz matches.)

Existe-t-il un test pratique que l'on peut appliquer dans un zshscript pour effectuer une classification similaire "texte / binaire"? (Autre que quelque chose comme grep '' somefile | grep -q Binary.)

(Je réalise qu'un tel test serait nécessairement heuristique et donc imparfait.)

kjo
la source
10
fileest un utilitaire standard et peut utiliser la magie de fichier pour déterminer au mieux ses types de fichier. Il peut dire la plupart des formats de texte et fait un travail assez décent sur les formats binaires. Si tout ce que vous essayez, c'est de savoir si un fichier
contient du
@Bratchley: certaines versions de fileseront imprimées, par exemple shell script, pour certains fichiers, je voudrais classer le fichier comme "texte". Existe-t-il un moyen filed’imprimer simplement textou binary?
kjo
1
@don_crissti Cette question concerne quelqu'un qui essaie d'amener les gens à déboguer son script bash. Détecter du texte est exactement ce que le script est censé faire. Ils ont fini par avoir un problème dans l'une de leurs cutcommandes.
Bratchley
1
@don_crissti Le fait qu'il y ait une réponse à la question A qui fonctionne pour la question B ne fait pas toujours de A un doublon de B. Considérez quelqu'un qui cherche un moyen de classer les fichiers en tant que texte ou binaire. Qu'est-ce qui est plus utile: une question de «débogage de mon script» qui contient une réponse générique enterrée parmi d'autres réponses spécifiques à ce script, ou un générique «comment classer les fiels en tant que texte ou binaire?»?
Gilles 'SO- arrête d'être méchant'
1
@ Gilles - dépend de la façon dont vous le lisez. En fait, je vois la question comme un cas typique de problème XY: OP souhaite vérifier si un fichier est un fichier texte - et pense que la filesortie de tuyauterie cutest la solution - bien sûr, il manque un espace qui le fait échouer et qui a rendu la plupart des gens s’adressent au Y au lieu du X mais les commentaires et la réponse de Stéphane montrent la bonne façon de déterminer si le fichier est textuel ou non.
don_crissti

Réponses:

27

Si vous demandez fileuniquement le type mime, vous obtiendrez de nombreux types comme text/x-shellscript, application/x-executableetc., mais j'imagine que si vous vérifiez simplement la partie "texte", vous devriez obtenir de bons résultats. Par exemple ( -bpour aucun nom de fichier dans la sortie):

file -b --mime-type filename | sed 's|/.*||'
meuh
la source
24
Rappelez - vous, selon votre file, que vous risquez de manquer certains formats de texte: application/xml(et similaire comme RSS), application/ecmascript, application/json, image/svg+xml, ... Vous auriez à whitelist ceux -ci .
Boldewyn
@Boldewyn wow, bons exemples! Il est donc probablement préférable d’accepter tout fichier ne contenant que des caractères imprimables, mais aussi de faire face à des problèmes d’encodage similaires à utf-8 et similaires.
Meuh
Oui, c'est l'essentiel de ma réponse ci-dessous. Le seul problème, c'est que cette solution doit examiner l' ensemble du fichier ...
Boldewyn
7
@Boldewyn En principe, les application/*types ne sont pas destinés à la consommation humaine, même s'ils peuvent être basés sur du texte pour faciliter le développement et le débogage. C'est pourquoi il y a à la fois un text/xmlet un application/xml. La question de savoir si les considérer comme un texte dépend des besoins du PO.
Tobia
3
Oucut -d/ -f1
Stéphane Chazelas
20

Une autre approche consisterait à utiliser isutf8la collection moreutils .

Il se ferme avec 0 si le fichier est valide UTF-8 ou ASCII, ou court-circuite, imprime un message d'erreur (silence avec -q) et se ferme avec 1 sinon.

Errer Nauta
la source
5
Bonne suggestion. Je viens de remarquer que donner un répertoire en tant que argument le fait retourner 0. J'aurais préféré au moins 1. Mais ensuite, garbage in, garbage out.
meuh
13

Si vous aimez l'heuristique utilisée par GNU grep, vous pouvez l'utiliser:

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

Il recherche les octets NUL dans le premier tampon lu dans le fichier (quelques kilo-octets pour un fichier normal, mais peut être beaucoup moins pour un tuyau, un socket ou des périphériques similaires /dev/random). Dans les paramètres régionaux UTF-8, il marque également les séquences d'octets qui ne forment pas des caractères UTF-8 valides. Cela suppose que LC_ALLla langue n’est pas l’anglais.

Le ${1-$REPLY}formulaire vous permet de l'utiliser comme zshqualificatif global:

ls -ld -- *(.+isbinary)

dresserait une liste des binaires fichiers.

Stéphane Chazelas
la source
7

Vous pouvez essayer de déterminer si vous iconvpouvez lire le fichier. Ceci est moins performant que file(qui ne lit que quelques octets dès le début), mais vous donnera des résultats plus fiables:

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

Cela rend iconvfondamentalement un no-op, mais si il rencontre des données non valides (UTF-8 non valide dans cet exemple), il fera un brouillage et se fermera.

Boldewyn
la source
4
Utiliser -fet -tau lieu des options longues GNU le rendrait plus portable. Notez qu'il appellera "binaire" les fichiers qu'il ne peut pas ouvrir. Il appellera les fichiers vides "texte".
Stéphane Chazelas
D'accord. J'ai utilisé les longs formulaires pour la documentation ad hoc, pour les personnes qui ne savent pas iconv. Mais -fet -tsont généralement mieux.
Boldewyn
7

Vous pouvez écrire un script qui appelle fileet utiliser une instruction case pour vérifier les cas qui vous intéressent.

Par exemple

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

bien sûr, il peut y avoir de nombreux cas particuliers qui présentent un intérêt. En vérifiant stringsune copie de libmagic, je vois environ 200 cas, par exemple,

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

Certains utilisent la chaîne "text" dans le cadre d’un type différent, par exemple,

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

de même scriptpourrait faire partie d'un mot, mais je ne vois pas de problèmes dans ce cas. Mais un script doit rechercher "text"un mot , pas une sous - chaîne .

Pour rappel, la filesortie n'utilise pas de description précise qui aurait toujours un "script" ou un "texte". Les cas spéciaux sont quelque chose à considérer. Un suivi a commenté que les --mime-typetravaux fonctionnent alors que cette approche ne fonctionnerait pas, pour les .svgfichiers. Cependant, dans un test, je vois ces résultats pour les fichiers 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

que j’ai sélectionné après avoir vu un millier de fichiers ne montrer que 6 avec "texte" dans la sortie de type mime. Faire correspondre le "xml" à la fin de la sortie de type mime pourrait être plus utile, par exemple, que faire correspondre le "SVG", mais utiliser un script pour le faire vous ramène à la suggestion faite ici.

La sortie de filenécessite un peu d'ajustage dans les deux cas de figure et n'est pas fiable à 100% (plusieurs de mes scripts Perl le confondent et l'appellent "données").

Il y a plus d'une implémentation de file. Celui qui est le plus couramment utilisé fait son travail libmagic, qui peut être utilisé à partir de différents programmes (peut-être pas directement à partir de zsh, bien que pythonpossible).

Selon le tableau de comparaison des tests de fichiers pour shell, Perl, Ruby et Python , Perl dispose d’une -Toption qu’il peut utiliser pour fournir ces informations. Mais il ne contient aucune caractéristique comparable pour zsh.

Lectures complémentaires:

Thomas Dickey
la source
Malheureusement, filela sortie de GNU pour les fichiers svg: SVG Scalable Vector Graphics imagene contient pas le mot text. Je pensais que cette approche serait meilleure que la réponse acceptée consistant à vérifier le type mime, mais il manque encore certains types.
Peter Cordes
Il manque encore, avec le type mime; pour le fichier svg de xterm je reçois image/svg+xml. En fait - je viens de cocher un fichier de 1000 fichiers, seulement 6 sont sortis sous forme de "texte" en fonction du type mime seul. Je vais m'en tenir à un script, qui au moins peut être fait fonctionner au besoin.
Thomas Dickey
3

filea une option --mime-encodingqui tente de détecter l'encodage d'un fichier.

 $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

Vous pouvez utiliser file --mime-encoding | grep binarypour détecter si un fichier est un fichier binaire. Cela fonctionne de manière fiable, même si un seul caractère non valide dans un long fichier texte peut compliquer les choses.

Par exemple, j'appelle catle script suivant pour éviter de ruiner mon terminal en ouvrant par inadvertance un fichier binaire:

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

Les catégories sont arbitraires. Avant de répondre à la question de savoir comment faire une classification, vous avez besoin d’une définition (stricte). Pour avoir une définition, vous avez besoin d'un objectif .

Alors, que voulez-vous faire avec cette classification?

  • Si vous souhaitez sélectionner ascii / binary dans FTP, il est important de ne pas transférer un fichier binaire en tant qu'ascii (sinon, il sera corrompu). Vous devez donc vérifier si le fichier est un texte brut, HTML, RTF et quelques autres. Mais en cas de doute, sélectionnez binaire. Et peut-être voudrez-vous également vérifier que le fichier ne contient qu'un sous-ensemble tel que 0x0A, 0x0D et 0x20-0x7F.
  • Si vous souhaitez transférer le fichier dans un protocole (POP3, SMTP), vous devez tester pour choisir d’encoder en base64 ou tout simplement. Dans ce cas, vous devez vérifier s'il existe des caractères non pris en charge.
  • Tout autre cas… peut avoir toute autre définition.
ESL
la source
3
perl -e'chomp(my$f=<>);print "binary$/" if -B $f;print "text$/" if -T _'

le fera. Voir la documentation pour -Bet-T (recherchez dans cette page la chaîne The -T and -B switches work as follows).

msh210
la source
perl -le 'print -B $ARGV[0] ? "binary" : "text"' --pourrait être plus clair. Ou mêmeperl -le 'print -B $_ ? "binary" : "text", @ARGV > 1 ? "\t$_" : "" for @ARGV' --
jrw32982 soutient Monica
1

J'ai contribué à https://github.com/audreyr/binaryornot Il n'a pas (encore) d'encapsuleur de ligne de commande, mais c'est une simple bibliothèque Python assez facile à appeler même à partir de la CLI. Il utilise une heuristique assez efficace pour déterminer si un fichier est du texte ou du binaire.

Philippe Ombredanne
la source
1

Maintenant, cette réponse est un peu ancienne, mais je pense que mon ami m'a appris un grand "hack" à faire cela.

Vous utilisez la diffcommande et vérifiez votre fichier par rapport à un fichier texte de test:

$ diff filetocheck testfile.txt

Maintenant si filetocheckest un fichier binaire, le résultat serait:

Binary files filetocheck and testfile.txt differ

De cette façon, vous pouvez exploiter la diffcommande et écrire par exemple une fonction qui effectue la vérification dans un script.

utilisateur3019105
la source