No código “{exec> / dev / null; } / dev / null ”o que está acontecendo sob o capô?


15

Quando você redireciona uma lista de comandos que contém um redirecionamento de exec, o exec> / dev / null ainda não parece ser aplicado posteriormente, como por exemplo:

{ exec >/dev/null; } >/dev/null; echo "Hi"

"Oi" é impresso.

Fiquei com a impressão de que a {}lista de comandos não é considerada um subshell, a menos que faça parte de um pipeline, portanto exec >/dev/nullainda deve ser aplicada no ambiente de shell atual em minha mente.

Agora, se você mudar para:

{ exec >/dev/null; } 2>/dev/null; echo "Hi"

não há saída conforme o esperado; o descritor de arquivo 1 permanece apontado para / dev / null para futuros comandos também. Isso é mostrado executando novamente:

{ exec >/dev/null; } >/dev/null; echo "Hi"

o que não dará saída.

Tentei fazer um script e segui-lo, mas ainda não tenho certeza do que está acontecendo aqui.

Em cada ponto deste script, o que está acontecendo com o descritor de arquivo STDOUT?

Edição: Adicionando minha saída strace:

read(255, "#!/usr/bin/env bash\n{ exec 1>/de"..., 65) = 65
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fcntl(1, F_GETFD)                       = 0
fcntl(1, F_DUPFD, 10)                   = 10
fcntl(1, F_GETFD)                       = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0
close(10)                               = 0
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fcntl(1, F_GETFD)                       = 0
fcntl(1, F_DUPFD, 10)                   = 10
fcntl(1, F_GETFD)                       = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0
dup2(10, 1)                             = 1
fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
close(10)                               = 0
fstat(1, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), ...}) = 0
ioctl(1, TCGETS, 0x7ffee027ef90)        = -1 ENOTTY (Inappropriate ioctl for device)
write(1, "hi\n", 3)                     = 3

Isso é estranho; Não consigo reproduzir o close(10). Você também pode postar todo o conteúdo do script que executou?
DepressedDaniel

@DepressedDaniel Aqui está o script e strace completa: roteiro strace
Joey Pabalinas

Você tem um desvio ;depois }, que altera o significado de > /dev/nullnão aplicar à lista composta, {}afinal.
DepressedDaniel

@DepressedDaniel Ah, você está completamente correto! Agora a saída é o que eu espero; Obrigado por suas respostas!
Joey Pabalinas

Respostas:


17

Vamos seguir

{ exec >/dev/null; } >/dev/null; echo "Hi"

passo a passo.

  1. Existem dois comandos:

    uma. { exec >/dev/null; } >/dev/null, Seguido por

    b. echo "Hi"

    O shell executa primeiro o comando (a) e depois o comando (b).

  2. A execução do { exec >/dev/null; } >/dev/nullproduto é a seguinte:

    uma. Primeiro, o shell executa o redirecionamento >/dev/null e lembra-se de desfazê-lo quando o comando termina .

    b. Em seguida, o shell é executado { exec >/dev/null; }.

    c. Por fim, o shell alterna a saída padrão de volta para onde estava. (Esse é o mesmo mecanismo que in ls -lR /usr/share/fonts >~/FontList.txt- redirecionamentos são feitos apenas pela duração do comando ao qual eles pertencem.)

  3. Depois que o primeiro comando é concluído, o shell é executado echo "Hi". A saída padrão está onde estava antes do primeiro comando.


Existe alguma razão por que 2a é executado antes de 2b? (da direita para a esquerda)
Joey Pabalinas

5
Os redirecionamentos devem ser executados antes do comando ao qual eles se aplicam, não? Como eles poderiam funcionar de outra maneira?
AlexP

Aha, nunca pensei nisso dessa maneira! Os dois primeiros são ótimas respostas; dando um pouco antes de eu escolher uma, mas agradeço as duas explicações!
Joey Pabalinas

Infelizmente, só posso escolher uma resposta, por isso vou com essa, uma vez que é um pouco menos técnica e, portanto, acho que seria capaz de ajudar até os usuários menos entendidos em tecnologia. No entanto, o @DepressedDaniel teve uma resposta igualmente ótima aqui, que oferece uma explicação mais aprofundada.
Joey Pabalinas

14

Para não usar um subcasca ou subprocesso, quando a saída de uma lista composta {}é canalizada >, o shell salva o descritor STDOUT antes de executar a lista composta e a restaura depois. Portanto, a exec >lista composta não leva seu efeito além do ponto em que o descritor antigo é restabelecido como STDOUT.

Vamos dar uma olhada na parte relevante de strace bash -c '{ exec >/dev/null; } >/dev/null; echo hi' 2>&1 | cat -n:

   132  open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
   133  fcntl(1, F_GETFD)                       = 0
   134  fcntl(1, F_DUPFD, 10)                   = 10
   135  fcntl(1, F_GETFD)                       = 0
   136  fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
   137  dup2(3, 1)                              = 1
   138  close(3)                                = 0
   139  open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
   140  fcntl(1, F_GETFD)                       = 0
   141  fcntl(1, F_DUPFD, 10)                   = 11
   142  fcntl(1, F_GETFD)                       = 0
   143  fcntl(11, F_SETFD, FD_CLOEXEC)          = 0
   144  dup2(3, 1)                              = 1
   145  close(3)                                = 0
   146  close(11)                               = 0
   147  dup2(10, 1)                             = 1
   148  fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
   149  close(10)                               = 0

Você pode ver como, na linha 134, o descritor 1( STDOUT) é copiado para outro descritor com índice, pelo menos 10(é o que F_DUPFDfaz; ele retorna o descritor mais baixo disponível, começando no número especificado após duplicar nesse descritor). Veja também como, na linha 137, o resultado de open("/dev/null")(descritor 3) é copiado no descritor 1( STDOUT). Finalmente, on-line 147, o antigo STDOUTsalvo no descritor 10é copiado de volta para o descritor 1( STDOUT). O efeito líquido é isolar a alteração para STDOUTon-line 144(que corresponde ao interior exec >/dev/null).


Como o FD 1 é substituído pelo FD 3 na linha 137, por que a linha 141 não aponta 10 para / dev / null?
Joey Pabalinas

A @JoeyPabalinas Line 141 está duplicando o FD 1 (ou seja, stdout) para o próximo descritor disponível após 10 , que acaba sendo 11, como você pode ver no valor de retorno dessa chamada de sistema. 10 é apenas codificado no bash, para que o salvamento do descritor do bash não interfira nos descritores de um dígito que você pode manipular no seu script exec.
DepressedDaniel

Então fcntl (1, F_DUPFD, 10) sempre se refere a STDOUT, não importa para onde o FD 1 esteja apontando atualmente?
Joey Pabalinas

@JoeyPabalinas Não sabe ao certo qual é a sua pergunta. FD 1 ESTÁ EM ESTADO . Eles são a mesma coisa.
DepressedDaniel

Adicionado saída de rastreio completo ao meu post original.
Joey Pabalinas

8

A diferença entre { exec >/dev/null; } >/dev/null; echo "Hi"e { exec >/dev/null; }; echo "Hi"é que o redirecionamento duplo faz dup2(10, 1);antes de fechar o fd 10, que é a cópia do original stdout, antes de executar o próximo comando ( echo).

Isso acontece porque o redirecionamento externo está realmente sobrepondo o redirecionamento interno. É por isso que ele copia de volta o stdoutfd original depois de concluído.


+1 para explicar a diferença de maneira fácil. A resposta de AlexP carece dessa explicação.
Kamil Maciorowski
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.