У меня странная проблема с большими файлами и bash
. Это контекст:
- У меня большой файл: 75G и 400,000,000+ строк (это файл журнала, мой плохой, я позволил ему расти).
- Первые 10 символов каждой строки представляют собой метки времени в формате ГГГГ-ММ-ДД.
- Я хочу разделить этот файл: один файл в день.
Я попытался с помощью следующего сценария, который не работал. Мой вопрос о том, что этот скрипт не работает, а не альтернативные решения .
while read line; do
new_file=${line:0:10}_file.log
echo "$line" >> $new_file
done < file.log
После отладки я нашел проблему в new_file
переменной. Этот скрипт:
while read line; do
new_file=${line:0:10}_file.log
echo $new_file
done < file.log | uniq -c
дает результат ниже (я ставлю x
es, чтобы сохранить конфиденциальность данных, другие символы являются реальными). Обратите внимание на dh
и более короткие строки:
...
27402 2011-xx-x4
27262 2011-xx-x5
22514 2011-xx-x6
17908 2011-xx-x7
...
3227382 2011-xx-x9
4474604 2011-xx-x0
1557680 2011-xx-x1
1 2011-xx-x2
3 2011-xx-x1
...
12 2011-xx-x1
1 2011-xx-dh
1 2011-xx-x1
1 208--
1 2011-xx-x1
1 2011-xx-dh
1 2011-xx-x1
...
Это не проблема в формате моего файла . Скрипт cut -c 1-10 file.log | uniq -c
выдает только допустимые метки времени. Интересно, что часть вышеприведенного вывода делается с cut ... | uniq -c
:
3227382 2011-xx-x9
4474604 2011-xx-x0
5722027 2011-xx-x1
Мы видим, что после подсчета uniq 4474604
мой первоначальный скрипт не удался.
Я достиг предела в bash, которого я не знаю, обнаружил ли я ошибку в bash (она кажется маловероятной), или я сделал что-то не так?
Обновление :
Проблема возникает после прочтения 2G файла. Это швы read
и перенаправление не любят большие файлы, чем 2G. Но все еще в поисках более точного объяснения.
Обновление 2 :
Это определенно выглядит как ошибка. Это может быть воспроизведено с:
yes "0123456789abcdefghijklmnopqrs" | head -n 100000000 > file
while read line; do file=${line:0:10}; echo $file; done < file | uniq -c
но это прекрасно работает в качестве обходного пути (кажется, что я нашел полезное применение cat
):
cat file | while read line; do file=${line:0:10}; echo $file; done | uniq -c
Ошибка была подана в GNU и Debian. Подвержены уязвимости версии bash
4.1.5 в Debian Squeeze 6.0.2 и 6.0.4.
echo ${BASH_VERSINFO[@]}
4 1 5 1 release x86_64-pc-linux-gnu
Update3:
Благодаря Андреасу Швабу, который быстро отреагировал на мое сообщение об ошибке, этот патч является решением этой проблемы. Файл, на который повлияли, - это, lib/sh/zread.c
как Жиль указал раньше:
diff --git a/lib/sh/zread.c b/lib/sh/zread.c index 0fd1199..3731a41 100644
--- a/lib/sh/zread.c
+++ b/lib/sh/zread.c @@ -161,7 +161,7 @@ zsyncfd (fd)
int fd; { off_t off;
- int r;
+ off_t r;
off = lused - lind; r = 0;
r
Переменная используется для хранения возвращаемого значения lseek
. As lseek
возвращает смещение от начала файла, когда оно превышает 2 ГБ, int
значение является отрицательным, что приводит if (r >= 0)
к сбою теста там, где он должен был быть успешным.
read
утверждения в bash.Ответы:
Вы нашли ошибку в Bash, в некотором роде. Это известная ошибка с известным исправлением.
Программы представляют смещение в файле как переменную в некотором целочисленном типе с конечным размером. В старые времена все использовали
int
практически все, иint
тип был ограничен 32 битами, включая знаковый бит, поэтому он мог хранить значения от -2147483648 до 2147483647. В настоящее время существуют разные имена типов для разных вещей , в том числеoff_t
для смещение в файле.По умолчанию
off_t
это 32-битный тип на 32-битной платформе (допускает до 2 ГБ) и 64-битный тип на 64-битной платформе (допускает до 8EB). Однако обычно программы компилируются с опцией LARGEFILE, которая переключает типoff_t
на 64- битную ширину и заставляет программу вызывать подходящие реализации таких функций, какlseek
.Похоже, что вы используете bash на 32-битной платформе, а ваш двоичный файл bash не скомпилирован с поддержкой больших файлов. Теперь, когда вы читаете строку из обычного файла, bash использует внутренний буфер для чтения символов в пакетах для повышения производительности (подробнее см. Источник в
builtins/read.def
). Когда строка завершена, bash вызываетlseek
перемотку смещения файла обратно к позиции конца строки, если какая-то другая программа заботится о позиции в этом файле. Вызовlseek
происходит вzsyncfc
функции вlib/sh/zread.c
.Я не читал источник в деталях, но я предполагаю, что что-то не происходит гладко в точке перехода, когда абсолютное смещение отрицательно. Таким образом, bash завершает чтение с неправильными смещениями, когда заполняет свой буфер, после того, как он прошел отметку 2 ГБ.
Если мой вывод неверен и ваш bash фактически работает на 64-битной платформе или скомпилирован с поддержкой больших файлов, это определенно ошибка. Пожалуйста, сообщите об этом в ваш дистрибутив или апстрим .
В любом случае, оболочка не является подходящим инструментом для обработки таких больших файлов. Это будет медленно. Если возможно, используйте sed, иначе awk.
источник
Я не знаю о неправильном, но это, конечно, запутанно. Если ваши строки ввода выглядят так:
Тогда действительно нет причин для этого:
Вы выполняете много подстрок, чтобы получить что-то, что выглядит ... именно так, как это уже выглядит в файле. Как насчет этого?
Это просто захватывает первые 10 символов из строки. Вы также можете отказаться от
bash
всего и просто использоватьawk
:Это захватывает дату в
$1
(первый разделенный пробелами столбец в каждой строке) и использует ее для генерации имени файла.Обратите внимание, что в ваших файлах могут быть фиктивные строки журнала. То есть проблема может быть связана с вводом, а не с вашим сценарием. Вы можете расширить
awk
скрипт, чтобы пометить фиктивные строки следующим образом:Это записывает строки, соответствующие
YYYY-MM-DD
вашим файлам журнала, и помечает строки, которые не начинаются с отметки времени на stdout.источник
cut -c 1-10 file.log | uniq -c
дает мне ожидаемый результат. Я использую,${line:0:4}-${line:5:2}-${line:8:2}
потому что я помещу файл в каталог${line:0:4}/${line:5:2}/${line:8:2}
, и я упростил проблему (я обновлю формулировку проблемы). Я знаю,awk
может помочь мне здесь, но я столкнулся с другими проблемами, используя его. Я хочу понять проблемуbash
, а не найти альтернативные решения.cut
утверждение, которое работает. Поскольку я хочу сравнивать яблоки с яблоками, а не с апельсинами, мне нужно сделать вещи максимально похожими.Похоже, что вы хотите сделать, это:
close
Сохраняет таблицы открытых файлов от заполнения.источник