Por que não consigo abrir um shell de um processo em pipeline?

4

Eu resumi meu problema no seguinte código:

#include <stdio.h>

int main(){
    char buffer[256];

    printf("Enter input: ");
    scanf("%s", buffer);

    system("/bin/sh");
    return 0;
}

Se eu executar este programa com a entrada do usuário, obtenho:

user@ubuntu:~/testing/temp$ ./main
Enter input: Test
$ 

Com a última linha sendo o shell que o programa iniciou.

Mas se eu executar o programa com entrada proveniente do pipeline:

user@ubuntu:~/testing/temp$ echo 'test' | ./main
Enter input: user@ubuntu:~/testing/temp$

O programa não parece abrir o shell.

Depois de alguns ajustes, percebi que, se fizesse isso:

user@ubuntu:~/testing/temp$ (python -c "print 'test'" ; echo 'ls') | ./main
a.out  main  main.c
Enter input: user@ubuntu:~/testing/temp

Consegui executar o comando ls no shell aberto.

Então, duas perguntas:

  1. Por que o shell não abre como no primeiro caso?
  2. Como posso lidar com isso? É muito inconveniente ter que decidir quais comandos executar antes executar o programa: Eu prefiro ter um shell onde eu possa escolher dinamicamente quais comandos executar.
por Daniel 12.05.2015 / 21:13

3 respostas

3

  1. Why doesn't the shell open like it did in the first case?

No primeiro caso, stdin é um terminal e o shell é interativo. Aguarda seus comandos etc.

No segundo caso, stdin é um canal e o shell não é interativo. Seu programa consome a primeira linha em stdin (ou seja, a string test\n ), então o shell tenta ler stdin e vê EOF . Ele sai, porque é isso que os programas que recebem EOF de entrada devem fazer.

No terceiro caso, o shell novamente não é interativo, pelo mesmo motivo. Seu scanf() consome a primeira linha em stdin (ou seja, test\n ), então o shell lê ls . O shell executa ls , tenta ler mais comandos, vê EOF e sai.

  1. How can I deal with this?

Se por "lidar com isso" você quer dizer executar um shell interativo quando stdin está conectado a um pipe, a solução é usar um pty(7) .

    
por 12.05.2015 / 21:27
0

Seu programa C se comportará de maneira muito semelhante a (read x; /bin/sh) .

Se você acabou de digitar isso cortado em uma linha de comando, então seu padrão a entrada está conectada ao teclado do seu terminal; uma linha será lida, e, em seguida, sh lerá mais linhas até uma condição de fim de arquivo ocorre (normalmente, pode-se pressionar Ctrl-D para causar um).

No exemplo em que você canaliza para o seu programa, o tamanho da entrada é limitado; é o que você coloca lá e nada mais: as linhas depois do primeiro, se houver, será interpretado por sh , que sairá.

Você poderia simular o caso sem tubo usando cat para encaminhar sua entrada de teclado para sh :

(echo test; cat) | (read x; /bin/sh)

ou talvez:

(echo test; cat) | ./main

Isso provavelmente não será tão bom quanto executar um shell diretamente; detectando que sua entrada não é um terminal, provavelmente desativará seus recursos de edição de linha.

    
por 12.05.2015 / 21:32
0

Para executar um shell interativo conectado a um pipe, você só precisa torná-lo interativo.

input | /bin/sh -i

O shell não precisa de um terminal para ser interativo - ele precisa de entrada interativa. Na maior parte, o comportamento do shell quando executado interativamente tem muito pouco a ver com terminais - (pelo menos de acordo com spec ) - e geralmente difere do comportamento de um shell não interativo em relação ao tratamento de erros, mais do que qualquer outra coisa. Os shells interativos tendem a não sair em condições de erro que, de outra forma, causariam o encerramento de um shell não interativo. Um shell deve padrão para o modo interativo, se a entrada vier de um terminal.

Algumas camadas parecem requerer entrada de terminal para uso interativo, mas isso é uma ilusão. Na verdade, esses shells normalmente funcionam muito parecido com o seu caso de exemplo sob o capô - bash , por exemplo, configura readline para manipular o terminal i / o nitty-gritty em seu nome, zsh invoca sua linha ZLE editor, e dash (se não compilado com a opção de tempo de criação SMALL ) links na biblioteca libedit do BSD. Esses editores de linha leem e processam a entrada do terminal em algo parecido com um shell script linha por linha, que o shell então executa conforme apropriado.

Você não está tendo problemas com nenhum desses editores. A julgar pelo seu prompt e sua chamada execve, você está chamando um dash que é compilado com a opção em tempo de criação SMALL (o padrão da Debian) nesse caso ele apenas funcionará - mas as únicas funções de edição de linha que você pode obter são aquelas fornecidas nativamente pela disciplina de linha do terminal (veja stty ) .

Seu problema é que o shell não possui nenhuma entrada - quando ele detecta EOF, ele morre como já foi observado em outro lugar. Você pode fazer ...

input | /bin/sh -i -o ignoreeof

Mas você provavelmente não gostará dos resultados. dash não fará o quit na décima coisa consecutiva de leitura nula que alguns shells fazem - apenas imprimirá ...

Type 'exit' to exit the shell

... para stderr para sempre . Um pouco melhor poderia ser ...

cat input - | /bin/sh -i

... para concatenar os comandos do shell no arquivo input com cat ' - stdin para seu stdout e, em seguida, para executar os resultados em um /bin/sh interativo. Isso funcionará - embora você queira garantir a configuração da stty line-discipline para algo como um estado canônico e com uma chave erase apropriada para que você possa pelo menos obter um backspace funcional. Isso provavelmente já está definido corretamente - mas vale a pena verificar de qualquer maneira.

Outra maneira ...

echo ": some command; exec <$(tty)" | /bin/sh -i

Os métodos acima funcionarão porque o pipeline é o trabalho atual em primeiro plano no terminal de controle - seu pipeline atualmente possui a entrada do terminal e cat está realmente lendo. Isso é contrastado por python -c e echo , que não o lêem - eles só transmitem a saída como gerada pelos argumentos da linha de comando - e, assim, quando a saída termina, também ocorre a entrada do seu shell. Isso é verdade a menos que o shell seja instruído a procurar informações em outro lugar, como acontece com echo no segundo exemplo.

O terminal é apenas uma fonte possível de entrada de muitos para um shell - e pode ser tratado de muitas maneiras diferentes. O líder de sessão do seu terminal - parece que bash - aguarda o término do controle de terminal do seu pipeline para que ele possa recuperar o controle e fazer a próxima tarefa. Esta informação será passada para ela como um sinal assíncrono - é para isso que servem os terminais. Os terminais multiplexam um par de exibição / entrada em qualquer número de processos de leitura / impressão. Eles fazem isso muito bem.

Por exemplo:

$ PS1='bgsh1: ' sh -i +m & PS1='bgsh2: ' sh -i +m &
$ bgsh1: bgsh2: 
[2] + Stopped (tty input)        sh -i +m
[1] - Stopped (tty input)        sh -i +m
$ i=0; while [ "$((i+=1))" -lt 5 ]; do fg "%$(((i%2)+1))"; done
sh -i +m

bgsh2: var=something
bgsh2: kill -TSTP $$
sh -i +m

bgsh1: var=else
bgsh1: kill -TSTP $$
sh -i +m
bgsh2: echo $var; kill -TSTP $$
something
sh -i +m
bgsh1: echo $var; kill -TSTP $$
else
[1] + Stopped                    sh -i +m
[2] - Stopped                    sh -i +m
$ 
    
por 13.05.2015 / 08:53