Как запустить несколько команд dd в фоновом режиме и посмотреть статус?

Я хочу запускать несколько команд 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 будут выдавать отчет о состоянии, когда вы отправляете им определенный сигнал (IIRC SIGUSR1, но проверьте справочную страницу).

tripleee 04.01.2023 21:21

Перенаправьте вывод каждой команды dd в файл. В конце распечатайте все файлы.

Barmar 04.01.2023 21:25

@markp-fuso Извините, я не сохранил код. Однако основная проблема заключалась в том, что я не мог найти способ одновременной печати двух FIFO. При использовании команды cat она печатала только первую и продолжала работать, пока не закончилась.

krystof.k 07.01.2023 11:19
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
74
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Перенаправьте статус каждого 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 определенно поможет, но вставляет темп. имена файлов в вывод.

Правильный способ справиться с ситуацией будет...

  • Используйте FIFO вместо файлов (см. команду mkfifo).
  • Удаляйте status=progress и периодически...
    • Звоните kill -s USR1 по всем* текущим dd вакансиям,
      что заставляет их печатать одну строку состояния.
    • Обновите некоторый внутренний массив состояния, прочитав файл fifos*.
    • Вывести все статусы из массива.

* В какой-то момент будет запущено только несколько заданий dd, в то время как другие уже завершили и закрыли свои fifo, что немного усложняет этот процесс. Это основная причина, по которой я придерживался файлов для этого ответа.

Спасибо. Да, я хотел избежать записи в файлы по описанным вами причинам. Я пытался работать с FIFO, но тогда мне не удалось прочитать из них несколько статусов: команда cat напечатала только один FIFO и оставалась открытой до его завершения.

krystof.k 07.01.2023 11:02

Используя сопроцессы, вы можете сделать что-то вроде этого

#! /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 сразу после запуска.

krystof.k 07.01.2023 11:18

Да, просто предупреждение, кажется, это никак не подавить.

Diego Torres Milano 07.01.2023 11:53

Я понимаю. Однако я не могу заставить его работать с циклом for при определении сопроцессов. См. обновленный вопрос выше.

krystof.k 07.01.2023 15:03

Спасибо, использование eval работает. Действительно странно, что переменная этого не делает.

krystof.k 08.01.2023 11:58

У меня есть это. Ваше решение действительно помогло, хотя оно нуждалось в небольшой настройке, чтобы цикл while завершался изящно. Размещение моего окончательного решения ниже.

krystof.k 08.01.2023 14:20
Ответ принят как подходящий

Вот мое окончательное решение, вдохновленное @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.

Другие вопросы по теме