Existe uma maneira conveniente de classificar arquivos como "binário" ou "texto"?

35

Os utilitários padrão do Unix gostam grepe diffusam alguma heurística para classificar os arquivos como "texto" ou "binário". (Por exemplo grep, a saída pode incluir linhas como Binary file frobozz matches.)

Existe um teste conveniente que se possa aplicar em um zshscript para executar uma classificação "texto / binário" semelhante? (Diferente de algo parecido grep '' somefile | grep -q Binary.)

(Eu percebo que qualquer teste desse tipo seria necessariamente heurístico e, portanto, imperfeito.)

kjo
fonte
10
fileé um utilitário padrão e pode executar a mágica de arquivos para determinar os tipos de arquivos da melhor maneira possível. Ele sabe a maioria dos formatos de texto e faz um trabalho bastante decente em formatos binários. Se tudo que você está tentando fazer é descobrir se um arquivo é de texto ou não, isso é o comando que você está interessado.
Bratchley
@Bratchley: algumas versões fileserão impressas, por exemplo shell script, para alguns arquivos que eu gostaria de classificar como "texto". Existe uma maneira filede imprimir apenas textou binary?
KJo
1
@don_crissti Essa pergunta é sobre alguém tentando convencer as pessoas a depurar seu script bash. Detectar texto é exatamente o que o script deve fazer. Eles acabaram tendo um problema em um de seus cutcomandos.
Bratchley
1
@don_crissti O fato de haver uma resposta na pergunta A que funciona para a pergunta B nem sempre faz de A uma duplicata de B. Considere alguém que esteja procurando uma maneira de classificar arquivos como texto ou binário. O que é mais útil: uma pergunta "depurar meu script" que, por acaso, tem uma resposta genérica oculta entre outras respostas específicas desse script, ou uma genérica "como classifico os campos como texto ou binários?"?
Gilles 'SO- stop be evil' '
1
@ Gilles - depende de como você o lê. Na verdade, vejo a pergunta lá como um caso típico de um problema XY: o OP quer verificar se um arquivo é um arquivo de texto - e acha que a filesaída da tubulação cuté a solução - com certeza, há um espaço em falta que faz com que ela falhe e a maioria das pessoas aborda o Y em vez do X, mas os comentários e respostas de Stéphane mostram a maneira correta de determinar se o arquivo é texto ou não.
don_crissti

Respostas:

27

Se você pedir fileapenas o tipo MIME, obterá muitos tipos diferentes text/x-shellscript, application/x-executableetc., mas imagino que se você apenas verificar a parte "texto", deverá obter bons resultados. Por exemplo ( -bpara nenhum nome de arquivo na saída):

