Apakah ada cara mudah untuk mengklasifikasikan file sebagai "biner" atau "teks"?

35

Utilitas Unix standar suka grepdan diffmenggunakan beberapa heuristik untuk mengklasifikasikan file sebagai "teks" atau "biner". ( grepOutput Eg mungkin termasuk baris seperti Binary file frobozz matches.)

Apakah ada tes yang nyaman yang dapat diterapkan dalam zshnaskah untuk melakukan klasifikasi "teks / biner" yang serupa? (Selain sesuatu seperti grep '' somefile | grep -q Binary.)

(Saya menyadari bahwa tes semacam itu harus heuristik, dan karena itu tidak sempurna.)

kjo
sumber
10
fileadalah utilitas standar dan dapat dijalankan melalui sihir file untuk menentukan jenis file dengan kemampuan terbaiknya. Ini dapat memberitahu sebagian besar format teks dan melakukan pekerjaan yang cukup baik pada format biner. Jika semua yang Anda coba lakukan adalah mencari tahu apakah suatu file adalah teks atau bukan, itulah perintah yang Anda minati.
Bratchley
@Bratchley: beberapa versi fileakan dicetak, misalnya shell script, untuk beberapa file saya ingin diklasifikasikan sebagai "teks". Apakah ada cara fileuntuk mencetak hanya textatau binary?
kjo
1
@don_crissti Pertanyaan itu adalah tentang seseorang yang mencoba membuat orang men-debug skrip bash-nya. Mendeteksi teks adalah apa yang seharusnya dilakukan skrip. Mereka akhirnya mengalami masalah di salah satu cutperintah mereka .
Bratchley
1
@don_crissti Fakta bahwa ada jawaban pada pertanyaan A yang berfungsi untuk pertanyaan B tidak selalu membuat A menjadi duplikat B. Pertimbangkan seseorang yang mencari cara untuk mengklasifikasikan file sebagai teks atau biner. Mana yang lebih berguna: pertanyaan "debug skrip saya" yang kebetulan memiliki jawaban umum terkubur di antara jawaban lain yang khusus untuk skrip itu, atau generik "bagaimana cara mengklasifikasikan fiel sebagai teks atau biner?"?
Gilles 'SO- berhenti bersikap jahat'
1
@Gilles - tergantung pada bagaimana Anda membacanya. Saya benar-benar melihat pertanyaan di sana sebagai kasus khas dari masalah XY: OP di sana ingin memeriksa apakah file adalah file teks - dan berpikir fileoutput perpipaan cutadalah solusinya - tentu saja, ada ruang yang hilang yang membuatnya gagal dan yang telah membuat kebanyakan orang di sana membahas Y bukan X tetapi komentar dan jawaban Stéphane menunjukkan cara yang tepat untuk menentukan apakah file tersebut teks atau tidak.
don_crissti

Jawaban:

27

Jika Anda filehanya meminta tipe mime, Anda akan mendapatkan banyak yang berbeda seperti text/x-shellscript, dan application/x-executablelain - lain, tetapi saya bayangkan jika Anda hanya memeriksa bagian "teks" Anda harus mendapatkan hasil yang baik. Misalnya ( -btanpa keluaran nama file):

