Esiste un modo conveniente per classificare i file come "binari" o "testo"?


35

I programmi di utilità Unix standard gradiscono grepe diffusano un po 'euristici per classificare i file come "testo" o "binario". (Ad esempio grep, l'output può includere righe simili Binary file frobozz matches.)

Esiste un comodo test che si può applicare in uno zshscript per eseguire una classificazione "testo / binaria" simile? (A parte qualcosa di simile grep '' somefile | grep -q Binary.)

(Mi rendo conto che qualsiasi test di questo tipo sarebbe necessariamente euristico e quindi imperfetto.)


10
fileè un'utilità standard e può eseguire la magia dei file per determinare i tipi di file al meglio delle sue capacità. Può dire la maggior parte dei formati di testo e fa un lavoro abbastanza decente sui formati binari. Se tutto ciò che stai cercando di fare è scoprire se un file è di testo o meno, questo è il comando che ti interessa.
Bratchley

@Bratchley: fileverranno stampate alcune versioni di , ad esempio shell script, per alcuni file che vorrei classificare come "testo". C'è un modo fileper stampare solo texto binary?
kjo,

1
@don_crissti Quella domanda riguarda qualcuno che cerca di convincere la gente a eseguire il debug del suo script bash. Rilevare il testo è proprio quello che dovrebbe fare lo script. Hanno finito per avere un problema in uno dei loro cutcomandi.
Bratchley,

1
@don_crissti Il fatto che ci sia una risposta alla domanda A che funziona per la domanda B non sempre rende A un duplicato di B. Considera qualcuno che sta cercando un modo per classificare i file come testo o binario. Che cosa è più utile: una domanda di "debug del mio script" che sembra avere una risposta generica sepolta tra le altre risposte specifiche di quello script o un generico "come classifico i campi come testo o binari?"?
Gilles 'SO- smetti di essere malvagio' il

1
@Gilles - dipende da come lo leggi. In realtà vedo la domanda lì come un tipico caso di problema XY: OP lì vuole verificare se un file è un file di testo - e pensa che l' fileoutput del piping cutsia la soluzione - certo, c'è uno spazio mancante che lo fa fallire e che ha fatto la maggior parte delle persone si rivolge a Y anziché a X ma i commenti e la risposta di Stéphane mostrano il modo corretto di determinare se il file è di testo o meno.
don_crissti,

Risposte:


27

