Escreva iterativamente no mesmo arquivo de substituição de processo em MacOs

Escreva iterativamente no mesmo arquivo de substituição de processo em MacOs

O seguinte código bash-fu funciona bem no Linux, mas falha no 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)

O erro é:

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

No Linux PROG 2escreve as mesmas linhas PROG 1sem erros. No MacOS, parece que o identificador do pipe está fechado ou não é herdado.

O acima é a amostra minimizada para reproduzir o problema. Na realidade, eu processo pesadamente a saída do empate e o identificador redirecionado. Algo no espírito de

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

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

O código não funciona no Bash 4.1, mas funciona no Bash 4.4 em várias distros (Arch, Ubuntu e Debian)

Responder1

O macOS vem com uma versão muito antiga do bash. Esse bug (que o descritor de arquivo para aquela substituição de processo é fechado antes de chamar teenesse contexto)¹ foi corrigido em versões mais recentes. Você teria o mesmo problema no Linux (com uma mensagem de erro diferente, conforme /dev/fd/ximplementado de forma diferente) com o bash 3.2.

Aqui, você poderia usar zshou ksh93em vez disso. É uma boa ideia evitar bashaqui de qualquer maneira, poisnão espera por processos em substituições de processos(zsh espera por eles, ksh93 pode ser informado waitpara eles).

Observe que mesmo com a versão mais recente (4.4.12 no momento da escrita), bashainda há alguns bugs aqui, como:

$ 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

e alguns ainda acionados por pipes como:

$ 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 fecha esse descritor de arquivo assim que houver um pipeline. Um reprodutor mais curto:

$ 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

informação relacionada