Как найти файлы со 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
l0b0
источник
Я получаю неожиданные результаты, используя 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
Peter.O
источник
Либо -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 - это просто оптимизация.

mxmlnkn
источник
-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 {}" \;

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

kenorb
источник
1
Вопрос, заданный для идентификации файлов, содержащих только нулевые символы (игнорируя символы новой строки), приведенный здесь код Python идентифицирует файлы, содержащие любые нулевые символы.
Тайсон