Se chiedi filesolo il tipo mime ne otterrai molti diversi come text/x-shellscript, application/x-executableecc., Ma immagino che se controlli la parte "testo" dovresti ottenere buoni risultati. Ad esempio ( -bper nessun nome file nell'output):

file -b --mime-type filename | sed 's|/.*||'

24
Basta ricordare, a seconda del vostro file, che si potrebbe perdere alcuni formati di testo: application/xml(e simile come RSS), application/ecmascript, application/json, image/svg+xml, ... Dovresti whitelist quelli.
Boldewyn,

@Boldewyn wow, begli esempi! Quindi probabilmente una risposta migliore è solo accettare qualsiasi file che ha solo caratteri stampabili, ma in qualche modo anche far fronte a utf-8 e problemi di codifica simili.
Meuh

Sì, questo è l'essenza della mia risposta qui sotto. L'unico problema è che quella soluzione deve esaminare l' intero file ...
Boldewyn,

7
@Boldewyn In linea di principio, i application/*tipi non sono destinati al consumo umano, anche quando possono essere basati su testo per facilitare lo sviluppo e il debug. Ecco perché c'è sia a text/xmlche an application/xml. Quindi la domanda se considerarli come testo dipende dalle esigenze del PO.
Tobia,

3
Oppurecut -d/ -f1
Stéphane Chazelas l'

20

Un altro approccio sarebbe quello di utilizzare isutf8dalla collezione moreutils .

Esce con 0 se il file è valido UTF-8 o ASCII, o cortocircuiti, stampa un messaggio di errore (silenzio con -q) ed esce con 1 altrimenti.


5
Bel suggerimento. Ho appena notato che dare una directory come arg la fa restituire 0. Avrei preferito almeno 1. Ma poi, immondizia dentro, spazzatura fuori.
Meuh

13

Se ti piace l'euristica usata da GNU grep, puoi usarla:

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

Si cerca NUL byte nel primo buffer letto dal file (qualche kilo-byte per un file regolare, ma potrebbe essere molto meno per un tubo o presa o alcuni dispositivi come /dev/random). Nelle versioni locali UTF-8, contrassegna anche le sequenze di byte che non formano caratteri UTF-8 validi. Presuppone che LC_ALLnon sia impostato su qualcosa in cui la lingua non è l'inglese.

Il ${1-$REPLY}modulo consente di utilizzarlo come zshqualificatore glob:

ls -ld -- *(.+isbinary)

elencherebbe i file binari .


7

Puoi provare a determinare se è iconvpossibile leggere il file. Questo è meno performante di file(che legge solo un paio di byte dall'inizio), ma ti darà risultati più affidabili:

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

Questo rende iconvfondamentalmente una no-op, ma se rileva dati non validi (UTF-8 non valido in questo esempio), scatta e termina.


4
L'uso di -fe al -tposto delle lunghe opzioni GNU lo renderebbe più portatile. Nota che chiamerà "binario" i file che non può aprire. Chiamerà file vuoti "testo".
Stéphane Chazelas,

Concordato. Ho usato i moduli lunghi per la documentazione ad hoc, per le persone che non lo sanno iconv. Ma -fe di -tsolito sono migliori.
Boldewyn,

7

Puoi scrivere uno script che chiama filee usare un'istruzione case per verificare i casi che ti interessano.

Per esempio

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

anche se ovviamente ci possono essere molti casi speciali che sono di interesse. Sto solo controllando stringsuna copia di libmagic, vedo circa 200 casi, ad es.

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

Alcuni usano la stringa "testo" come parte di un tipo diverso, ad es.

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

allo stesso modo scriptpotrebbe far parte di una parola, ma in questo caso non vedo problemi. Ma uno script dovrebbe cercare "text"come parola , non come sottostringa .

Come promemoria, l' fileoutput non utilizza una descrizione precisa che avrebbe sempre "script" o "testo". Casi speciali sono qualcosa da considerare. Un follow-up ha commentato che --mime-typefunziona mentre questo approccio no, per i .svgfile. Tuttavia, in un test vedo questi risultati per i file 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

che ho selezionato dopo aver visto un migliaio di file mostra solo 6 con "testo" nell'output di tipo mime. Probabilmente, abbinare "xml" alla fine dell'output di tipo mime potrebbe essere più utile, diciamo, che abbinare "SVG", ma usare uno script per farlo ti riporta al suggerimento fatto qui.

L'output di filerichiede un po 'di tuning in entrambi gli scenari, e non è affidabile al 100% (è confuso da molti dei miei script Perl, chiamandoli "dati").

Esiste più di una implementazione di file. Quello più comunemente usato fa il suo lavoro libmagic, che può essere utilizzato da diversi programmi (forse non direttamente da zsh, anche se pythonpuò).

Secondo la tabella di confronto dei test dei file per shell, Perl, Ruby e Python , Perl ha -Tun'opzione che può usare per fornire queste informazioni. Ma non elenca alcuna funzionalità comparabile per zsh.

Ulteriori letture:


Sfortunatamente filel'output di GNU per i file svg: SVG Scalable Vector Graphics imagenon contiene il testo della parola. Ho pensato che questo approccio sarebbe stato migliore della risposta accettata del controllo del tipo MIME, ma manca ancora alcuni tipi.
Peter Cordes,

Manca ancora, con il tipo mime; per il file svg di xterm che ottengo image/svg+xml. In realtà - ho appena controllato lo stesso file 1000, solo 6 sono usciti come "testo" secondo il solo tipo mime. Continuerò con una sceneggiatura, che almeno può essere fatta funzionare secondo necessità.
Thomas Dickey,

3

fileha un'opzione --mime-encodingche tenta di rilevare la codifica di un file.

 $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

È possibile utilizzare file --mime-encoding | grep binaryper rilevare se un file è un file binario. Funziona in modo affidabile anche se può essere confuso da un singolo carattere non valido in un lungo file di testo.

Ad esempio, alias catal seguente script di shell per evitare di rovinare il mio terminale aprendo inavvertitamente un file binario:

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

3

Le categorie sono arbitrarie. Prima di rispondere a come effettuare una classificazione, è necessaria una definizione (rigorosa). Per avere una definizione, hai bisogno di uno scopo .

Quindi, cosa vuoi fare con quella classificazione?

  • Se si desidera selezionare ascii / binary in FTP, è importante non trasferire un file binario come ascii (o sarà danneggiato). Quindi dovresti testare se il file è in testo semplice, HTML, RTF e alcuni altri. Ma nel dubbio, seleziona binario. E forse vuoi anche provare che il file ha solo un sottoinsieme come 0x0A, 0x0D e 0x20-0x7F.
  • Se si desidera trasferire il file in un protocollo (POP3, SMTP), è necessario provare per scegliere se codificare in base64 o semplicemente. In questo caso, è necessario verificare se sono presenti caratteri non supportati.
  • Qualsiasi altro caso ... può avere qualsiasi altra definizione.

3
perl -e'chomp(my$f=<>);print "binary$/" if -B $f;print "text$/" if -T _'

lo farà. Vedere la documentazione per -Be-T (cercare la stringa in quella pagina The -T and -B switches work as follows).


perl -le 'print -B $ARGV[0] ? "binary" : "text"' --potrebbe essere più chiaro. O addiritturaperl -le 'print -B $_ ? "binary" : "text", @ARGV > 1 ? "\t$_" : "" for @ARGV' --
jrw32982 supporta Monica il


1

Ora questa risposta è un po 'vecchia, ma penso che il mio amico mi abbia insegnato un ottimo "hack" per farlo.

Si utilizza il diffcomando e si controlla il file in un file di testo di prova:

$ diff filetocheck testfile.txt

Ora se filetocheckè un file binario, l'output sarebbe:

Binary files filetocheck and testfile.txt differ

In questo modo è possibile sfruttare il diffcomando e ad esempio scrivere una funzione che esegue il controllo in uno script.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.