Como salvar / dev / stdout local de destino em um script bash?

12

Eu tenho um certo script bash, que quer preservar o local original do /dev/stdout antes de substituir o primeiro descritor de arquivo por outro local.

Então, naturalmente, eu escrevi algo como

old_stdout=$(readlink -f /dev/stdout)

E não funcionou. Rapidamente entendo qual foi o problema:

test@ubuntu:~$ echo $(readlink -f /dev/stdout)
/proc/5175/fd/pipe:[31764]
test@ubuntu:~$ readlink -f /dev/stdout
/dev/pts/18

Obviamente, $() é executado em um subshell, que é canalizado para o shell pai.

Portanto, a pergunta é: existe uma maneira confiável (com escopo de portabilidade entre as distribuições Linux) de salvar /dev/stdout location como uma string em um script bash?

    
por alexey.e.egorov 29.07.2016 / 18:53

3 respostas

14

Para salvar um descritor de arquivo, você o duplica em outro fd. Salvar um caminho para o arquivo correspondente não é suficiente, você precisa salvar o modo de abertura, os flags de abertura, a posição atual dentro do arquivo e assim por diante. E, claro, para tubos anônimos, ou soquetes, isso não funcionaria, já que eles não têm caminho. O que você deseja salvar é a descrição do arquivo aberto que o fd se refere, e a duplicação de um fd está retornando um novo fd para a mesma descrição do arquivo aberto .

Para duplicar um descritor de arquivo em outro, com shell parecido com o Bourne, a sintaxe é:

exec 3>&1

Acima, o fd 1 é duplicado no fd 3.

O que quer que o fd 3 já estivesse aberto antes seria fechado, mas note que os fds 3 a 9 (geralmente mais, até 99 com yash ) são reservados para esse propósito (e não têm nenhum significado especial contrário a 0, 1 , ou 2), o shell não sabe usá-los para seus próprios negócios internos. A única razão pela qual o fd3 teria sido aberto de antemão é porque você o fez no script 1 , ou foi vazado pelo chamador.

Depois, você pode alterar o padrão para outra coisa:

exec > /dev/null

e mais tarde, para restaurar a stdout:

exec >&3 3>&-

( 3>&- é fechar o descritor de arquivo que não precisamos mais).

Agora, o problema com isso é que, exceto em ksh, todos os comandos que você executar depois que exec 3>&1 herdarão o fd 3. Isso é um vazamento de fd. Geralmente não é grande coisa, mas isso pode causar problemas.

ksh define o sinalizador close-on-exec nesses fds (para fds acima de 2), mas não outros shells e outros shells não têm como configurar esse sinalizador manualmente.

A solução para outro shell é fechar o fd 3 para cada comando, como:

exec 3>&-

exec > file.log

ls 3>&-
uname 3>&-

exec >&3 3>&-

Incómodo. Aqui, a melhor maneira seria não usar exec , mas redirecionar os grupos de comando:

{
  ls
  uname
} > file.log

Lá, é o shell que toma cuidado para salvar stdout e restaurá-lo depois (e faz isso internamente duplicando-o em um fd (acima de 9, acima de 99 para yash ) com o close-on -exec flag set).

Nota 1

Agora, o gerenciamento desses fds 3 a 9 pode ser incômodo e problemático se você usá-los extensivamente ou em funções, especialmente se o seu script usar algum código de terceiros que possa, por sua vez, usar esses fds.

Alguns shells ( zsh , bash , ksh93 , todos adicionaram o recurso ( sugerido por Oliver Kiddle de zsh ) por volta da mesma época em 2005 depois de ter sido discutido entre seus desenvolvedores) tem uma sintaxe alternativa para atribuir o primeiro fd livre acima de 10, o que ajuda nesse caso:

myfunction() {
  local fd
  exec {fd}>&1
  # stdout was duplicated onto a new fd above 10, whose actual value
  # is stored in the fd variable
  ...
  # it should even be safe to re-enter the function here
  ...
  exec >&"$fd" {fd}>&-
}
    
por 29.07.2016 / 20:26
3

Como você pode ver, bash scripting não é como uma linguagem de programação regular onde você pode atribuir descritores de arquivos.

A solução mais simples é usar um sub-shell para executar o que você deseja redirecionar, para que o processamento possa ser revertido para o shell superior, que tem sua E / S padrão intacta.

Uma solução alternativa seria usar tty para identificar o dispositivo TTY e controlar a E / S no seu script. Por exemplo:

dev=$(tty)

e depois você pode ..

echo message > $dev
    
por 29.07.2016 / 19:01
1

$$ te daria o PID do processo atual, no caso de shell interativo ou script do PID de shell relevante.

Então você pode usar:

readlink -f /proc/$$/fd/1

Exemplo:

% readlink -f /proc/$$/fd/1
/dev/pts/33

% var=$(readlink -f /proc/$$/fd/1)

% echo $var                       
/dev/pts/33
    
por 29.07.2016 / 19:03