linguistics, computers, and puns

A Little Bit Louder Now

Tim Hammerquist March 12, 2021 #linux #macos #shell #unix

There 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 a SIGINFO ... 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

dd "$@" & pid=$!

while kill -INFO "${pid}" 2>/dev/null; do
  sleep 1
done

From a high level, here's what we're doing:

And here's what it looks like in action:

% loudd if=ReallyBig.img of=a_usb_stick bs=1m
2894+0 records in
2893+0 records out
3033530368 bytes transferred in 0.999853 secs (3033975955 bytes/sec)
5703+0 records in
5703+0 records out
5980028928 bytes transferred in 2.025140 secs (2952896486 bytes/sec)
8321+0 records in
8320+0 records out
8724152320 bytes transferred in 3.028724 secs (2880471037 bytes/sec)
10542+1 records in
10542+1 records out
11054921728 bytes transferred in 3.880040 secs (2849177134 bytes/sec)

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

dd "$@" &

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.


kill -INFO "${pid}"

Here we send the SIGINFO or INFO signal to dd using the pid we just stored.

If the dd process is still running:

If dd has finished, however:


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 1

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 COND; do LOOP; 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:

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

  1. A feature of some terminals will cause CTRL-t to send a SIGINFO 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.
  2. GNU dd offers a status option that provides similar functionality to loudd. However, it is limited to ~1 second intervals, and is not available on non-GNU platforms like macOS or BSD.