I use emacs a lot. I use ssh a lot. I also use Cluster SSH. Finger macros being what they are, and Cluster SSH being a focus stealing bastard, I sometimes answer ‘yes’ to an SSH or Emacs question in one of 20 terminals and press Enter and inadvertently execute yes(1)
on 19 of them, causing an endless column of y
to appear. Since these SSH sessions are mostly remote ones, if I press Control-C to stop execution there are enough lines buffered for me to go on a short coffee break.
This won't do. Let's be negative just this once.
Here's a quick wrapper for yes
that stops early if its standard output is a terminal.
What the—? What's ‘yes’?
Glad you didn't actually ask. I'll answer regardless: yes is one of those obscure, standard little Unix programs you didn't know you needed. It prints out the same line of text again and again, endlessly. By default, the line it generates is y
—short for ‘yes’, of course, and meant to be piped into commands that ask for confirmation and can't be used non-interactively.
The program is meant to be used in pipelines, of course.
$ yes | sudo rm -rf /home/* # Don't try this at home! (see what I did there?)
If it's part of a pipeline, with its standard output sent to another program, it's all good. The trouble starts when you fire off this sucker with its standard output still connected to your terminal and you're connecting from a remote machine. A serious number of very small packets start coming your way en masse, and even after you break out of the program itself, you're still taking delivery of thousands of y
lines. So disgustingly positive it probably goes to tree hugging classes.
The Solution
Here's the hack. Originally, when run interactively, the output of yes piped to an awk
script. I rewrote it in pure bash
. It's better to avoid spawning external processes, of course, but this is a pipeline and it can't be helped. Might as well keep to a single language in the script.
#!/bin/bash
ORIGINAL_YES=/usr/bin/yes
if [[ -t 1 ]]; then
"$ORIGINAL_YES" "$@" | {
declare -i n=1
while read -r line; do
[[ "$line" == "$oldline" ]] && n+=1 || n=1
if [[ $n -gt 10 ]]; then
echo "... and so on, for ever and ever. Stopping here since stdout is a TTY."
exit 0
fi
oldline="$line"
echo "$line"
done
}
code=${PIPESTATUS[0]}
exit $code
else
exec "$ORIGINAL_YES" "$@"
fi
# End of file.
Save this somewhere like /usr/local/bin/yes
where it'll take precedence over /usr/bin/yes
. Check your $PATH
! While you're at it, set ORIGINAL_YES
near the top to the right value. On Debian and RH-like systems, /usr/bin/yes
is correct.
The theory of operation is relatively simple. We check if /dev/stdout
(file descriptor 1) is a tty using [[ -t 1 ]]
. If it is, we pipe yes
to a bash while
loop. The loop prints out its input until the eleventh identical line is seen in a row; it then prints out a message and exits. This extra logic (rather than just counting ten lines) makes it possible for, e.g. yes --help
to work unmodified.
Finally, if /dev/stdout
isn't a TTY, we exec
the standard yes
. We're done with the shell script at this point, so we use exec
, to entirely replace the shell wrapper process with yes
.