Що змушує grep вважати файл двійковим?


185

У мене в ящику є декілька скидів у базу даних із системи Windows. Вони є текстовими файлами. Я використовую cygwin, щоб проглядати їх. Вони здаються простими текстовими файлами; Я відкриваю їх текстовими редакторами, такими як блокнот та текстовий блок, і вони виглядають розбірливо. Однак, коли я запускаю греп на них, це скаже binary file foo.txt matches.

Я помітив, що файли містять деякі NULсимволи ascii , які, на мою думку, є артефактами з дампів бази даних.

Отже, що змушує греп вважати ці файли бінарними? NULХарактер? Чи є прапор у файловій системі? Що потрібно змінити, щоб отримати греп, щоб показати мені відповідність ліній?


2
--null-dataможе бути корисним, якщо NULє роздільником.
Стів-о

Відповіді:


125

Якщо NULв файлі де-небудь є символ, grep вважатиме його двійковим файлом.

Можна вирішити подібне рішення, щоб cat file | tr -d '\000' | yourgrepспочатку усунути всі нулі, а потім шукати файли.


149
... або використовувати -a/ --text, принаймні, з GNU grep.
дероберт

1
@derobert: насправді в деяких (старих) системах grep бачить рядки, але його результат спочатку обріже кожну відповідну лінію NUL(ймовірно, тому що вона називає C printf C і надає їй відповідну лінію?). У такій системі a grep cmd .sh_historyповернеться стільки порожніх рядків, скільки є рядків, що відповідають 'cmd', оскільки кожен рядок sh_history має певний формат з a NULна початку кожного рядка. (але ваш коментар "принаймні щодо GNU grep", мабуть, справдиться. У мене зараз немає під рукою тестування, але я сподіваюся, що вони прекрасно впораються з цим)
Олів'є Дулак,

4
Чи є єдиним критерієм наявність символу NUL? Я сумніваюся в цьому. Це, мабуть, розумніше від цього. Я б здогадувався про все, що потрапляє за межі діапазону Ascii 32-126, але нам доведеться переконатися у вихідному коді.
Майкл Мартінес

2
Моя інформація була зі сторінки man у конкретному екземплярі grep. Ваш коментар щодо впровадження дійсний, джерела козових документів.
bbaja42

2
У мене був файл, який grepна cygwin вважався двійковим, тому що він мав тривалий тире (0x96) замість звичайного дефісу ASCII / мінус (0x2d). Я думаю, що ця відповідь вирішила питання ОП, але, здається, воно є неповним.
cp.engr


21

Ви можете використовувати stringsутиліту для вилучення текстового вмісту з будь-якого файлу , а потім по конвеєру через grep, наприклад: strings file | grep pattern.


2
Ідеально підходить для отримання файлів журналів, які можуть бути частково пошкоджені
Ханнес Р.

так, іноді трапляються і двійкові змішані журнали. Це добре.
sdkks

13

GNU grep 2,24 RTFS

Висновок: лише 2 та 2 справи:

  • NUL, напр printf 'a\0' | grep 'a'

  • помилка кодування відповідно до C99 mbrlen(), наприклад:

    export LC_CTYPE='en_US.UTF-8'
    printf 'a\x80' | grep 'a'
    

    тому що \x80не може бути першим байтом точки Unicode UTF-8 : UTF-8 - Опис | en.wikipedia.org

Крім того, як згадував Стефан Шазелас. Що змушує греп вважати файл двійковим? | У Unix та Linux Stack Exchange ці перевірки проводяться лише до першого зчитування буфера довжиною TODO.

Лише до першого зчитування буфера

Отже, якщо в середині дуже великого файлу трапляється помилка NUL або кодування, вона все одно може бути зірвана.

Я думаю, це з міркувань продуктивності.

Наприклад: це друкує рядок:

printf '%10000000s\n\x80a' | grep 'a'

але це не так:

printf '%10s\n\x80a' | grep 'a'

Фактичний розмір буфера залежить від того, як читається файл. Наприклад, порівняйте:

export LC_CTYPE='en_US.UTF-8'
(printf '\n\x80a') | grep 'a'
(printf '\n'; sleep 1; printf '\x80a') | grep 'a'

З sleep, перший рядок передається в grep, навіть якщо він лише 1 байт, оскільки процес переходить у режим сну, а другий читання не перевіряє, чи файл є бінарним.

RTFS

git clone git://git.savannah.gnu.org/grep.git 
cd grep
git checkout v2.24

Знайдіть, де закодоване повідомлення про помилку stderr:

git grep 'Binary file'

Веде нас до /src/grep.c:

