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.