Прочитайте середину великого файлу


19

У мене є файл на 1 ТБ. Я хотів би прочитати з байту 12345678901 в байт 19876543212 і поставити його на стандартний вихід на машині зі 100 Мб оперативної пам'яті.

Я легко можу написати сценарій perl, який це робить. sysread забезпечує 700 Мб / с (що нормально), але систематизація забезпечує лише 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 є в байтах?
Graeme

Твоє bsє 4,096, що означає, що ти не можеш пропустити точніше 4,096байти
Стівен Пенні

1
@StevenPenny, ось чому існують три різні запуски ddз першого та останнього bs=1, щоб скопіювати дані, які не починаються та не закінчуються на вирівнюванні блоку.
Graeme

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 підходить для копіювання даних? (або, коли читаються () і записуються () частково), але лише до тих пір, поки його не перерве сигнал . За допомогою GNU coreutils ви можете використовувати fullblockпрапор, але це не є портативним.

Інша проблема ddполягає в тому, що може бути важко знайти кількість блоків, яка працює, оскільки як кількість пропущених байтів, так і кількість переданих байтів повинні бути кратними розміру блоку. Ви можете використовувати кілька дзвінків для dd: одного для копіювання першого часткового блоку, одного для копіювання основної маси вирівнюваних блоків та одного для копіювання останнього часткового блоку - див . Відповідь Graeme на фрагмент оболонки. Але не забувайте, що вам під час запуску сценарію, якщо ви не використовуєте 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... пристрій петлі.


Це здається дуже орієнтованим на 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.

Для цього використовується розмір блоку 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це білд тільки в буфер взагалі нічого , якщо буфер НЕ буде запитано на його Command- рядок. bs=представляє максимальний розмір блоку, оскільки мета введення dd/ виводу в реальному часі.

У другому прикладі я чітко вказую розмір вихідного блоку і ddбуфери зчитуються до тих пір, поки не може бути здійснено повне записування. Це не впливає на те, count=що базується на вхідних блоках, але для цього вам просто потрібен інший dd. Будь-яка дезінформація, яка вам надана в іншому випадку, повинна не враховуватися.

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