bashshellterminalcommand-linedd

How to run multiple dd commands in background & see status?


I want to run multiple dd commands in background, but be able to see the status.

I have the following 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."

Which results in the following output:

$ 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

Is there any way how to output the respective dd statuses below the drive paths? For example:

$ 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

I tried various redirects, named pipes etc. but wasn't able to achieve such (or similar) output.


I tried the coprocesses approach which seems to be the way to go, but now I'm unable to make it work with the for cycle.

This works fine:

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

However this:

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

doesn't work for the second coprocess:

sda PID: 12759
./wipe.sh: line 39: warning: execute_coproc: coproc [12759:dd_sda] still exists
sdb PID: 

When hardcoding the name using if condition, it also works:

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

Solution

  • Here is my final solution, massively inspired by @Diego Torres Milano (thanks again).

    #!/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."
    

    Which results in such output:

    $ 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.