Keep running command until output differs from previous run in Bash
From man 1 watch
:
-g
,--chgexit
Exit when the output of command changes.
watch
is not required by POSIX but it's quite common anyway. In Debian or Ubuntu it's in the procps
package along with kill
and ps
(and few other tools).
Example:
watch -g -n 5 'date +%H:%M'
This should do the trick - explanations inline:
#!/usr/bin/env bash
# Exit if any errors occur or any undefined variables are dereferenced
set -o errexit -o nounset
# Store the outputs in a separate directory
output_directory="$(mktemp --directory)"
last_output_file="${output_directory}/last"
current_output_file="${output_directory}/current"
touch "$current_output_file"
ln --symbolic "$current_output_file" "$last_output_file"
# The count is just here to show that we always run at least twice, and that
# after that we run the command a random number of times.
count=0
while true
do
echo "$((++count))"
# This is our command; it prints 0 or 1 randomly
if [[ "$RANDOM" -gt 16383 ]]
then
echo 0 > "$current_output_file"
else
echo 1 > "$current_output_file"
fi
# Abort the loop when the files differ
if ! diff --brief "$last_output_file" "$current_output_file"
then
break
fi
# Shunt the current output to the last
mv "$current_output_file" "$last_output_file"
done
There are probably simpler ways to do this, but it has a couple useful features:
- It avoids creating any extraneous files, such as using
mktemp
for every output. - By starting with the last output file being a symbolic link to the current output file we guarantee that the first
diff
command sees two identical files. This avoids duplicating the command we're testing for, at the expense of one extradiff
execution.
Obviously, watch
is the way to go here, as described in another answer. But if you ever do want or need to do this in the shell, here's one way to do it:
#!/bin/sh
unset prev
while output=$(some_command);
[ "${prev+set}" != set ] || [ "$output" = "$prev" ];
do
prev=$output
sleep 10;
done
"${prev+set}"
expands to set
if prev
was set to a value after being unset at the start, so in effect it forces the contents of the loop to run at least once. Alternatively we could just initialize prev
as prev=$(some_command)
at the cost of running some_command
an additional time at the start of the loop.
Also, as noted in comments, command substitution removes all trailing newlines from the command output, so if the only changes in the result are with those, this won't be able to tell the outputs apart. That should rarely matter, though.