Как найти файлы со 100% NUL-символами в их содержимом?


16

Что такое команда командной строки Linux, которая может идентифицировать такие файлы?

Насколько нам известно , findкоманда (или grep) может только соответствовать определенной строке внутри текстового файла. Но я хочу сопоставить все содержимое, то есть я хочу увидеть, какие файлы соответствуют регулярному выражению \0+, игнорируя символы конца строки . Может быть, find . cat | grepидиома могла бы работать, но я не знаю, как заставить grep игнорировать строки (и обрабатывать файл как двоичный).

Предыстория: каждые несколько дней, когда мой ноутбук зависает, мой раздел btrfs теряет информацию: файлы, открытые для записи, заменяют свое содержимое нулями (размер файла остается более или менее неизменным). Я использую синхронизацию и не хочу, чтобы эти поддельные файлы распространялись: мне нужен способ идентифицировать их, чтобы я мог получить их из резервной копии.


Вы имеете в виду файлы с нулями?
— Рахул Патил

2
Я думаю, что это о NULL символов, а не числовые нули.
— gertvdijk

10
Давайте сделаем шаг назад. Каждые несколько дней, когда ваш ноутбук зависает? Почему мы не пытаемся исправить эту , реальную проблему здесь?
— D_Bye

2
@D_Bye, это хорошая идея, но пока она не зашла слишком далеко: [ unix.stackexchange.com/questions/57894/…
— Адам

1
Рассматривали ли вы -vвариант grep: отфильтровать все файлы с байтами от 1 до 255.
— ctrl-alt-delor

Ответы:


10

grepДля символов ␀ вы можете использовать режим регулярных выражений Perl:

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

Так что вы можете использовать это:

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

Я получаю неожиданные результаты, используя GNU grep 2.5.4. Независимо от того, что я использую --binary-files=textили --binary-files=binary, это дает trueрезультат для всех непустых значений данных, например. "\0\0", "\0x\0", "abcd"... Точный код я использовал: 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
Я сейчас еще попробовал GNU grep) 2.10. Эта более поздняя версия дает ожидаемые результаты ... так что запоздалый +1
— Peter.O 21.12.12

1
Сбой на файл, созданный с printf '\0\n\0\0\n\n' > fileили printf '\n' > fileдля этого имеет значение.
— Стефан Шазелас

2
@ StéphaneChazelas OP сказал "игнорируя символы конца строки". Таким образом , любой файл , состоящий только \0и \nсимволов (даже нуль либо) будет матч.
— l0b0

6

Я согласен с тем, что D_Bye говорит о нахождении корня проблемы.

В любом случае, чтобы проверить, содержит ли файл только \0и / или \nвы можете использовать tr:

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

Который возвращает 0 для пустых / новых строк и пустых файлов.


2
tr -d '\0\n'решает проблему с новой строкой, которая затем оставляет проблему (?) пустых файлов, перечисленных в выводе ... Он обрабатывает каждый байт каждого файла, хотя (что может или не может быть проблемой) +1
— Peter.O

@ Peter.O: я пропустил требование перевода строки, спасибо. Это решение не очень оптимизировано, и если оно будет работать на большом количестве данных, было бы лучше с решением, которое будет продолжаться при поиске несоответствующих байтов.
— Тор

Это работает очень хорошо. В моем случае мне нужно было только исключить файлы нулевой длины. Спасибо.
— Адам Рычковски

1
Это также, однако, будет считать файлы с символами новой строки как «пустые».
— Крис Даун

1
@ChrisDown: я ясно дал понять текст ответа. Не ясно, что ОП хочет сделать с файлами только для новой строки.
— Тор

5

Я подозреваю, что эти файлы редки, то есть им не выделено дисковое пространство, они просто указывают размер файла ( duсообщит 0 для них).

В этом случае, с помощью GNU find, вы можете сделать это (при условии, что путь к файлу не содержит символов новой строки):

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

Хорошая точка зрения. Я никогда не думал об этом. Я постараюсь. Использование duне позволит поцарапать содержимое каждого файла в файловой системе, поэтому вся процедура не займет более 30 минут.
— Адам Рычковски

(и printf %bвыше сообщает, что duбы сообщить)
— Стефан Chazelas

Я хотел бы изменить , -size +0чтобы -size +1таким образом файлы нулевой длины исключены из результатов. Также файлы, содержащиеся \nв их пути, вызовут проблемы для этой команды.
— Тайсон

@Tyson -size +0для размеров, строго превышающих 0. -size +1будет для размеров, строго превышающих 512. Ограничение новой строки уже упоминалось.
— Стефан Шазелас

@ StéphaneChazelas Спасибо, что просветили меня относительно -size +1, вы действительно правы. Я исправил свой ответ. :-)
— Тайсон

4

Вот небольшая программа на Python, которая может это сделать:

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))

