Копирование терабайтного раздела выглядит как зависший терминал: dd молчит до самого конца, и остаётся гадать, идёт ли процесс или всё давно остановилось. Утилита pv, сокращение от Pipe Viewer, встаёт в конвейер между командами и превращает немой поток в живую строку с процентами, скоростью и прогнозом времени. Разберём её на самых частых задачах: клонирование дисков, копирование файлов и каталогов, передача по сети и подключение к уже запущенным процессам. Инструмент маленький, а экономит часы наблюдений в самый ответственный момент переноса. Когда stdout не терминал, например в cron, pv всё равно пишет статус в stderr, и лог задачи получает свои числа.
Строка прогресса и её элементы
pv работает насквозь как cat, только с индикатором: данные проходят через утилиту, а на терминал выводится строка состояния. По умолчанию видны прошедшее время, переданные байты и текущая скорость. Проценты и прогноз появляются, когда pv знает общий объём: его задаёт флаг -s, а полный набор из бара, таймера, оценки и средней скорости собирают флаги -p, -t, -e, -r, -a.
pv -p -t -e -r -b /srv/big.img > /mnt/usb/big.img
dd молчит по своей природе: сообщение о сделанном появляется только после завершения, а с версии coreutils 8.24 появился встроенный status=progress, минимальный индикатор для одиночной команды. pv остаётся удобнее там, где конвейер длиннее одной команды: сжатие, сеть, шифрование, и каждый участок можно измерить отдельно. Строка состояния обновляется раз в секунду, интервал настраивает -i, и для коротких передач его уменьшают до десятых долей секунды. Утилита появилась в начале 2000-х, написана на C, ставится одним пакетом из репозиториев любого дистрибутива и не тянет зависимостей. Разделение потоков здесь принципиально: строка состояния уходит в stderr, поэтому конвейер с pv можно вкладывать в другой конвейер или перенаправлять в файл, и статус не подмешается к содержимому. Все приёмы ниже работают начиная с pv 1.6, которая есть в любом поддерживаемом дистрибутиве, а свежие сборки добавляют лишь мелочи вроде управления форматом строки.
Обёртка для dd при клонировании диска
Классическая задача: перенести систему со старого диска на новый. Команда dd без обвязки займёт час тишины, с pv каждая минута видна и спрогнозирована.
dd if=/dev/sda bs=4M status=none | \
pv -s "$(blockdev --getsize64 /dev/sda)" -p -t -e -r | \
dd of=/dev/sdb bs=4M conv=fsync status=none
Размер источника снимает blockdev --getsize64, и прогноз времени становится честным. Размер блока 4M выравнивает нагрузку на диск: мелкий bs по умолчанию в 512 байт режет скорость в разы. conv=fsync прижимает содержимое к носителю в конце, а status=none глушит собственный вывод dd, чтобы в терминале остался только бар pv. Для изношенных дисков добавляют conv=noerror,sync: сбойный сектор не остановит копирование, а дырки заменятся нулями и будут видны на приёмнике.
Размер снимают и с раздела, и с LVM-тома: blockdev --getsize64 /dev/sda1 или lsblk -b дают байты для -s без ручных пересчётов, и прогноз остаётся честным на любых носителях. Проверку записи не заменяет никакой бар: сравнение контрольных сумм до и после закрывает вопрос быстрее любых индикаторов.
Копию диска удобнее класть в файл-образ, а не прямо на устройство, и жать её на лету тем же конвейером: pv -s ... | zstd -T0 > disk.img.zst, где три участка показывают, где теряется скорость: чтение, сжатие, запись. dd if=/dev/sda bs=4M | pv -s "$(blockdev --getsize64 /dev/sda)" | gzip > disk.img.gz. Образ занимает место по заполненности, а не по ёмкости диска, разворачивается на носитель любого подходящего размера и сжимается на лету тем же конвейером. Прогресс здесь трёхслойный, и на каждом слое видно свой темп: чтение, сжатие, запись. Обратная операция симметрична: gzip -dc disk.img.gz | pv -s "$(blockdev --getsize64 /dev/sdb)" | dd of=/dev/sdb bs=4M conv=fsync, и разворачивание образа видно так же подробно, как его создание.
Известный размер через -s и точный прогноз
Проценты появляются только при известном размере, и его стоит вычислять, а не угадывать. Для файлов служит stat -c %s, для каталогов du -sb, для блочных устройств blockdev --getsize64 или чтение /sys/class/block/sda/size с умножением на 512.
pv -s "$(stat -c %s /srv/dump.sql)" /srv/dump.sql | mysql example_db
Без -s pv показывает скорость и счётчик байтов, и в ручной работе этого хватает. В скриптах точный размер превращает вывод в управляемую величину: флаг -W заставляет pv ждать первый байт, чтобы таймер не тикал впустую, пока партнёр по конвейеру раздумывает. Средняя скорость с флагом -a рядом с мгновенной показывает, выравнивается ли поток или прыгает по участкам, и это ранний сигнал о проблемах диска или сети. Прогноз считается по скользящему среднему, и на переменном темпе он честно плавает вместо вранья круглыми цифрами. Оценка учитывает лимит -L, и при замедлении прогноз ползёт вверх, а не продолжает врать старой скоростью. Свой вид строки собирает -F с подстановками процентов, скорости и таймера, что удобно для узких терминалов и панелей. Для сетевых конвейеров -W особенно ценна: таймер не стартует, пока от второй стороны не придёт первый байт, и нулевые секунды не портят статистику. Когда размер неизвестен принципиально, например на выводе долгой команды, остаётся режим строк: флаг -l считает не байты, а строки, и для журналов это удачная метрика.
Копирование каталогов и передача по сети
Каталог целиком уезжает через tar-конвейер, и pv ставится либо перед сжатием, либо в сети, показывая фактический темп канала.
tar -C /srv -cf - data | \
pv -s "$(du -sb /srv/data | cut -f1)" | \
gzip > /backup/data.tar.gz
tar -C /srv -cf - data | \
pv -s "$(du -sb /srv/data | cut -f1)" | \
ssh Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript. "cat > /backup/data.tar"
rsync показывает собственный прогресс опцией --info=progress2, и для повторяемых синхронизаций он и правда лучше, но на миллионе мелких файлов его проценты прыгают, а pv в tar-конвейере считает байты и даёт ровную шкалу. Сжатие zstd в конвейере замеряется тем же способом:
tar -C /srv -cf - data | \
pv -s "$(du -sb /srv/data | cut -f1)" | \
zstd -T0 > /backup/data.tar.zst
Здесь шкала показывает вход конвейера, и скорость сжатия видна косвенно, по разрыву между первой и последней цифрами журнала. pv выигрывает там, где rsync не нужен: одноразовый перенос, упаковка, чтение образа с сомнительного диска. Сжатие в конвейере тоже видно: переместив pv за gzip, вы измерите скорость уже сжатого потока и точно оцените сеть, а не диск.
cp прогресса не показывает вовсе, и для крупных файлов связка pv источник > приёмник заменяет cp один к одному. pv не переносит права и метки времени, это работа cp и rsync, pv измеряет поток, и смешивать роли не стоит. Пара pv на обоих концах отвечает на вечный вопрос, чья сторона тормозит: передающий показывает, что уходит, принимающий, что приходит, и разрыв сразу указывает на сеть. Связка с tee дублирует поток с измерением: pv исходник | tee копия1 > копия2, и оба приёмника пишутся одновременно под одним счётчиком. Прямая передача без SSH через nc измеряется так же: передающий запускает pv образ | nc host 9000, принимающий nc -l 9000 | pv > образ, и две строки прогресса честно показывают свои концы.
Два счётчика в одном конвейере и подключение к процессу
В длинном конвейере хочется видеть несколько участков сразу. Флаг -c переводит pv в режим курсоров: каждый экземпляр получает имя и живёт своей строкой, и пара счётчиков до и после сжатия отвечает на вопрос, где именно узкое место.
tar -C /srv -cf - data | \
pv -c "чтение" | \
gzip | \
pv -c "запись" > /backup/data.tar.gz
Отдельный фокус pv 1.6 и новее: подключение к уже запущенному процессу. Команда pv -d $(pidof dd) находит дескрипторы работающего dd и показывает его прогресс без перезапуска задачи, когда копирование начато без обвязки и останавливать его ради индикатора не хочется. Ночная задача с nohup и pv -n в лог переживает закрытие терминала, и утром график по строкам готов без отдельных служб. Индикатор уместен и внутри systemd-юнита: pv между двумя командами пишет темп в журнал юнита, и дежурный видит темп без входа на машину.
Для ночных заданий это спасение: не надо угадывать, сколько осталось, достаточно одного короткого вызова с любым терминалом. Синтаксис допускает уточнение по номеру дескриптора: pv -d PID:1 следит за первым, что выручает, когда процесс держит несколько открытых файлов. Скорость такого наблюдения нулевая, содержимое не перехватывается, pv лишь подглядывает в счётчики ядра, и работающий dd ничего не замечает. Режим курсоров красиво живёт в отдельной панели tmux: одна панель с парой pv, другая с журналом задачи, и картинка собирается без лишних экранов.
Логи, автоматизация и ограничение скорости
Для скриптов есть режим -n: вместо бара pv печатает число процентов, и вывод удобно направлять в лог или в индикаторы вроде dialog --gauge. Флаг -f принудительно пишет статус в stderr, оставляя stdout чистым для содержимого, а -L ограничивает скорость, чтобы фоновая копия не душила рабочий сервис.
pv -n -i 30 -f /srv/big.img /mnt/nas/big.img 2>> /var/log/copy.log
pv -L 20m /srv/big.img /mnt/nas/big.img
Изменить скорость уже работающего процесса можно без перезапуска: pv -R PID принимает новый лимит на лету, и ночная копия после полуночи разгоняется одним коротким вызовом. Идентификатор работающего pv подсказывает pgrep -f pv, так что и эта операция не требует запоминания ничего, кроме флага. Лимит меняют и на паузе, и после окончания окна нагрузки, без остановки копии. Для больших фоновых задач это экономит и время, и нервы дежурных: дневной лимит сменяется ночным без остановки копирования. Журнал с числами процентов строится в график простым парсером, и по нему видно, когда именно скорость просела. Полноценное окно прогресса из псевдографики собирается в пару команд: pv -n /srv/big.img | dialog --gauge "Копирование" 7 70 читает проценты из stdin и рисует индикатор. Лимит -L выручает и на слабых носителях, и на общих хранилищах, где ночная копия не должна съедать весь дисковый бюджет. В лог-режиме интервал стоит укрупнять: -i 30 пишет строку каждые полминуты, и ночные журналы остаются читаемыми.
Типовые ошибки при больших копированиях
Большинство провалов копирования предсказуемы, и их список короток. Размер блока 1M универсален для среднего железа, и отклонения от него выбирают по замерам, а не по привычке. Размер блока по умолчанию в 512 байт медлителен всегда, а слишком крупный bs на изношенных дисках даёт сбои на границах чтения. Для SSD и NVMe блок 16M иногда даёт ещё пару процентов, для старых HDD за пределами 4M роста нет, и слепое увеличение блока только ест память. Забытая синхронизация кэша: без conv=fsync или отдельного sync команда снаружи выглядит завершённой, когда содержимое ещё лежит в буферах. Прерванный конвейер оставляет частичный файл без предупреждения, и полезная привычка помечать незавершённые копии суффиксом .part, снимая его только после проверки.
Проверка результата стоит одну строку: sha256sum на источнике и приёмнике закрывает вопрос о целостности разом, а для образов носителей достаточно суммы таблицы разделов. Порядок безопасного клонирования диска выглядит так:
- снять размеры обоих дисков и убедиться, что приёмник не меньше источника;
- вычислить размер источника командой blockdev --getsize64;
- запустить dd через pv с -s, bs=4M и conv=fsync;
- дождаться честных ста процентов и вызвать sync;
- сверить контрольные суммы ключевых разделов или таблицы разделов.
Парные проверки контрольных сумм съедают время, зато закрывают вопрос навсегда: md5sum быстрее, sha256sum надёжнее, а для образов носителей достаточно суммы заголовков таблицы разделов.
pv не ускоряет копирование, он делает его видимым, а видимость в больших операциях дороже процентов производительности: именно она отличает управляемый процесс от угадайки с терминалом на час тишины. Один бинарник без зависимостей, работающий в любом конвейере, объясняет, почему pv десятилетиями остаётся в наборе администратора рядом с dd и tar.