Применение математической функции к каждой строке в большом файле

find . -type f -name "*.mp3" -exec dirname {} \; | uniq | wc -l
2
11.11.2016, 15:55
3 ответа

Простой подход: Используйте ex для изменения строк и передайте весь буфер (измененный файл) через bc. Затем выведите модифицированную версию.

printf '%s\n' '%s:.*:&/8640000+719529:' 0a scale=10 . '%!bc' %p 'q!' | ex file.txt

Вывод на ваш образец файла:

735235.0000000000
735235.0000001157
735235.0000002314
735235.0000003472
735235.0000004629

Или сохранить изменения, а не просто распечатать их:

printf '%s\n' '%s:.*:&/8640000+719529:' 0a scale=10 . '%!bc' x | ex file.txt

Пояснение:

Чтобы увидеть, какие команды передаются в ex, запустите команду printf саму по себе:

$ printf '%s\n' '%s:.*:&/8640000+719529:' 0a scale=10 . '%!bc' %p 'q!'
%s:.*:&/8640000+719529:
0a
scale=10
.
%!bc
%p
q!

Давайте теперь разобьем их на команды ex. Первая из них довольно сложная, поэтому я специально отформатирую объяснение:

%s:.*:&/8640000+719529:
%  - For every line of the buffer (file)
 s  - Run a substitute command
  :  - Using ':' as the regex delimiter
   .*  - Match each entire line
     :  - and replace with
      &  - The entire line, followed by
       /8640000+719529  - this text
                      :  - End command

0a означает "добавить текст после строки 0", то есть в начало буфера (файла).

Текст scale=10 - это буквальный текст для добавления.

Текст в строке сама по себе завершает команду "append".

Команда %!bc передает содержимое всего буфера в качестве стандартного ввода внешней команде bc и заменяет весь буфер полученным выводом.

Команда %p означает печать всего буфера (на стандартный вывод).

q! означает выход без сохранения изменений.


Если у вас очень, очень большой файл, в десятки миллионов строк, это, очевидно, вызывает проблемы. Я исследовал возможные решения для этого, используя ex, и есть несколько способов сделать это, но в итоге я отказался от этого подхода в пользу гораздо, гораздо более простого, который по-прежнему использует только POSIX определенные инструменты.

Используйте split для разбиения вашего файла на фрагменты, затем выполните ранее указанную команду для каждого фрагмента и cat выведите все вместе:

split -l 1000000 -a 3 file.txt myprefix.
for f in myprefix.???; do
  printf '%s\n' '%s:.*:&/8640000+719529:' 0a scale=10 . '%!bc' %p 'q!' |
    ex "$f"
done > myoutputfile.txt
rm myprefix.???

Команда split используется здесь для разбиения file.txt на фрагменты, каждый длиной в четный миллион строк (остаток, конечно, тоже помещается в файл). Поскольку указано -a 3, суффикс у кусков будет длиной 3 символа. myprefix.aaa, myprefix.aab, etc.

Затем каждый файл может быть обработан ex по отдельности, без необходимости сохранять изменения, так как мы просто перенаправим вывод всего цикла в myoutputfile.txt (а затем удалим файлы чанков, для аккуратности).

2
27.01.2020, 21:50

Выполнение этого в оболочке будет очень медленным.

$ awk '{printf "%.10f\n", (($1/(100*86400))+719529)}' filename
735235.0000000000
735235.0000001157
735235.0000002314
735235.0000003473
735235.0000004630

Как видно из последней записи, результаты округления будут немного другими.

2
27.01.2020, 21:50

Известно, что у ракушек очень низкая скорость обработки информации.
То, о чем вы просите, можно реализовать в оболочке следующим образом:

#!/bin/bash
while read line; do
    bc <<<"scale=10;($line/(100*86400))+719529"
done <datafile

Для обработки 1000 строк требуется около 1,1 секунды.
Вся партия из 8,640 миллионов должна занять около 2 часов 41 минуты.

Кроме того, числовые результаты от bc неправильно округляются.
Пять строк из вашего примера дают такие значения:

735235.0000000000
735235.0000001157
735235.0000002314
735235.0000003472
735235.0000004629

Давайте изменим точность на 20, чтобы увидеть больше цифр:

735235.00000000000000000000
735235.00000011574074074074
735235.00000023148148148148
735235.00000034722222222222
735235.00000046296296296296

Например, третья строка, которая заканчивается на 2314, неправильно округлена, следующая цифра после 4 - 8, ее следовало округлить до 5.

AWK

Возможно, мы найдем более быстрое решение с помощью awk. Реализация того, о чем вы просите, в awk будет выглядеть так:

$ awk '{printf ("%.10f\n",($0/(100*86400))+719529)}' datafile

735235.0000000000
735235.0000001157
735235.0000002314
735235.0000003473
735235.0000004630

Для обработки 1000 строк требуется всего 0.006 (6 миллисекунд). Все 8,64 миллиона строк должны быть обработаны примерно за 50 секунд.
Но awk уже превышает свой диапазон точности. По умолчанию он использует 64-битное представление значений с плавающей точкой. Это представление имеет точность около 15 десятичных цифр. Результаты ваших данных имеют целочисленную часть из 6 цифр, десятичная часть может быть оценена как правильная только до 8-й цифры.
На самом деле, если мы попытаемся увеличить количество цифр:

awk '{printf ("%.20f\n",($0/(100*86400))+719529)}' datafile

мы получим просто шум:

735235.00000000000000000000
735235.00000011571682989597
735235.00000023143365979195
735235.00000034726690500975
735235.00000046298373490572

Сравните с более точными результатами bc:

735235.00000000000000000000
735235.00000000000000000000

735235.00000011571682989597
735235.00000011574074074074

735235.00000023143365979195
735235.00000023148148148148

735235.00000034726690500975
735235.00000034722222222222

735235.00000046298373490572
735235.00000046296296296296

Чтобы действительно решить эту проблему, нам нужен более точный awk.

Многопрецизионный AWK

Если вы используете GNU awk (далее я буду называть его gawk) и он был скомпилирован с MPFR (библиотека многократной точности с плавающей точкой), вы можете получить большую точность.

Убедитесь, что в вашем awk есть эта библиотека (просто спросите ее версию):

$ awk --version
GNU Awk 4.1.3, API: 1.1 (GNU MPFR 3.1.5, GNU MP 6.1.1)
Copyright (C) 1989, 1991-2015 Free Software Foundation.

И измените команду awk, чтобы использовать доступную точность:

gawk -M -v PREC=100 '{printf ("%.20f\n",($0/(100*86400))+719529)}' datafile

735235.00000000000000000000
735235.00000011574074074074
735235.00000023148148148148
735235.00000034722222222222
735235.00000046296296296296

Результаты те же, что и при использовании bc высокой точности.
В этом случае мы получаем скорость awk и точность bc.

Последняя команда для 10 десятичных цифр, которые вы просите, такова:

gawk -M -v PREC=100 '{printf ("%.10f\n",($0/(100*86400))+719529)}' datafile

735235.0000000000
735235.0000001157
735235.0000002315
735235.0000003472
735235.0000004630

Все значения правильно округлены.

6
27.01.2020, 21:50

Теги

Похожие вопросы