Por que o shell interativo do bash, por padrão, escreve seu prompt e ecoa sua entrada interativa para stderr?

4

Acabei de notar que o shell interativo do bash, por padrão, grava o prompt e ecoa tudo o que você digita no descritor de arquivo 2 (stderr). Isto pode ser verificado executando o bash dentro do strace.

Qual é o motivo disso? Por que não escreve essas coisas para stdout?

    
por Michael Martinez 21.07.2017 / 21:15

2 respostas

6

As versões originais do UNIX 5-7 podem ser vistas fazendo a mesma coisa. ( UFD output = 2; link )

time é / foi construído, e é definitivamente útil que seja gerado para stderr. No entanto time não é um dos builtins da V5.

Eu não acho que tenha havido grandes razões não para gravar saída de terminal para stderr. A saída do shell era claramente uma mensagem de erro ou saída destinada apenas a terminais interativos. Não foi necessário redirecionar o stdout para redirecionar a saída interativa.

Embora stderr tenha sido introduzido em V6, não V5, em V5 sh manualmente dup() s stdout para FD 2 após fechar o antigo FD 2 se necessário. Parece que eles já encontraram a necessidade de imprimir mensagens de erro, por exemplo if exec() falhou ao tentar iniciar um comando como foo > output .

Agora, preste atenção em como compact é o código unix histórico. É deliberadamente mantido curto, porque não havia necessariamente muita RAM física.

Existe uma única função prs() para imprimir cadeias. Não é necessário um parâmetro FD. Ele imprime mensagens de erro para FD 2, e as outras strings também estão OK para imprimir em FD 2, então simplesmente imprime em FD 2 incondicionalmente. Código curto, que compila poucas instruções, portanto, usando o mínimo de RAM.

E uma vez que as coisas estão por aí por algum tempo, mudando elas sempre correm o risco de quebrar mais coisas do que melhora.

O que me intriga é por que os desenvolvedores de python perceberam isso e copiaram - IMO é um fato bem obscuro. Talvez isso implique uma razão adicional que não encontrei.

err(s)
char *s;
{

    prs(s);
    prs("\n");
    if(promp == 0) {
        seek(0, 0, 2);
        exit();
    }
}

prs(as)
char *as;
{
    register char *s;

    s = as;
    while(*s)
        putc(*s++);
}

putc(c)
{

    write(2, &c, 1);
}
    
por 21.07.2017 / 23:42
2

Um shell é interativo quando não está interpretando um script (possivelmente em linha com -c ) e seu stdin é um terminal (mas veja abaixo para shells POSIX)

Nesse caso, o que queremos é que o prompt (e o echo do que você digita para shells que possuem seu próprio editor de linha) sejam exibidos no mesmo terminal. O problema é que não é garantido que stdin esteja aberto no modo de leitura + gravação, portanto, a saída do prompt no stdin não seria confiável. Também no meio da sessão interativa, pode-se querer fazer exec < other-file , o que também quebraria a exibição do prompt.

Uma coisa sensata a ser feita e qual zsh seria determinar qual terminal é (usando ttyname() ) e reabri-lo para leitura + gravação (em um fd dedicado, acima de 10) para interação do usuário. Usando um descritor de arquivos aberto em /dev/tty (o terminal de controle, que na grande maioria dos casos estará se referindo ao mesmo terminal que o stdin se stdin é um terminal) faria sentido para mim, mas não parece qualquer shell faz isso (possivelmente para cuidar dos casos em que não há terminal de controle, o que pode acontecer em alguns casos, como shells de recuperação no console).

No entanto, a especificação POSIX diz que um shell só é interativo se, além dos requisitos descritos acima, stderr também é um terminal e POSIX requer que os prompts sejam escritos em stderr .

bash tem como objetivo a conformidade POSIX, portanto, precisa seguir esses requisitos.

Suponho que a razão seja histórica. A especificação sh do POSIX é baseada em ksh88 e é assim que ksh88 se comportou e o shell Bourne antes dele. (mesmo que ttyname() já existisse no Unix V7 onde o shell Bourne foi lançado).

É comum que a interação do usuário para aplicativos de terminal (como os prompts de rm / mv / find -ok ...) esteja em stdin + stderr. stderr seria aberto no modo de gravação quando aberto, e em sessões de terminal apontaria para o terminal. stdout é para a saída normal do comando, é importante mantê-lo separado das mensagens de interação do usuário para que se possa usar o redirecionamento para armazenar ou pós-processar a saída normal. Tê-lo em stderr (um fd conhecido antecipadamente específico) em vez de um fd interno aberto em /dev/tty torna mais fácil remover essas mensagens (executando o comando com 2> /dev/null por exemplo) ou usar o comando não interativamente. / p>

No caso de um shell, porém, não é particularmente útil. Executar sh 2> /dev/null torna o shell não interativo (o que significa que os prompts não são exibidos, mas têm muitos outros efeitos colaterais). Isso significa que é possível desabilitar os prompts com exec 2> /dev/null , mas isso teria o efeito colateral de também descartar todos os erros de comando, a menos que você execute cada comando com cmd 2> something . Esvaziar PS1, PS2, PS3 e PS4 seria muito melhor.

Isso permite que a entrada do usuário venha de um terminal e a saída vá para um terminal diferente, mas não vejo por que alguém iria querer fazer isso.

Um possível motivo é que seria mais infalível:

  • /dev/tty como visto acima pode não funcionar nos casos de canto em que não há terminal de controle,
  • ttyname() pode não funcionar quando /dev não está preenchido corretamente ou quando usa chroot s.

Note que é pior em alguns outros shells. csh , tcsh , rc , es , akanga , fish exibe o prompt e o eco do que você digita no stdout (embora para fish não o prompt se stdout não for um terminal e csh , que não possui um editor de linhas, não gera nenhum echo (que é cuidado pela disciplina de linha tty do dispositivo terminal)).

    
por 22.07.2017 / 11:08