Por que ''cat "${1:-/dev/stdin} | ... &>/dev/null'' funciona no bash, mas não no dash?

Por que ''cat "${1:-/dev/stdin} | ... &>/dev/null'' funciona no bash, mas não no dash?

Roteiro:

#!/bin/sh
#
# reads stdin/file and copies it to clipboard
# clears it after 30s
#
cat "${1:-/dev/stdin}" | timeout 30 xclip -i -selection clipboard -r -verbose &>/dev/null &

Posso ver que apenas stdin não funciona (com bash funciona em stdin/file).
PS verbose é usado para fazer com que o xclip não seja daemonizado.

Responder1

&>é um bashismo, você terá que alterá-lo >/dev/null 2>&1para shells POSIX

Responder2

[esta resposta é sobre pipelines assíncronos em scripts; para o &>operador bash obsoleto e por que você deve sempre usá >output 2>&1-lo, consultesintaxe obsoleta e obsoleta]

#! /bin/sh
cat "${1:-/dev/stdin}" | ... &

Aqui você tem um pipeline rodando de forma assíncrona (porque finalizado por &), iniciado a partir de um script, ou seja, a partir de um shell com o controle de job desabilitado.

De acordo compadrão:

command1 & [command2 & ... ]

A entrada padrão para uma lista assíncrona, antes de qualquer redirecionamento explícito ser executado, deve ser considerada atribuída a um arquivo que possui as mesmas propriedades de /dev/null.

O problema é que dash, ksh, mksh, yash, etc interpretam "lista assíncrona" como qualquer comando, incluindo um pipeline, e redirecionarão o stdin do primeiro comando de /dev/null:

$ echo foo | dash -c 'cat | tr fo FO & echo DONE'
DONE
$ echo | dash -c 'readlink /proc/self/fd/0 | cat & echo DONE'
DONE
/dev/null

Mas bashsó irá interpretá-lo como "comando simples" e só irá redirecionar seu stdin /dev/nullquando fornãoparte de um pipeline:

$ echo foo | bash -c 'cat | tr fo FO & echo DONE'
DONE
FOO
$ echo | bash -c 'readlink /proc/self/fd/0 | cat & echo DONE'
DONE
pipe:[69872]
$ echo | bash -c 'readlink /proc/self/fd/0 & echo DONE'
DONE
/dev/null
$ bash -c 'cat | tr a A & echo DONE'
DONE
cat: -: Input/output error

zshirá redirecioná-lo apenas /dev/nullquando o stdin original for um tty, não quando for outro tipo de arquivo:

$ zsh -c 'readlink /proc/self/fd/0 &' </dev/tty
/dev/null
$ zsh -c 'readlink /proc/self/fd/0 &' </dev/zero
/dev/zero

Uma solução alternativa que funciona em todos os shells é duplicar o stdin em outro descritor de arquivo e redirecionar o stdin do primeiro comando dele:

#! /bin/sh
exec 3<"${1:-/dev/stdin}"
cat <&3 | timeout 30 xclip -i -selection clipboard -verbose -r >/dev/null 2>&1 &

Responder3

dashposicionado como padrão POSIX. POSIX especificou apenas [n]>redirecionamento. Mas bashapresenta muitos recursos próprios. &>é um deles e significa descritores de saída ( stderre stdout).

Você deveria ler um artigo sobrebater e corrercompatibilidade.

Talvez você seja útilcheckbashismosutilitário que pode ajudar a encontrar bashinstruções específicas em seus scripts.

informação relacionada