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

14

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

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

"Oi" é impresso.

Eu estava com a impressão de que {} lista de comandos não é considerada um subshell a menos que seja parte de um pipeline, então o exec >/dev/null ainda deve ser aplicado dentro do ambiente shell atual em mente.

Agora, se você alterar para:

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

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

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

que não dará saída.

Eu tentei criar um script e corrigi-lo, mas ainda não sei exatamente o que está acontecendo aqui.

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

EDITAR: Adicionando minha saída de 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
    
por Joey Pabalinas 20.12.2016 / 22:45

3 respostas

18

Vamos seguir

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

passo a passo.

  1. Existem dois comandos:

    a. { 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 de { exec >/dev/null; } >/dev/null é a seguinte:

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

    b. Então, o shell executa { exec >/dev/null; } .

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

  3. Quando o primeiro comando for concluído, o shell executará echo "Hi" . A saída padrão é onde quer que esteja antes do primeiro comando.

por 20.12.2016 / 23:00
13

Para não usar um sub-shell ou subprocesso, quando a saída de uma lista composta {} for canalizada > , o shell salvará o descritor STDOUT antes de executar a lista composta e restaurá-la depois. Assim, o exec > na lista composta não carrega seu efeito além do ponto em que o antigo descritor é reintegrado 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 em outro descritor com índice de pelo menos 10 (é o que F_DUPFD faz; ele retorna o descritor mais baixo disponível a partir do dado número após duplicar naquele descritor). Veja também como, na linha 137, o resultado de open("/dev/null") (descritor 3 ) é copiado no descritor 1 ( STDOUT ). Finalmente, na linha 147 , o antigo STDOUT salvo no descritor 10 é copiado de volta para o descritor 1 ( STDOUT ). O efeito líquido é isolar a alteração em STDOUT na linha 144 (que corresponde ao exec >/dev/null interno).

    
por 20.12.2016 / 22:54
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 cobrindo o redirecionamento interno. É por isso que copia de volta o original stdout fd depois de concluído.

    
por 21.12.2016 / 00:43