1

The following bash-fu code works fine on Linux but breaks on MacOS:

files="foo bar"

echo PROG 1
for file in $files
do
  echo $file | tee -a tempfile.txt
done

sort -u tempfile.txt

echo PROG 2
function trick {
  for file in $files
  do
    echo $file | tee -a $1
  done
}

trick >(sort -u)

The error is:

PROG 1
foo
bar
bar
foo
PROG 2
tee: /dev/fd/63: Bad file descriptor
foo
tee: /dev/fd/63: Bad file descriptor
bar

On Linux PROG 2 writes the same lines as PROG 1 without errors. On MacOS it seems that the pipe handle is either closed or not inherited.

The above is the minimized sample to reproduce the problem. In reality I process both tie output and redirected handle heavily. Something in the spirit of

function trick {
  for file in $files
  do
     echo $file | tee -a $1 | grep -Eo "^.."
  done
}

trick >(sort -u | sed 's|o|x|g')

The code doesn't work in Bash 4.1, but works in Bash 4.4 in multiple distros (Arch, Ubuntu and Debian)

  • 1
    Have you heard of handle inheritance? – nponeccop Dec 1 '17 at 8:19
  • Ah, right you are. Didn't think it through fully, sorry. – Wildcard Dec 1 '17 at 10:45
2

macOS comes with a very old version of bash. That bug (that the file descriptor for that process substitution is closed before calling tee in that context)¹ was fixed in newer versions. You'd get the same problem on Linux (with a different error message as /dev/fd/x is implemented differently there) with bash 3.2 there.

Here, you could use zsh or ksh93 instead. It's a good idea to avoid bash here anyway as it doesn't wait for processes in process substitutions (zsh waits for them, ksh93 can be told to wait for them).

Note that even with the latest (4.4.12 as of writing) version, bash still has a few bugs here like:

$ bash -c 'eval cat <(echo test)'
test # OK but:
$ bash -c 'eval "echo foo;cat" <(echo test)'
foo
cat: /dev/fd/63: No such file or directory
$ bash -c 'eval f=<(echo test) "; cat \$f"'
cat: /dev/fd/63: No such file or directory

and some still triggered by pipes like:

$ cat file
echo "$1"
cat "$1"
$ bash -c 'source ./file <(echo test)'
/dev/fd/63
test  # OK but:
$ cat file2
echo "$1" | wc -c
cat "$1"
$ bash -c 'source ./file2 <(echo test)'
11
cat: /dev/fd/63: No such file or directory

¹ bash closes that file descriptor as soon as a there is a pipeline. A shorter reproducer:

$ bash -c 'f() { :; cat "$1"; }; f <(echo OK)'
OK
$ bash -c 'f() { :|:; cat "$1"; }; f <(echo test)'
cat: /dev/fd/63: No such file or directory

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.