if (!out_quiet && (encoding_error_output
                    || (0 <= nlines_first_null && nlines_first_null < nlines)))
    {
    printf (_("Binary file %s matches\n"), filename);

Якщо ці змінні були добре названі, ми в основному дійшли висновку.

encoding_error_output

Швидке схвалення encoding_error_outputпоказує, що єдиний шлях коду, який може змінити його, проходить buf_has_encoding_errors:

clen = mbrlen (p, buf + size - p, &mbs);
if ((size_t) -2 <= clen)
  return true;

то просто man mbrlen.

nlines_first_null і nlines

Ініціалізовано як:

intmax_t nlines_first_null = -1;
nlines = 0;

тому коли знайдено нуль 0 <= nlines_first_nullстає істинним.

TODO, коли коли- nlines_first_null < nlinesнебудь може бути помилковим? Я лінивий.

POSIX

Grep не визначає бінарні параметри - пошук у файлі за шаблоном | pubs.opengroup.org , і GNU grep не документує його, тому RTFS - єдиний спосіб.


1
Вражаюча експлікація!
користувач394

2
Зауважте, що перевірка на дійсність UTF-8 відбувається лише в локалях UTF-8. Також зауважте, що перевірка виконується лише на першому буфері, прочитаному з файлу, який для звичайного файлу здається 32768 байтом у моїй системі, але для труби або сокета може бути розміром як один байт. Порівняйте (printf '\n\0y') | grep yз (printf '\n'; sleep 1; printf '\0y') | grep y, наприклад.
Стефан Шазелас

@ StéphaneChazelas "Зауважте, що перевірка на дійсність UTF-8 відбувається лише в локалях UTF-8": ви маєте на увазі export LC_CTYPE='en_US.UTF-8'як у моєму прикладі чи щось інше? Buf прочитав: дивовижний приклад, доданий для відповіді. Ви, очевидно, читали джерело більше, ніж я, нагадує мені ті хакерські коани "Студент був просвітлений" :-)
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

1
Я також не розглядав великі деталі, але зовсім недавно
Стефан Шазелас

1
@CiroSantilli 巴拿馬 文件 六四 事件 法轮功 Яку версію GNU grep ви протестували?
jrw32982

6

Один з моїх текстових файлів несподівано грепом сприймається як двійковий:

$ file foo.txt
foo.txt: ISO-8859 text

Рішенням було перетворити його за допомогою iconv:

iconv -t UTF-8 -f ISO-8859-1 foo.txt > foo_new.txt

1
Це сталося і зі мною. Зокрема, причиною став нерозривний простір, кодований ISO-8859-1, який мені довелося замінити звичайним пробілом, щоб отримати греп для пошуку у файлі.
Gallaecio

4
grep 2.21 розглядає текстові файли ISO-8859 так, ніби вони є двійковими, додайте експорт LC_ALL = C перед командою grep.
netawater

@netawater Дякую! Так, наприклад, якщо у текстовому файлі є щось на зразок Мюллера. Це 0xFCшістнадцять, тому поза межами діапазону греп очікував би utf8 (до 0x7F). Перевірте з printf 'a \ x7F' | grep 'a', як описано Циро вище.
Енн ван Россум

5

Файл /etc/magicабо /usr/share/misc/magicмає список послідовностей, які команда fileвикористовує для визначення типу файлу.

Зауважте, що бінарний файл може бути просто резервним рішенням. Іноді файли із дивним кодуванням також вважаються бінарними.

grepв Linux є кілька варіантів обробки двійкових файлів, таких як --binary-filesабо-U / --binary


Точніше, помилка кодування відповідно до C99 mbrlen(). Приклад та інтерпретація джерел за адресою: unix.stackexchange.com/a/276028/32558
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

2

Один мій студент мав цю проблему. Існує помилка в grepв Cygwin. Якщо у файлі є символи, що не належать Ascii, grepі egrepрозглядайте їх як двійкові.


Це звучить як особливість, а не помилка. Тим більше, що існує варіант командного рядка для управління ним (-a / --text)
Буде Шеппард

2

Насправді, відповідаючи на питання "Що змушує" grep "вважати файл двійковим?", Ви можете використовувати iconv:

$ iconv < myfile.java
iconv: (stdin):267:70: cannot convert

У моєму випадку були іспанські символи, які правильно відображалися в текстових редакторах, але греп вважав їх бінарними; iconvвисновок вказував мені на рядки та номери стовпців цих символів

Що стосується NULсимволів, iconvвони вважатимуть їх нормальними та не будуть друкувати такого виходу, тому цей метод не підходить


1

У мене була така ж проблема. Я vi -b [filename]бачив додані символи. Я знайшов контрольні символи ^@і ^M. Потім введіть тип vi, :1,$s/^@//gщоб видалити ^@символи. Повторіть цю команду для ^M.

Попередження: Щоб отримати "сині" контрольні символи, натисніть Ctrl+, vпотім Ctrl+ Mабо Ctrl+ @. Потім збережіть та вийдіть із vi.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.