Como encontrar arquivos com 100% de caracteres NUL no conteúdo?


16

Qual é o comando da linha de comando do Linux que pode identificar esses arquivos?

AFAIK o findcomando (ou grep) só pode corresponder a uma sequência específica dentro do arquivo de texto. Mas quero combinar o conteúdo inteiro, ou seja, quero ver quais arquivos correspondem à expressão regular \0+, ignorando o (s) caractere (s) de final de linha . Talvez o find . cat | grepidioma possa funcionar, mas não sei como fazer grep ignorar linhas (e tratar o arquivo como binário).

Antecedentes: todos os dias, quando meu laptop congela, minha partição btrfs perde informações: os arquivos abertos para gravação recebem seu conteúdo substituído por zeros (o tamanho do arquivo permanece mais ou menos intacto). Uso a sincronização e não quero que esses arquivos falsos se propaguem: preciso de uma maneira de identificá-los para que possa obtê-los do backup.


você quer dizer arquivos com zeros numéricos?
Rahul Patil

2
Eu acho que é sobre caracteres NULL, em vez de zeros numéricos.
Gdvdijk

10
Vamos dar um passo atrás aqui. A cada poucos dias, quando seu laptop congela? Por que não estamos tentando consertar isso , o verdadeiro problema aqui?
D_Bye

2
@D_Bye é uma boa ideia, mas até agora não foi longe demais: [ unix.stackexchange.com/questions/57894/… #
Adam Ryczkowski 20/12/12 /

1
você já considerou a -vopção de grep: filtrar todos os ficheiros que tenham qualquer byte 1 a 255.
ctrl-alt-Delor

Respostas:


10

Você pode grepobter using caracteres usando o modo regex Perl:

$ echo -ne "\0\0" > nul.bin
$ echo -ne "\0x\0" > non-nul.bin
$ grep -P "[^\0]" *.bin
Binary file non-nul.bin matches

Então você pode usar isso:

for path in *.foo
do
    grep -P "[^\0]" "$path" || echo "$path"
done

Eu recebo resultados inesperados, usando GNU grep 2.5.4. Independentemente de eu usar --binary-files=textou --binary-files=binary, ele fornece um trueresultado para todos os valores de dados não vazios, por exemplo. "\0\0", "\0x\0", "abcd"... O código exato que usei é: for typ in binary text ;do for dat in '\0\0' '\0x\0' 'abcd' '' ;do printf "$dat" >f; grep --binary-files=$typ -P '[^\0]' f >/dev/null && echo true || echo false; done; done
Peter.O

1
Eu tentei agora GNU grep) 2.10. Esta versão mais recente fornece os resultados esperados ... então, um +1 tardio
Peter.O

1
Falha em um arquivo criado com printf '\0\n\0\0\n\n' > fileou printf '\n' > filepara esse assunto.
Stéphane Chazelas

2
@ StéphaneChazelas OP disse "ignorando o (s) caractere (s) de final de linha". Portanto, qualquer arquivo que consiste apenas \0e \ncaracteres (mesmo zero de qualquer um) seria uma correspondência.
l0b0

6

Eu concordo com o que D_Bye diz sobre encontrar a raiz do problema.

De qualquer forma, verifique se um arquivo contém apenas \0e / ou \nvocê pode usar tr:

<file tr -d '\0\n' | wc -c

Que retorna 0 para arquivos nulos / nova linha e vazios.


2
tr -d '\0\n'resolve o problema da nova linha, que deixa apenas o problema (?) dos arquivos vazios listados na saída ... No entanto, ele processa todos os bytes de cada arquivo (o que pode ou não ser um problema) +1
Peter.O

@ Peter.O: eu perdi o requisito de nova linha, obrigado. Essa solução não é muito otimizada e, para executar muitos dados, seria melhor com uma solução que segue em frente ao encontrar bytes não correspondentes.
Thor

Funciona muito bem. No meu caso, só tive que me certificar de excluir arquivos de tamanho zero. Obrigado.
Adam Ryczkowski

1
No entanto, isso também contará arquivos com novas linhas como "vazios".
Chris Baixo

1
@ ChrisDown: deixei o texto da resposta claro sobre o que ele faz. Não está claro o que o OP quer fazer com arquivos somente de nova linha.
Thor

5

Eu suspeito que esses arquivos sejam escassos, ou seja, eles não têm espaço em disco alocado para eles, eles apenas especificam o tamanho do arquivo ( duinformaria 0 para eles).