И в действии:

$ 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

Вы можете проверить несколько файлов с помощью ФАЙНД -exec, xargsГНУ parallelи аналогичные программы. В качестве альтернативы, это напечатает имена файлов, с которыми нужно иметь дело:

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

Имейте в виду, что если вы собираетесь передать вывод этого в другую программу, имена файлов могут содержать символы новой строки, поэтому вы должны разграничить его по-разному (соответственно, с помощью \0).

Если у вас много файлов, было бы лучше использовать опцию для параллельной обработки, так как это читает только один файл за раз.


2
Осторожно, файлы с нулевой длиной (например /etc/nologin, ~/.hushlogin, .nomedia, ...) которые не распознали этот ответ.
— Тайсон

@ Тайсон Спасибо за указание на это! Я только что исправил это.
— Крис Даун

3

Найдите файлы, которые содержат только нулевые символы '\ 0' и символы новой строки '\ n'.
Параметр qin sed приводит к немедленному завершению поиска по каждому файлу при обнаружении в строке любого ненулевого символа.

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

Сделать тестовые файлы

> 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

выход

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

Либо -print0аргумент отсутствует, findлибо IFS=часть испорчена. Каков был предполагаемый разделитель?
— Тайсон

3

Это один вкладыш является наиболее эффективным способом , чтобы найти 100% файлы с помощью GNU последовательности нулевых find, xargsи grep(предполагая , что последний построено с поддержкой PCRE):

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

Преимущества этого метода перед другими ответами:

  • не разреженные файлы включены в поиск.
  • нечитаемые файлы не передаются в grep, избегая Permission deniedпредупреждений.
  • grepпрекратит чтение данных из файлов после нахождения любого ненулевого байта ( LC_ALL=Cиспользуется для проверки того, что каждый байт интерпретируется как символ ).
  • пустые файлы (ноль байтов) не включены в результаты.
  • меньше grepпроцессов эффективно проверяют несколько файлов.
  • пути, содержащие символы новой строки или начинающиеся с -, обрабатываются правильно.
  • работает на большинстве встроенных систем, в которых отсутствует Python / Perl.

Передача -Zопции grepи использование xargs -r0 ...позволяет выполнять дальнейшие действия со 100% файлами nul (например, очистка):

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

Я также рекомендую использовать findпараметры, -Pчтобы избежать следующих символических ссылок и -xdevизбежать обхода файловых систем (например: удаленные монтирования, деревья устройств, монтирование связывания и т. Д.).

Для игнорирования символа (ов) конца строки должен работать следующий вариант (хотя я не думаю, что это хорошая идея):

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

Собираем все вместе, включая удаление ненужных файлов (100% нуль / символы новой строки), чтобы предотвратить их резервное копирование:

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

Я не рекомендую включать пустые файлы (нулевые байты), они часто существуют для очень определенных целей .


Быть самым быстрым из множества альтернатив - смелое утверждение. Я отмечу ваш ответ как принятый, если вы добавите тест :-)
— Адам Рычковски

Такой тест будет зависеть от многих факторов, включая производительность различных дисковых подсистем.
— Тайсон

Конечно, но все лучше, чем ничего. Различные подходы по-разному оптимизируют использование процессора, поэтому имеет смысл сравнить его с SSD или даже с кэшированными файлами. Возьмите машину, на которой вы сейчас работаете, напишите одно предложение о том, что это такое (тип процессора, количество ядер, объем оперативной памяти, тип жесткого диска), опишите набор файлов (например, исходный клон ядра + файл объемом 1 ГБ, заполненный \0дырой в 900 МБ) и настоящее время результатов. Если вы сделаете это таким образом, чтобы эталон был убедительным для вас, он, скорее всего, будет убедительным для всех нас
— Адам Рычковски,

«большинство встроенных систем» не имеют утилит GNU. Скорее занятые.
— Стефан Шазелас

-Pпо умолчанию в find. Если вы хотите перейти по символическим ссылкам, это -L/ -follow. Вы обнаружите, что POSIX даже не указывает эту опцию find(хотя POSIX и ввел эти параметры -P / -H / -L для нескольких команд).
— Стефан Шазелас

0

Для использования GNU sed вы можете использовать -zопцию, которая определяет строку как строки с нулевым символом в конце, а также сопоставляет и удаляет пустые строки следующим образом:

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

Команда head inbetween - это просто оптимизация.


-1

питон

Отдельный файл

Определите псевдоним:

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

Проверь это:

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

Несколько файлов

Найти все двоичные файлы рекурсивно:

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 {}" \;

Чтобы найти все недвоичные файлы, измените &&на ||.


1
Вопрос, заданный для идентификации файлов, содержащих только нулевые символы (игнорируя символы новой строки), приведенный здесь код Python идентифицирует файлы, содержащие любые нулевые символы.
— Тайсон
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.