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.