Прочитайте середину большого файла

19

У меня есть файл 1 ТБ. Я хотел бы прочитать от байта 12345678901 до байта 19876543212 и поставить его на стандартный вывод на машине с 100 МБ ОЗУ.

Я могу легко написать Perl-скрипт, который делает это. sysread обеспечивает 700 МБ / с (что нормально), но syswrite обеспечивает только 30 МБ / с. Я хотел бы что-то более эффективное, желательно что-то, что устанавливается в каждой системе Unix и может доставлять порядка 1 ГБ / с.

Моя первая идея:

dd if=1tb skip=12345678901 bs=1 count=$((19876543212-12345678901))

Но это не эффективно.

Редактировать:

Я понятия не имею, как я измерил syswrite неправильно. Это обеспечивает 3,5 ГБ / с:

perl -e 'sysseek(STDIN,shift,0) || die; $left = shift; \
         while($read = sysread(STDIN,$buf, ($left > 32768 ? 32768 : $left))){ \
            $left -= $read; syswrite(STDOUT,$buf);
         }' 12345678901 $((19876543212-12345678901)) < bigfile

и избегает yes | dd bs=1024k count=10 | wcкошмара.

Оле Танге
источник
Ваша команда сbs=1M iflag=skip_bytes,count_bytes
frostschutz

Ответы:

21

Это медленно из-за небольшого размера блока. Используя недавнюю версию GNU dd( coreutils v8.16 + ), самый простой способ - использовать параметры skip_bytesи count_bytes:

in_file=1tb

start=12345678901
end=19876543212
block_size=4096

copy_size=$(( $end - $start ))

dd if="$in_file" iflag=skip_bytes,count_bytes,fullblock bs="$block_size" \
  skip="$start" count="$copy_size"

Обновить

fullblockопция добавлена ​​выше согласно ответу @Gilles . Сначала я подумал, что это может подразумеваться count_bytes, но это не так.

Упомянутые проблемы являются потенциальной проблемой, приведенной ниже. Если ddвызовы чтения / записи прерваны по какой-либо причине, данные будут потеряны. В большинстве случаев это маловероятно (шансы несколько снижаются, поскольку мы читаем из файла, а не из канала).


Использование ddбез параметров skip_bytesи count_bytesболее сложно:

in_file=1tb

start=12345678901
end=19876543212
block_size=4096

copy_full_size=$(( $end - $start ))
copy1_size=$(( $block_size - ($start % $block_size) ))
copy2_start=$(( $start + $copy1_size ))
copy2_skip=$(( $copy2_start / $block_size ))
copy2_blocks=$(( ($end - $copy2_start) / $block_size ))
copy3_start=$(( ($copy2_skip + $copy2_blocks) * $block_size ))
copy3_size=$(( $end - $copy3_start ))

{
  dd if="$in_file" bs=1 skip="$start" count="$copy1_size"
  dd if="$in_file" bs="$block_size" skip="$copy2_skip" count="$copy2_blocks"
  dd if="$in_file" bs=1 skip="$copy3_start" count="$copy3_size"
}

Вы также можете поэкспериментировать с различными размерами блоков, но выигрыш не будет очень значительным. Смотрите - Есть ли способ определить оптимальное значение параметра bs для dd?

Graeme
источник
@ Грэм не провалится второй метод, если bsне является фактором skip?
Стивен Пенни
@ StevenPenny, не уверен, что вы получаете, но skipэто количество блоков, а не байтов. Может быть , вы запутались , так как skip_bytesиспользуется в первом примере значением skip является в байтах там?
Грэм
Ваш bsесть 4,096, которые означают , что вы не можете более точно показывать , что 4,096байты
Стивен Penny
1
@StevenPenny, вот почему есть три разных прогона ddс первым и последним использованием bs=1, чтобы скопировать данные, которые не начинаются или не заканчиваются при выравнивании блоков.
Грэм
6

bs=1говорит ddчитать и писать по одному байту за раз. Для каждого вызова readи writeобработки накладных расходов , что делает это медленным. Используйте больший размер блока для достойной производительности.

При копировании весь файл, по крайней мере под Linux, я обнаружил , что cpи catбыстрее , чемdd , даже если вы указываете большой размер блока.

Чтобы скопировать только часть файла, вы можете перейти tailв head. Это требует GNU coreutils или какой-либо другой реализации, которая head -cдолжна копировать указанное количество байтов ( tail -cв POSIX, но head -cнет). Быстрый тест на Linux показывает, что это медленнее, чем dd, вероятно, из-за конвейера.

tail -c $((2345678901+1)) | head -c $((19876543212-2345678901))

Проблема в ddтом, что он ненадежен: он может копировать частичные данные . Насколько я знаю, ddбезопасно читать и записывать в обычный файл - см. Когда dd подходит для копирования данных? (или, когда read () и write () частичны) - но только до тех пор, пока это не прервано сигналом . С GNU coreutils вы можете использовать fullblockфлаг, но он не переносим.

