
У меня есть файл размером 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
кошмара.
решение1
Это медленно из-за малого размера блока. Используя последнюю версию 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
опция добавлена выше согласно@Жиль ответ. Сначала я думал, что это может подразумеваться под count_bytes
, но это не так.
Упомянутые проблемы являются потенциальной проблемой ниже, если dd
вызовы чтения/записи прерываются по какой-либо причине, то данные будут потеряны. Это маловероятно в большинстве случаев (вероятность несколько снижается, поскольку мы читаем из файла, а не из канала).
Использование a 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?
решение2
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
).
решение3
С 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
... петлевое устройство.
решение4
Вот как это можно сделать:
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
.
Это использует размер блока 64k, что часто является идеальным. Вопреки распространенному мнению, больший размер блока не ускоряет dd
работу. С другой стороны, крошечные буферы тоже не годятся. dd
необходимо синхронизировать свое время в системных вызовах, чтобы не приходилось ждать копирования данных в память и обратно, а также чтобы не приходилось ждать системных вызовов. Поэтому вы хотите, чтобы это заняло достаточно времени, чтобы следующему read()
не пришлось ждать последнего, но не настолько, чтобы вы буферизовали большие размеры, чем необходимо.
Итак, первый dd
переходит в начальную позицию. Это занимаетнульвремя. Вы можете вызвать любую другую программу, которая вам нравится в этот момент, чтобы прочитать ее stdin, и она начнет читать прямо с нужного вам смещения байта. Я вызываю другую, dd
чтобы прочитать((interval / blocksize) -1)
подсчет блоков в stdout.
Последнее, что нужно, это скопировать модуль(если есть)предыдущей операции по разделению. И это все.
Не верьте, кстати, когда люди говорят факты на лицо без доказательств. Да, это возможно для того, чтобы 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
. Любая дезинформация, которая вам дается в противном случае, должна быть проигнорирована.