Я хочу запускать несколько команд dd в фоновом режиме, но иметь возможность видеть статус.
У меня есть следующее script.sh
:
#!/usr/bin/env bash
for drive in $@
do
echo "Wiping $drive"
dd if=/dev/zero of=$drive status=progress &
done
wait
echo "Done."
Что приводит к следующему результату:
$ sudo bash ./script.sh /dev/sda /dev/sdb
Wiping /dev/sda
Wiping /dev/sdb
288788992 bytes (289 MB, 275 MiB) copied, 10 s, 28.9 MB/s 14404864 bytes (114 MB, 109 MiB) copied, 4 s, 28.6 MB/s
Есть ли способ вывести соответствующие статусы dd ниже путей к дискам? Например:
$ sudo bash ./script.sh /dev/sda /dev/sdb
Wiping /dev/sda
288788992 bytes (289 MB, 275 MiB) copied, 10 s, 28.9 MB/s
Wiping /dev/sdb
14404864 bytes (114 MB, 109 MiB) copied, 4 s, 28.6 MB/s
Я пробовал различные перенаправления, именованные каналы и т. д., но не смог добиться такого (или подобного) результата.
Я попробовал подход сопроцессов, который кажется подходящим, но теперь я не могу заставить его работать с циклом for.
Это отлично работает:
coproc dd_sda { dd if=/dev/zero of=/dev/sda status=progress 2>&1; }
echo "sda PID: $dd_sda_PID"
coproc dd_sdb { dd if=/dev/zero of=/dev/sdb status=progress 2>&1; }
echo "sdb PID: $dd_sdb_PID"
sda PID: 12494
./wipe.sh: line 86: warning: execute_coproc: coproc [12494:dd_sda] still exists
sdb PID: 12496
Однако это:
for drive in sda sdb
do
coproc_name=dd_${drive}
coproc $coproc_name { dd if=/dev/zero of=/dev/$drive status=progress 2>&1; }
pid_var = "${coproc_name}_PID"
echo "$drive PID: ${!pid_var}"
done
не работает для второго сопроцесса:
sda PID: 12759
./wipe.sh: line 39: warning: execute_coproc: coproc [12759:dd_sda] still exists
sdb PID:
При жестком кодировании имени с использованием условия if оно также работает:
for drive in sda sdb
do
coproc_name=dd_${drive}
if [[ "$drive" == 'sda' ]]
then
coproc dd_sda { dd if=/dev/zero of=/dev/$drive status=progress 2>&1; }
elif [[ "$drive" == 'sdb' ]]
then
coproc dd_sdb { dd if=/dev/zero of=/dev/$drive status=progress 2>&1; }
fi
pid_var = "${coproc_name}_PID"
echo "$drive PID: ${!pid_var}"
done
sda PID: 12998
./wipe.sh: line 39: warning: execute_coproc: coproc [12998:dd_sda] still exists
sdb PID: 13000
Перенаправьте вывод каждой команды dd
в файл. В конце распечатайте все файлы.
@markp-fuso Извините, я не сохранил код. Однако основная проблема заключалась в том, что я не мог найти способ одновременной печати двух FIFO. При использовании команды cat
она печатала только первую и продолжала работать, пока не закончилась.
Перенаправьте статус каждого dd
в файл и многократно печатайте эти файлы, очищая старый вывод.
dd status=progress
из GNU coreutils выводит свой статус в stderr, поэтому используйте 2>
для перенаправления информации о статусе.
Статус обновляется путем перезаписи текущей строки с помощью \r
, за которым следует новый статус. Поскольку \r
работает только для отдельных строк, для обновления четырех разных строк требуются последовательности управления терминалом, например. Escape-коды ANSI, которые можно удобно распечатать с помощью таких команд, как clear
и tput
.
outfile_prefix=/tmp/wipe-status-$$-
for drive in "$@"; do
(
echo "Wiping $drive"
# dummy-version for testing ...
dd if=/dev/zero of=/dev/null bs=1 count=5M status=progress 2>&1
# ... if you are happy with the output, replace it with
# dd if=/dev/zero of = "$drive" status=progress 2>&1
) > "$outfile_prefix$BASHPID" &
done
clear -x # clear the currently visible screen
while # do-while loop
tput home # move cursor to top left
sed '' "$outfile_prefix"* # print files like `cat`, but with \n at the end
jobs %% &> /dev/null # while jobs are running
do
sleep 1
done
rm "$outfile_prefix"*
Приведенный выше код неэффективен, если вы стираете много больших или медленных дисков, потому что файлы состояния продолжают расти (даже если они, кажется, содержат только одну строку), и мы продолжаем печатать эти растущие файлы снова и снова.
Если у вас возникнут проблемы, попробуйте увеличить время сна. Возможно, sed 's/.*\r//'
вместо sed ''
ускорит процесс. tail -c100
определенно поможет, но вставляет темп. имена файлов в вывод.
Правильный способ справиться с ситуацией будет...
mkfifo
).status=progress
и периодически...
kill -s USR1
по всем* текущим dd
вакансиям,* В какой-то момент будет запущено только несколько заданий dd
, в то время как другие уже завершили и закрыли свои fifo, что немного усложняет этот процесс. Это основная причина, по которой я придерживался файлов для этого ответа.
Спасибо. Да, я хотел избежать записи в файлы по описанным вами причинам. Я пытался работать с FIFO, но тогда мне не удалось прочитать из них несколько статусов: команда cat
напечатала только один FIFO и оставалась открытой до его завершения.
Используя сопроцессы, вы можете сделать что-то вроде этого
#! /bin/bash
coproc DD1 { dd if=/dev/zero of=$drive1 status=progress 2>&1; }
coproc DD2 { dd if=/dev/zero of=$drive2 status=progress 2>&1; }
coproc DD3 { dd if=/dev/zero of=$drive3 status=progress 2>&1; }
el=$(tput el)
cuu1=$(tput cuu1)
IFS=
while :
do
for n in {1..3}
do
v = "DD$n"
if read -r -d $'\r' -u ${!v[0]} line
then
printf '%s%s: %s\r\n' "$el" "$v" "$line"
else
printf '%s: Done\r\n' "$el" "$v"
fi
done
for n in {1..3}
do
printf '%s' "$cuu1"
done
done
у моего dd
нет status
, поэтому я предполагаю, что dd
добавляет \r
вместо \n
.
цикл for (к сожалению, eval кажется единственным вариантом, хотя не знаю, почему coproc
не берет имя из переменной)
for n in {1..3}
do
eval coproc "DD${n}" "{ dd if=/dev/zero of=\$drive$n status=progress 2>&1; }"
done
Круто, сопроцессы - это то, что нужно, спасибо! Просто получаю ошибку warning: execute_coproc: coproc still exists
сразу после запуска.
Да, просто предупреждение, кажется, это никак не подавить.
Я понимаю. Однако я не могу заставить его работать с циклом for при определении сопроцессов. См. обновленный вопрос выше.
Спасибо, использование eval работает. Действительно странно, что переменная этого не делает.
У меня есть это. Ваше решение действительно помогло, хотя оно нуждалось в небольшой настройке, чтобы цикл while завершался изящно. Размещение моего окончательного решения ниже.
Вот мое окончательное решение, вдохновленное @Diego Torres Milano (еще раз спасибо).
#!/usr/bin/env bash
drives=$@
number_of_drives=$#
drives_to_wipe=($drives)
stripped_drive_path() {
echo $1 | awk -F/ '{ print $3 }'
}
wipe() {
echo "Filling \`$1\` drive with zeros:"
dd if=/dev/zero of=$1 status=progress 2>&1
}
echo "Total number of drives: $number_of_drives"
# Wipe the drives in parallel
iteration=1
for drive in $drives; do
drive_name=$(stripped_drive_path $drive)
coproc_name=dd_${drive_name}
eval coproc $coproc_name "{ wipe $drive; }"
if [[ "$iteration" == 1 ]]; then
echo 'Feel free to ignore the following warnings'
fi
((iteration++))
done
# Display the progress
iteration=1
# Run until all drives are wiped
while [[ ${#drives_to_wipe[@]} > 0 ]]; do
for drive in $drives; do
drive_name=$(stripped_drive_path $drive)
coproc_name=dd_${drive_name}
# Move one line below the "Filling drive with zeros" message
if [[ $iteration > 1 ]]; then
tput cud 1
fi
# Read the drive's current status from the coprocess
if read -r -d $'\r' -u ${!coproc_name[0]} line &> /dev/null; then
tput el # Clear line
echo -e "$line\r\n"
else
# Remove the finished drive from the list
for i in ${!drives_to_wipe[@]}; do
if [ "${drives_to_wipe[$i]}" == "$drive" ]; then
unset drives_to_wipe[$i]
fi
done
tput el # Clear line
echo -e "$(($number_of_drives - ${#drives_to_wipe[@]}))/$number_of_drives done!\r\n"
fi
# Move back one line up
if [[ $iteration > 1 ]]; then
tput cuu 1
fi
done
# Move two lines up for each drive
if [[ ${#drives_to_wipe[@]} > 0 ]]; then
tput cuu $(($number_of_drives * 2))
fi
((iteration++))
done
echo "All drives ($number_of_drives) wiped."
Что приводит к такому выводу:
$ sudo bash ./script.sh /dev/sda /dev/sdb
Total number of drives: 2
Feel free to ignore the following warnings
./wipe.sh: line 24: warning: execute_coproc: coproc [85994:dd_sda] still exists
Filling `/dev/sda` drive with zeros:
1/2 done!
Filling `/dev/sdb` drive with zeros:
2/2 done!
All drives (2) wiped.
Некоторые реализации
dd
будут выдавать отчет о состоянии, когда вы отправляете им определенный сигнал (IIRCSIGUSR1
, но проверьте справочную страницу).