file -b --mime-type filename | sed 's|/.*||'
meuh
fonte
24
Basta lembrar, dependendo do seu file, que você pode perder alguns formatos de texto: application/xml(e semelhante, como RSS), application/ecmascript, application/json, image/svg+xml, ... Você teria que whitelist aqueles.
precisa saber é o seguinte
@Boldewyn wow, bons exemplos! Portanto, provavelmente uma resposta melhor é aceitar qualquer arquivo que possua apenas caracteres imprimíveis, mas de alguma forma também lidar com utf-8 e problemas de codificação semelhantes.
Meu # 11/16
Sim, essa é a essência da minha resposta abaixo. O único problema é que essa solução tem de olhar para o todo arquivo ...
Boldewyn
7
@Boldewyn Em princípio, os application/*tipos não são destinados ao consumo humano, mesmo quando podem ser baseados em texto para facilitar o desenvolvimento e a depuração. É por isso que existe um text/xmle um application/xml. Portanto, a questão de considerá-los como texto depende das necessidades do OP.
Tobia
3
Oucut -d/ -f1
Stéphane Chazelas
20

Outra abordagem seria usar isutf8a coleção moreutils .

Ele sai com 0 se o arquivo for UTF-8 ou ASCII válido, ou curto-circuito, imprime uma mensagem de erro (silencie com -q) e sai com 1 caso contrário.

Wander Nauta
fonte
5
Boa sugestão. Acabei de notar que fornecer um diretório como arg faz com que ele retorne 0. Eu teria preferido 1 pelo menos. Mas então, lixo dentro, lixo fora.
Meuhttp
13

Se você gosta da heurística usada pelo GNU grep, você pode usá-lo:

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

Ele procura por NUL bytes no primeiro buffer lido do arquivo (alguns quilo-bytes para um arquivo normal, mas pode ser muito menor para um pipe ou soquete ou alguns dispositivos como /dev/random). Nos códigos de idioma UTF-8, também sinaliza sequências de bytes que não formam caracteres UTF-8 válidos. Ele assume que LC_ALLnão está definido para algo em que o idioma não é o inglês.

O ${1-$REPLY}formulário permite usá-lo como um zshqualificador glob:

ls -ld -- *(.+isbinary)

listaria os arquivos binários .

Stéphane Chazelas
fonte
7

Você pode tentar determinar se iconvpode ler o arquivo. Isso tem menos desempenho do que file(que lê apenas alguns bytes desde o início), mas oferece resultados mais confiáveis:

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

Isso torna iconvbasicamente um no-op, mas se encontrar dados inválidos (UTF-8 inválido neste exemplo), ele vomitará e sairá.

Boldewyn
fonte
4
Usar -fe em -tvez das opções longas do GNU o tornaria mais portátil. Observe que ele chamará "binário" dos arquivos que não pode abrir. Ele chamará os arquivos vazios de "texto".
Stéphane Chazelas
Acordado. Usei os formulários longos para documentação ad hoc, para pessoas que não sabem iconv. Mas -fe -tgeralmente são melhores.
precisa saber é o seguinte
7

Você pode escrever um script que chama filee usar uma declaração de caso para verificar os casos nos quais está interessado.

Por exemplo

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

embora, é claro, possa haver muitos casos especiais de interesse. Apenas checandostrings uma cópia libmagic, vejo cerca de 200 casos, por exemplo,

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

Alguns usam a string "text" como parte de um tipo diferente, por exemplo,

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

da mesma forma scriptpoderia ser parte de uma palavra, mas não vejo problemas neste caso. Mas um script deve verificar "text"como uma palavra , não como uma substring .

Como lembrete, a filesaída não usa uma descrição precisa que sempre teria "script" ou "texto". Casos especiais são algo a considerar. Um acompanhamento comentou que os --mime-typetrabalhos, embora essa abordagem não funcionasse, para .svgarquivos. No entanto, em um teste, vejo estes resultados para arquivos 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 eu selecionei depois de ver milhares de arquivos mostrar apenas 6 com "texto" na saída do tipo MIME. Indiscutivelmente, combinar o "xml" no final da saída do tipo MIME poderia ser mais útil, digamos, do que combinar o "SVG", mas usar um script para fazer isso leva você de volta à sugestão feita aqui.

A saída de file requer algum ajuste em qualquer cenário e não é 100% confiável (é confundida por vários dos meus scripts Perl, chamando-os de "dados").

Existe mais de uma implementação de file. O mais usado faz seu trabalho libmagic, que pode ser usado em diferentes programas (talvez não diretamente zsh, emborapython possa).

De acordo com a tabela de comparação de teste de arquivo para shell, Perl, Ruby e Python , o Perl tem um-T opção que pode ser usada para fornecer essas informações. Mas ele não lista nenhum recurso comparável para zsh.

Leitura adicional:

Thomas Dickey
fonte
Infelizmente file, a saída do GNU para arquivos svg: SVG Scalable Vector Graphics imagenão contém a palavra texto. Eu pensei que essa abordagem seria melhor do que a resposta aceita para verificar o tipo MIME, mas ainda falta alguns tipos.
Peter Cordes
Ainda falta, com o tipo mímica; para o arquivo svg do xterm que recebo image/svg+xml. Na verdade - apenas verifiquei um arquivo de 1000 iguais, apenas 6 apareceram como "texto" de acordo apenas com o tipo MIME. Vou ficar com um script, que pelo menos pode ser feito para funcionar conforme necessário.
Thomas Dickey
3

filetem uma opção --mime-encodingque tenta detectar a codificação de um arquivo.

 $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

Você pode usar file --mime-encoding | grep binarypara detectar se um arquivo é um arquivo binário. Funciona de maneira confiável, embora possa ser confundido por um único caractere inválido em um arquivo de texto longo.

Por exemplo, alias catao seguinte script de shell para evitar arruinar meu terminal abrindo inadvertidamente um arquivo binário:

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

As categorias são arbitrárias. Antes de responder como fazer uma classificação, você precisa de uma definição (estrita). Para ter uma definição, você precisa de um objetivo .

Então, o que você quer fazer com essa classificação?

  • Se você deseja selecionar ascii / binário no FTP, é importante não transferir um arquivo binário como ascii (ou ele estará corrompido). Portanto, você deve testar se o arquivo é texto simples, html, rtf e alguns outros. Mas na dúvida, selecione binário. E talvez você também queira testar se o arquivo tem apenas um subconjunto como 0x0A, 0x0D e 0x20-0x7F.
  • Se você deseja transferir o arquivo em algum protocolo (POP3, SMTP), é necessário testar para escolher se codifica em base64 ou simplesmente. Nesse caso, você deve testar se há caracteres não suportados.
  • Qualquer outro caso ... pode ter qualquer outra definição.
ESL
fonte
3
perl -e'chomp(my$f=<>);print "binary$/" if -B $f;print "text$/" if -T _'

vai fazer isso. Consulte a documentação para -Be-T (pesquise nessa página a sequência The -T and -B switches work as follows).

msh210
fonte
perl -le 'print -B $ARGV[0] ? "binary" : "text"' --pode ser mais claro. Ou aindaperl -le 'print -B $_ ? "binary" : "text", @ARGV > 1 ? "\t$_" : "" for @ARGV' --
jrw32982 suporta Monica
1

Eu contribuí com https://github.com/audreyr/binaryornot Ele ainda não possui um wrapper de linha de comando, mas esta é uma biblioteca Python simples, fácil de chamar, mesmo a partir da CLI. Ele usa uma heurística bastante eficiente para determinar se um arquivo é texto ou binário.

Philippe Ombredanne
fonte
1

Agora, essa resposta é um pouco antiga, mas acho que meu amigo me ensinou um ótimo "truque" para fazer isso.

Você usa o diffcomando e verifica seu arquivo em um arquivo de texto de teste:

$ diff filetocheck testfile.txt

Agora, se filetocheckfor um arquivo binário, a saída seria:

Binary files filetocheck and testfile.txt differ

Dessa forma, você pode aproveitar o diffcomando e, por exemplo, escrever uma função que faz a verificação em um script.

user3019105
fonte