Nesse caso, com o GNU find, você pode fazer (assumindo que nenhum caminho do arquivo contenha caracteres de nova linha):

find . -type f -size +0 -printf '%b:%p\n' | grep '^0:' | cut -d: -f2-

Bom ponto. Eu nunca pensei sobre isso. Vou tentar. O uso duevitará riscar o conteúdo de cada arquivo no sistema de arquivos, portanto, todo o procedimento não levará mais de 30 minutos para ser concluído.
Adam Ryczkowski

(e printf %bacima relatórios que duiria relatar)
Stéphane Chazelas

Gostaria de mudar -size +0para -size +1arquivos de tamanho zero são excluídos dos resultados. Também os arquivos que contêm \nseu caminho causarão problemas para este comando.
Tyson

@Tyson -size +0é para tamanhos estritamente maiores que 0. -size +1seria para tamanhos estritamente maiores que 512. A limitação da nova linha já foi mencionada.
Stéphane Chazelas 21/01

@ StéphaneChazelas Obrigado por me esclarecer a respeito -size +1, você está realmente correto. Eu consertei minha resposta. :-)
Tyson

4

Aqui está um pequeno programa python que pode fazer isso:

import sys

def only_contains_nulls(fobj, chunk_size=1024):
    first = True
    while True:
        data = fobj.read(chunk_size)
        if not data:
            if first:
                return 1  # No data
            else:
                return 0
        if data.strip("\0"):
            return 1
        first = False

if __name__ == '__main__':
    with open(sys.argv[1]) as f:
        sys.exit(only_contains_nulls(f))

E em ação:

$ printf '\0\0\0' > file
$ ./onlynulls file && echo "Only nulls" || echo "Non-null characters"
Only nulls
$ printf a >> file
$ ./onlynulls file && echo "Only nulls" || echo "Non-null characters"
Non-null characters

Você pode verificar vários arquivos usando descoberta de -exec, xargs, GNU parallel, e programas semelhantes. Como alternativa, isso imprimirá nomes de arquivos que precisam ser tratados:

files=( file1 file2 )
for file in "${files[@]}"; do
    ./onlynulls "$file" || printf '%s\n' "$file"
done

Lembre-se de que se você passar a saída disso para outro programa, os nomes de arquivos podem conter novas linhas, portanto, você deve delimitá-lo de maneira diferente (apropriadamente, com \0).

Se você tiver muitos arquivos, seria melhor usar uma opção para processamento paralelo, pois isso lê apenas um arquivo por vez.


2
Cuidado, arquivos com tamanho zero (por exemplo: /etc/nologin, ~/.hushlogin, .nomedia, ...) são erroneamente identificada por esta resposta.
Tyson

@ Tyson Obrigado por apontar isso! Eu apenas consertei.
Chris Down

3

Encontre arquivos que contenham apenas caracteres nulos '\ 0' e caracteres de nova linha '\ n'.
O qin sed faz com que cada pesquisa de arquivo seja encerrada imediatamente após encontrar qualquer caractere não nulo em uma linha.

