I recently stumbled over a surprising issue with an invalid /dev/stderr
on an up-to-date cygwin which is also present on a well-matured Debian installation. (Edit: Opposed to what I originally thought, my Debian system does not expose this error but simply produces the desired output. I now must suppose that this is a cygwin bug.)
Background: I'm using tools which produce thousands of lines of output (specifically: version control systems on a large production system).
I'm running them script-controlled and wanted to optionally redirect the noisy tool output to a log file. A simple solution seemed to always redirect their (stderr and stdout) output to a file system destination which was stored in an environment variable. If output to the terminal (or some user-controlled destination) was desired, the destination DBG_STDERR
would simply be "/dev/stderr", otherwise some temp file name. A typical tool execution line would then resemble noisy_command >> "$DBG_STDERR" 2>&1
.
This works fine unless I pipe the output of the script. Here is a minimal reproduction:
$ uname -a
CYGWIN_NT-6.1-WOW xxxxxxx 2.8.1(0.312/5/3) 2017-07-03 14:06 i686 Cygwin
$ bash --version
GNU bash, version 4.4.12(3)-release (i686-pc-cygwin)
$ cat say-something.sh
#!/bin/sh
echo something > /dev/stderr
$ (x=$(./say-something.sh 2> /dev/stderr)) 2>&1 |cat
./say-something.sh: line 2: /dev/stderr: No such file or directory
$ (x=$(./say-something.sh 2> /dev/stderr)) 2>&1
something
$ (x=$(./say-something.sh 2> /dev/stderr)) |cat
something
$ x=$(./say-something.sh 2> /dev/stderr) 2>&1 |cat
something
Of course all the redirections and nested shells look funny out of context. The extra shell is necessary because say-something.sh would actually be called by another script. The redundant redirection of fd 2 to stderr is the "switch" to facilitate optional redirection to a file (/dev/stderr, or a different path, is actually the configurable contents of a variable).
It seems as if all constituents of this pipeline are necessary, as the experiments after the failed example show: They all succeed.
- We need the final pipe of stdout
- We need the copying of stderr to stdout by the caller
- We need the outer shell around the command substitution.