Як знайти файли зі 100% NUL символами у своєму вмісті?


16

Яка команда командного рядка Linux може ідентифікувати такі файли?

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

Передумови: Кожні кілька днів, коли мій ноутбук замерзає, мій розділ btrfs втрачає інформацію: файли, відкриті для запису, змінюють їх вміст на нулі (розмір файла залишається більш-менш недоторканим). Я використовую синхронізацію, і не хочу, щоб ці підроблені файли поширювалися: мені потрібен спосіб їх ідентифікації, щоб я міг захопити їх із резервного копіювання.


ви маєте на увазі файли з числовими нулями в ньому?
Рахул Патіль

2
Я думаю, що йдеться про NULL символів, а не числових нулів.
gertvdijk

10
Зробимо тут крок назад. Кожні кілька днів, коли ваш ноутбук замерзає? Чому ми не намагаємося виправити це , справжня проблема тут?
D_Bye

2
@D_Bye це гарна ідея, але поки що вона не надто зайшла: [ unix.stackexchange.com/questions/57894/…
Адам Ріцковскі

1
чи розглядали ви -vможливість вимкнути: відфільтруйте всі файли, що мають байт від 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
Петер.

1
Не вдалося створити файл, створений за допомогою printf '\0\n\0\0\n\n' > fileабо printf '\n' > fileдля цього.
Stéphane Chazelas

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 . -type f -size +0 -printf '%b:%p\n' | grep '^0:' | cut -d: -f2-

Гарна думка. Я ніколи про це не думав. Я постараюсь. Використання duне дозволить подряпати вміст кожного файлу у файловій системі, тому на повну процедуру не знадобиться 30+ хвилин.
Адам Річковський

printf %bвище повідомляє про що duповідомлятиметься)
Стефан Шазелас

Я хотів би змінити , -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, ...) які НЕ розпізнали цей відповідь.
Тайсон

@Tyson Дякую, що вказали на це! Я щойно це виправив.
Кріс Даун

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% нулями (наприклад: очищення):

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

Для використання sed GNU ви можете скористатись -zопцією, яка визначає рядок як рядки з нульовим завершенням та збігаються з порожніми рядками та видаляють їх так:

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

Головна команда між ними - це лише оптимізація.


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