Другая проблема ddзаключается в том, что может быть трудно найти работающий счетчик блоков, поскольку количество пропущенных байтов и количество переданных байтов должны быть кратными размеру блока. Вы можете использовать несколько вызовов dd: один для копирования первого частичного блока, один для копирования основной части выровненных блоков и один для копирования последнего частичного блока - см . Ответ Грэма для фрагмента оболочки. Но не забывайте, что когда вы запускаете скрипт, если вы не используете fullblockфлаг, вам нужно молиться, ddчтобы скопировать все данные. ddвозвращает ненулевое состояние, если копия является частичной, поэтому легко обнаружить ошибку, но нет практического способа ее исправить.

POSIX не может предложить ничего лучшего на уровне оболочки. Мой совет - написать небольшую специализированную программу на C (в зависимости от того, что именно вы реализуете, вы можете назвать ее dd_done_rightили tail_headили mini-busybox).

Жиль "ТАК - прекрати быть злым"
источник
Вау, я никогда не знал yes | dd bs=1024k count=10 | wcпроблемы раньше. Насти.
Оле Танге
4

С dd:

dd if=1tb skip=12345678901 count=$((19876543212-12345678901)) bs=1M iflags=skip_bytes,count_bytes

Альтернативно с losetup:

losetup --find --show --offset 12345678901 --sizelimit $((19876543212-12345678901))

А потом dd, cat... устройство петли.

frostschutz
источник
Это кажется очень ориентированным на Linux. Мне нужен один и тот же код для работы на AIX, FreeBSD и Solaris.
Оле Танге
0

Вот как вы можете сделать это:

   i=$(((t=19876543212)-(h=12345678901)))
   { dd count=0 skip=1 bs="$h"
     dd count="$((i/(b=64*1024)-1))" bs="$b"
     dd count=1 bs="$((i%b))"
   } <infile >outfile

Это все, что действительно необходимо - это не требует намного большего. На первом месте dd count=0 skip=1 bs=$block_size1будет lseek()обычный ввод файлов практически мгновенно. Там нет шансов пропущенных данных или любой другой неправды об этом, вы можете просто искать прямо к вашей желаемой стартовой позиции. Поскольку файловый дескриптор принадлежит оболочке и ddони просто наследуют его, они будут влиять на его положение курсора, поэтому вы можете просто выполнить его пошагово. Это действительно очень просто - и нет стандартного инструмента, лучше подходящего для этой задачи, чем dd.

Это использует размер блока 64 КБ, который часто идеален. Вопреки распространенному мнению, большие размеры блоков не заставляют ddработать быстрее. С другой стороны, крошечные буферы тоже не годятся. ddему нужно синхронизировать свое время в системных вызовах, чтобы ему не нужно было ждать при копировании данных в память и снова, а также чтобы не нужно было ждать системных вызовов. Таким образом, вы хотите, чтобы это заняло достаточно времени, чтобы следующий read()не должен был ждать последнего, но не настолько, чтобы вы буферизировали в больших размерах, чем это необходимо.

Итак, первый ddпереходит на стартовую позицию. Это занимает нулевое время. Вы можете вызвать любую другую программу, которая вам нравится в этот момент, чтобы прочитать ее стандартный вывод, и она начнет читать непосредственно с желаемым байтовым смещением. Я звоню другому, ddчтобы прочитать ((interval / blocksize) -1)количество блоков в стандартный вывод.

Последнее, что необходимо, это скопировать модуль (если есть) предыдущей операции деления. И это все.

Кстати, не верьте этому, когда люди заявляют факты на лице без доказательств. Да, это возможно для ddкраткого чтения (хотя такие вещи невозможны при чтении с исправного блочного устройства - таким образом, имя) . Такое возможно только в том случае, если вы неправильно буферизуете ddпоток, который читается не с блочного устройства. Например:

cat data | dd bs="$num"    ### incorrect
cat data | dd ibs="$PIPE_MAX" obs="$buf_size"   ### correct

В обоих случаях ddкопируются все данные. В первом случае возможно (хотя и маловероятно,cat что ), что некоторые из выходных блоков, которые ddкопируются, будут равны байтам «$ num», потому что ddспецификация предназначена только для буферизации чего-либо вообще, когда буфер специально запрашивается по его команде. линия. bs=представляет собой максимальный размер блока , так как цель из ddв реальном времени ввода / вывода.

Во втором примере я явно указываю выходной размер блока и ddбуферизует чтение до тех пор, пока не будут выполнены полные записи. Это не влияет на то, count=что основано на входных блоках, но для этого вам просто нужен другой dd. Любая дезинформация, предоставленная вам в противном случае, должна игнорироваться.

mikeserv
источник