find -type f -name 'file-*' |
  while IFS= read -r file ;do 
      out=$(sed -n '1=; /^\x00\+$/d; i non-null
                      ; q' "$file")
      [[ $out == "1" ]] &&  echo "$file"
  done

Faça arquivos de teste

> file-empty
printf '%s\n' 'line1' 'line2' 'line3'      > file-with-text           
printf '%4s\n' '' '' xx | sed 's/ /\x00/g' > file-with-text-and-nulls
printf '%4s\n' '' '' '' | sed 's/ /\x00/g' > file-with-nulls-and-newlines
printf '%4s'   '' '' '' | sed 's/ /\x00/g' > file-with-nulls-only

resultado

./file-with-nulls-and-newlines
./file-with-nulls-only

Ou o -print0argumento parece estar faltando findou a IFS=parte está confusa. Qual era o delimitador pretendido?
Tyson

3

Este one-liner é a forma mais eficiente de encontrar 100% arquivos nul usando GNU find, xargse grep(assumindo que o último é construído com o apoio PCRE):

find . -type f -size +0 -readable -print0 |
  LC_ALL=C xargs -r0 grep -LP "[^\x00]" --

As vantagens deste método sobre outras respostas fornecidas são:

  • arquivos não esparsos são incluídos na pesquisa.
  • arquivos não legíveis não são passados ​​para o grep, evitando Permission deniedavisos.
  • grepinterromperá a leitura dos dados dos arquivos após localizar qualquer byte diferente de nulo ( LC_ALL=Cé usado para garantir que cada byte seja interpretado como um caractere ).
  • arquivos vazios (zero bytes) não são incluídos nos resultados.
  • menos grepprocessos verificam com eficiência vários arquivos.
  • caminhos contendo novas linhas ou começando com -são tratados corretamente.
  • funciona na maioria dos sistemas embarcados que não possuem Python / Perl.

Passar a -Zopção para grepe usar xargs -r0 ...permite que outras ações sejam executadas nos arquivos 100% nul (por exemplo: limpeza):

find . -type f -size +0 -readable -print0 |
  LC_ALL=C xargs -0 grep -ZLP "[^\x00]" -- |
  xargs -r0 rm --

Eu também recomendo usar as findopções -Ppara evitar seguir links simbólicos e -xdevevitar percorrer sistemas de arquivos (por exemplo: montagens remotas, árvores de dispositivos, montagens de ligação, etc).

Para ignorar o (s) caractere (s) de final de linha , a seguinte variante deve funcionar (embora eu não ache que seja uma boa ideia):

find . -type f -size +0 -readable -print0 |
  LC_ALL=C xargs -r0 grep -LP "[^\x00\r\n]" --

Juntando tudo, incluindo a remoção de arquivos indesejados (100% de caracteres nul / nova linha) para impedir o backup:

find -P . -xdev -type f -size +0 -readable -print0 |
  LC_ALL=C xargs -0 grep -ZLP "[^\x00\r\n]" -- |
  xargs -0 rm --

Eu não recomendo incluir arquivos vazios (zero bytes), eles geralmente existem para fins muito específicos .


Ser o mais rápido dentre tantas alternativas é uma afirmação ousada.
Marcarei

Essa referência dependeria de muitos fatores, incluindo o desempenho dos vários subsistemas de disco.
Tyson

Claro, mas qualquer coisa é melhor que nada. Várias abordagens otimizam o uso da CPU de maneira diferente, por isso faz sentido compará-lo no SSD ou mesmo em arquivos em cache. Pegue a máquina na qual você trabalha atualmente, escreva uma frase do que é (tipo de CPU, número de núcleos, RAM, tipo de disco rígido), descreva o conjunto de arquivos (por exemplo, clone de origem do kernel + arquivo de 1 GB cheio de \0buraco de 900 MB) e momento atual dos resultados. Se você fizer isso de uma maneira que a referência seja convincente para você, provavelmente será convincente para todos nós
Adam Ryczkowski

"a maioria dos sistemas embarcados" não possui utilitários GNU. Provavelmente os mais ocupados.
Stéphane Chazelas 22/01

-Pé o padrão em find. Se você deseja seguir os links simbólicos, é -L/ -follow. Você verá que o POSIX nem mesmo especifica essa opção find(mesmo que o POSIX tenha introduzido -P / -H / -L para alguns comandos).
Stéphane Chazelas 22/01

0

Para usar o GNU sed, você pode usar a -zopção, que define uma linha como seqüências terminadas em zero e corresponde e exclui linhas vazias da seguinte forma:

if [ "$( sed -z '/^$/d' "$file" | head -c 1 | wc -c )" -eq 0 ]; then
    echo "$file contains only NULL!"
fi

O comando head inbetween é apenas uma otimização.


-1

Pitão

Único arquivo

Defina o alias:

alias is_binary="python -c 'import sys; sys.exit(not b\"\x00\" in open(sys.argv[1], \"rb\").read())'"

Teste-o:

$ is_binary /etc/hosts; echo $?
1
$ is_binary `which which`; echo $?
0

Vários arquivos

Encontre todos os arquivos binários recursivamente:

IS_BINARY='import sys; sys.exit(not b"\x00" in open(sys.argv[1], "rb").read())'
find . -type f -exec bash -c "python -c '$IS_BINARY' {} && echo {}" \;

Para encontrar todos os arquivos não binários, altere &&com ||.


1
A pergunta feita para identificar arquivos que contenham única (novas linhas ignorando) caracteres nul, o código Python dada aqui arquivos identifica contendo quaisquer caracteres nul.
Tyson
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.