christoph ender's

blog

monday the 19th of january, 2026

buffered pipefail

I'm in the habit of catching bash/shell errors using set -eo pipefail – on the one hand, this will make bash exit immediately when any executed command fails, on the other, the pipefail option will return an error in case any command of a pipeline fails. For example, non-existent-command | cat will have an exit code of 0 if pipefail is not set, but 127 – “command not found” when the pipefail options is set. For a shell script, the latter behavior is usually desriable.

I've had a bash script, which contained the following line, running for almost a year:

find ${LOCATION} -type f | head -1

This simply returns the first line of the output provided by “find” and discards all the rest. Last week, “find” mysteriously started returning non-zero exit codes and I had absolutely no idea why. The find command itself didn't return any error when running on it's own, and there's also not much where head -1 can go wrong.

After some fiddling around, it turned out that the error is returned by the “find” command – for not being able to deliver all of it's output correctly. The head command reads all the input it is instructed to deliver – in our case, a single line – and then quits. At that point, the “find” command finds its output stream closed, and since it hasn't written all the output it is supposed to, it returns an error.

But still – why does it fail after it's been running without error for almost a year? The find command's output has always been “cut” before without it ever returning an error, so why now? Further testing showed that for the last week, find did produce much more output than before, and that was the key: Up until last week, find's entire output fitted nicely into the pipe buffer. For my version of linux, the pipe buffer currently has a size of 64kB. As soon as find's output exceeded that size, the data doesn't fit into the pipeline any more, and from this point on find encounters a closed stdout when head quits. The solution/workaround in this case was to avoid using a pipe alltogether:

head -1 <(find ${LOCATION} -type f)