A Little Bit Louder Now
Tim Hammerquist March 12, 2021 #linux #macos #shell #unixThere aren't many Unix utilities more Unixy than dd(1)
. It takes data from the
input file and writes it to the output file — but it's kind of shy. With
very little work, we can convince dd
to give us status updates!
DD R U OK?
For almost half a century, dd
has been the go-to Unix utility for copying raw
data from place to place, and it's still one of the most common tools used to
write disk images (e.g., Linux distribution images) onto discs and USB sticks.
But for all the time, it's been pretty reticent. You invoke it from the command line. Give it the files/devices to operate on, maybe a parameter or two, and off it goes... you hope. I still recall writing 4+GB Linux DVD-ROM ISOs to USB thumb drives, watching that unchanging cursor, and wondering how many days would pass until it finished.[1]
loudd
is a drop-in replacement for dd
. It doesn't turn dd
into a font of
elegance, but it does make writing images a little more verbose.
Poking the PID
It turns out that dd(1)
will happily provide an update on its progress. It
just needs a little prodding.[2]
If
dd
receives aSIGINFO
... signal, the current input and output blockcounts will be written to the standard error output in the same format as the standard completion message.
Here's a wrapper script I've called loudd
that takes advantage of this. We'll
dig into it a bit more below.
#!/bin/sh
# loudd - a louder dd
& pid=
while ; do
done
From a high level, here's what we're doing:
- Start
dd
with the arguments passed toloudd
- Send it to the background (
&
), capturing its pid ($!
) - For as long as
dd
is running:- ... send it a
SIGINFO
signal - ... sleep for 1 second
- ... send it a
And here's what it looks like in action:
)
)
)
)
In this example, loudd
sends 3 SIGINFO
signals to dd
before it completes,
triggering the first 3 stat stanzas.
At completion, dd
sends its regularly scheduled block of statistics.
Keep reading for more detail on how it works.
Break It Down
&
This line invokes the actual dd
process. The $@
shell variable expands to
anything passed after the script's name.
Given loudd if=a_file of=another_file
, the $@
variable will contain the
individual arguments if=a_file
and of=another_file
. The resulting command
would be dd if=a_file of=another_file &
.
Note that there's a another shell variable $*
which is very similar, but it
doesn't treat the arguments as separate parts. For example, by contrast:
Given loudd if=a_file of=another_file
, the result would be dd "if=a_file of=another_file" &
, which won't make any sense to dd
.
Finally, the &
turns dd
into a background process.
pid=
Having just started dd
as a background process in the previous line, we use
the $!
to get its pid and store it in the $pid
variable for
later.
Here we send the SIGINFO
or INFO
signal to dd
using the pid we just
stored.
If the dd
process is still running:
dd
will dump its current stats to itsstderr
stream, generally the shellkill
will return 0 (success), since the signal was successfully sent
If dd
has finished, however:
kill
will fail to send theINFO
signal and return nonzero
2>/dev/null
If dd
has terminated, kill
will be unable to send the signal and write an
error to stderr
(fd 2). However, we full expect dd
to terminate eventually,
so the error message can be safely sent to /dev/null
— i.e., silenced.
Sleep for 1 second. This can be any value. I like seeing the status every second or so, if only to reassure me the write hasn't got stuck somewhere. Adjust to taste.
while ; do ; done
The POSIX while
loop. As long as the COND clause returns a successful
status (0), the shell will execute the LOOP clause.
In this case, as long as dd
is still running, we'll send the INFO
signal and
sleep for approximately 1 second.
What now?
This isn't a flashy script. Much like dd
itself, it does the bare minimum
asked of it. This has been more than enough for my needs, but it certainly COULD
do more.
Some ideas I've spit-balled:
- Parse and convert the bytes read/written to a
curl
-style progress meter. - Compute total bytes to be written, and use bytes written so far to produce a progress bar.
I appreciate being able to use this same script on Linux, macOS, and BSD without any dependencies or changes, but there's nothing stopping you, dear reader, from expanding on this idea.
If you do, or if you learned something from this, drop me a line and tell me about it!
Footnotes
- A feature of some terminals will cause
CTRL-t
to send aSIGINFO
signal to the process attached to the tty. This means that it may be possible to just use^T
to trigger an ad hoc statistics block. - GNU
dd
offers astatus
option that provides similar functionality toloudd
. However, it is limited to ~1 second intervals, and is not available on non-GNU platforms like macOS or BSD.