file -b --mime-type filename | sed 's|/.*||'
meuh
sumber
24
Hanya ingat, tergantung pada Anda file, bahwa Anda mungkin kehilangan beberapa format teks: application/xml(dan mirip seperti RSS), application/ecmascript, application/json, image/svg+xml, ... Anda harus daftar putih mereka.
Boldewyn
@Boldewyn wow, contoh-contoh yang bagus! Jadi mungkin jawaban yang lebih baik adalah hanya menerima file yang hanya memiliki karakter yang dapat dicetak, tetapi entah bagaimana juga mengatasi utf-8 dan masalah penyandian yang serupa.
meuh
Ya, itulah inti dari jawaban saya di bawah ini. Satu-satunya masalah adalah, bahwa solusi harus melihat seluruh file ...
Boldewyn
7
@Boldewyn Pada prinsipnya, application/*tipe tidak dimaksudkan untuk konsumsi manusia, bahkan ketika mereka mungkin berbasis teks untuk memfasilitasi pengembangan dan debugging. Itu sebabnya ada a text/xmldan a application/xml. Jadi pertanyaan apakah menganggapnya sebagai teks tergantung pada kebutuhan OP.
Tobia
3
Ataucut -d/ -f1
Stéphane Chazelas
20

Pendekatan lain akan digunakan isutf8dari koleksi moreutils .

Keluar dengan 0 jika file tersebut valid UTF-8 atau ASCII, atau korsleting, mencetak pesan kesalahan (diam dengan -q) dan keluar dengan 1 sebaliknya.

Mengembara Nauta
sumber
5
Saran yang bagus. Saya hanya memperhatikan bahwa memberikan direktori sebagai arg membuatnya kembali 0. Saya lebih suka 1 setidaknya. Tapi kemudian, sampah masuk, sampah keluar.
meuh
13

Jika Anda menyukai heuristik yang digunakan oleh GNU grep, Anda bisa menggunakannya:

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

Itu mencari NUL byte di buffer pertama yang dibaca dari file (beberapa kilo-byte untuk file biasa, tetapi bisa jauh lebih sedikit untuk pipa atau soket atau beberapa perangkat seperti /dev/random). Di lokal UTF-8, ia juga menandai urutan byte yang tidak membentuk karakter UTF-8 yang valid. Diasumsikan LC_ALLtidak diatur ke sesuatu di mana bahasa itu bukan bahasa Inggris.

The ${1-$REPLY}Bentuk memungkinkan Anda untuk menggunakannya sebagai zshkualifikasi glob:

ls -ld -- *(.+isbinary)

akan daftar file biner .

Stéphane Chazelas
sumber
7

Anda dapat mencoba menentukan apakah iconvdapat membaca file. Ini kurang berkinerja daripada file(yang hanya membaca beberapa byte dari awal), tetapi akan memberi Anda hasil yang lebih dapat diandalkan:

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

Ini iconvpada dasarnya membuat no-op, tetapi jika ia menemukan data yang tidak valid (tidak valid UTF-8 dalam contoh ini), itu akan muntah dan keluar.

Boldewyn
sumber
4
Menggunakan -fdan -tbukannya GNU, opsi panjang akan membuatnya lebih portabel. Perhatikan bahwa ia akan memanggil "biner" file yang tidak dapat dibuka. Ini akan memanggil file kosong "teks".
Stéphane Chazelas
Sepakat. Saya menggunakan formulir panjang untuk dokumentasi ad hoc, untuk orang-orang yang tidak tahu iconv. Tapi -fdan -tbiasanya lebih baik.
Boldewyn
7

Anda dapat menulis skrip yang memanggil file, dan menggunakan pernyataan kasus untuk memeriksa kasus yang Anda minati.

Sebagai contoh

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

walaupun tentu saja mungkin ada banyak kasus khusus yang menarik. Hanya memeriksa stringssalinan libmagic, saya melihat sekitar 200 kasus, misalnya,

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

Beberapa menggunakan string "teks" sebagai bagian dari tipe yang berbeda, misalnya,

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

juga scriptbisa menjadi bagian dari kata, tetapi saya tidak melihat masalah dalam hal ini. Tetapi skrip harus memeriksa "text"sebagai kata , bukan substring .

Sebagai pengingat, fileoutput tidak menggunakan deskripsi yang tepat yang akan selalu memiliki "skrip" atau "teks". Kasus khusus adalah sesuatu yang perlu dipertimbangkan. Tindak lanjut berkomentar bahwa --mime-typebekerja sementara pendekatan ini tidak, untuk .svgfile. Namun, dalam tes saya melihat hasil ini untuk 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

yang saya pilih setelah melihat seribu file hanya menampilkan 6 dengan "teks" di output tipe mime. Bisa dibilang, mencocokkan "xml" di akhir output tipe mime bisa lebih bermanfaat, katakanlah, daripada mencocokkan "SVG", tetapi menggunakan skrip untuk melakukan itu akan membawa Anda kembali ke saran yang dibuat di sini.

Output dari filememerlukan beberapa penyetelan dalam skenario baik, dan tidak 100% dapat diandalkan (itu bingung oleh beberapa skrip Perl saya, menyebutnya "data").

Ada lebih dari satu implementasi dari file. Yang paling umum digunakan melakukan tugasnya libmagic, yang dapat digunakan dari program yang berbeda (mungkin tidak langsung dari zsh, meskipun pythonbisa).

Menurut tabel perbandingan uji file untuk shell, Perl, Ruby, dan Python , Perl memiliki -Topsi yang dapat digunakan untuk memberikan informasi ini. Tapi itu tidak mencantumkan daftar fitur yang sebanding zsh.

Bacaan lebih lanjut:

Thomas Dickey
sumber
Sayangnya filekeluaran GNU untuk file svg: SVG Scalable Vector Graphics imagetidak mengandung teks kata. Saya pikir pendekatan ini akan lebih baik daripada jawaban yang diterima untuk memeriksa tipe mime, tetapi masih melewatkan beberapa tipe.
Peter Cordes
Masih merindukan, dengan tipe pantomim; untuk file svg xterm saya dapatkan image/svg+xml. Sebenarnya - hanya memeriksa 1000 file yang sama, hanya 6 yang keluar sebagai "teks" sesuai dengan tipe mime saja. Saya akan tetap menggunakan skrip, yang setidaknya dapat dibuat berfungsi sesuai kebutuhan.
Thomas Dickey
3

filememiliki opsi --mime-encodingyang mencoba mendeteksi penyandian 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

Anda dapat menggunakan file --mime-encoding | grep binaryuntuk mendeteksi apakah suatu file adalah file biner. Ini berfungsi andal meskipun bisa dikacaukan oleh satu karakter yang tidak valid dalam file teks yang panjang.

Sebagai contoh, saya alias catke skrip shell berikut untuk menghindari merusak terminal saya dengan secara tidak sengaja membuka file biner:

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

Kategori arbitrer. Sebelum menjawab cara membuat klasifikasi, Anda memerlukan definisi (ketat). Untuk memiliki definisi, Anda memerlukan suatu tujuan .

Jadi, apa yang ingin Anda lakukan dengan klasifikasi itu?

  • Jika Anda ingin memilih ascii / biner di FTP, penting untuk tidak mentransfer file biner sebagai ascii (atau akan rusak). Jadi Anda harus menguji apakah file tersebut adalah teks biasa, html, rtf, dan beberapa lainnya. Namun ragu, pilih biner. Dan mungkin Anda juga ingin menguji bahwa file tersebut hanya memiliki subset seperti 0x0A, 0x0D, dan 0x20-0x7F.
  • Jika Anda ingin mentransfer file dalam beberapa protokol (POP3, SMTP), Anda perlu menguji untuk memilih apakah menyandikan di base64 atau sekadar. Dalam hal ini, Anda harus menguji apakah ada karakter yang tidak didukung.
  • Kasus lain ... mungkin memiliki definisi lain.
ESL
sumber
3
perl -e'chomp(my$f=<>);print "binary$/" if -B $f;print "text$/" if -T _'

akan melakukannya. Lihat dokumentasi untuk -Bdan-T (cari string tersebut di halaman itu The -T and -B switches work as follows).

msh210
sumber
perl -le 'print -B $ARGV[0] ? "binary" : "text"' --mungkin lebih jelas. Atau bahkanperl -le 'print -B $_ ? "binary" : "text", @ARGV > 1 ? "\t$_" : "" for @ARGV' --
jrw32982 mendukung Monica
1

Saya berkontribusi pada https://github.com/audreyr/binaryornot Ia belum memiliki pembungkus baris perintah (namun) tetapi ini adalah pustaka Python sederhana yang cukup mudah untuk dipanggil bahkan dari CLI. Ia menggunakan heuristik yang cukup efisien untuk menentukan apakah suatu file adalah teks atau biner.

Philippe Ombredanne
sumber
1

Saya sekarang jawaban ini agak tua, tetapi saya pikir teman saya mengajari saya "retasan" yang hebat untuk melakukan ini.

Anda menggunakan diffperintah dan memeriksa file Anda terhadap file teks uji:

$ diff filetocheck testfile.txt

Sekarang jika filetocheckfile biner, hasilnya adalah:

Binary files filetocheck and testfile.txt differ

Dengan cara ini Anda dapat memanfaatkan diffperintah dan misalnya menulis fungsi yang melakukan pemeriksaan dalam skrip.

pengguna3019105
sumber