Como o redirecionamento do arquivo bash para o padrão difere do shell ('sh') no Linux?

8

Eu escrevi um script que alterna os usuários durante a execução e os executei usando o redirecionamento de arquivos para padrão. Assim, user-switch.sh is ...

#!/bin/bash

whoami
sudo su -l root
whoami

E executá-lo com bash me dá o comportamento que eu espero

$ bash < user-switch.sh
vagrant
root

No entanto, se eu executar o script com sh , obtenho uma saída diferente

$ sh < user-switch.sh 
vagrant
vagrant

Por que bash < user-switch.sh fornece resultados diferentes de sh < user-switch.sh ?

Notas:

  • acontece em duas caixas diferentes que executam o Debian Jessie
por amorphid 12.04.2017 / 22:04

2 respostas

12

Um script semelhante, sem sudo , mas resultados semelhantes:

$ cat script.sh
#!/bin/bash
sed -e 's/^/--/'
whoami

$ bash < script.sh
--whoami

$ dash < script.sh
itvirta

Com bash , o restante do script entra como entrada para sed , com dash , o shell o interpreta.

Executando strace sobre eles: dash lê um bloco do script (oito kB aqui, mais do que suficiente para manter todo o script) e, em seguida, gera sed :

read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 8192) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...

O que significa que o filehandle está no final do arquivo e sed não verá nenhuma entrada. A parte restante sendo armazenada em buffer em dash . (Se o script fosse maior que o tamanho de bloco de 8 kB, a parte restante seria lida por sed .)

Bash, por outro lado, busca voltar ao final do último comando:

read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 36) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
...
lseek(0, -7, SEEK_CUR)                  = 29
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...

Se a entrada vem de um pipe, como aqui:

$ cat script.sh | bash

O rebobinamento não pode ser feito, pois canos e soquetes não são procurados. Neste caso, o Bash volta a ler a entrada um caractere de cada vez para evitar a sobreposição. ( fd_to_buffered_stream() in input.c ) Fazendo uma chamada completa do sistema para cada byte não é muito eficaz em princípio. Na prática, não creio que as leituras sejam um grande gasto comparativamente, por ex. ao fato de que a maioria das coisas que o shell envolve envolve a criação de novos processos.

Uma situação semelhante é esta:

echo -e 'foo\nbar\ndoo' | bash -c 'read a; head -1'

O subshell precisa garantir que read apenas leia a primeira nova linha, para que head veja a próxima linha. (Isso funciona com dash também.)

Em outras palavras, o Bash vai a comprimentos adicionais para suportar a leitura da mesma fonte para o próprio script e para os comandos executados a partir dele. dash não. O zsh e ksh93 empacotados no Debian vão com o Bash.

    
por 12.04.2017 / 22:31
12

O shell está lendo o script da entrada padrão. Dentro do script, você executa um comando que também deseja ler a entrada padrão. Qual entrada vai para onde? Você não pode dizer com segurança .

A maneira como os shells funcionam é que eles lêem um trecho do código-fonte, analisam-no e, se encontrarem um comando completo, executem o comando, em seguida, prossigam com o restante do trecho e o restante do arquivo. Se o chunk não contiver um comando completo (com um caractere de finalização no final - acho que todos os shells foram lidos até o final de uma linha), o shell lê outro trecho e assim por diante.

Se um comando no script tentar ler o mesmo descritor de arquivo do qual o shell está lendo o script, o comando encontrará o que vier depois do último bloco que leu. Este local é imprevisível: depende do tamanho do fragmento escolhido pelo shell, e isso pode depender não apenas do shell e de sua versão, mas também da configuração da máquina, da memória disponível, etc.

Bash busca o final do código-fonte de um comando no script antes de executar o comando. Isso não é algo com o qual você pode contar, não apenas porque outras shells não o fazem, mas também porque isso só funciona se o shell estiver lendo de um arquivo normal. Se o shell estiver lendo de um canal (por exemplo, ssh remote-host.example.com <local-script-file.sh ), os dados lidos serão lidos e não poderão ser lidos.

Se você quiser passar a entrada para um comando no script, precisará fazê-lo explicitamente, normalmente com um documento aqui . (Um documento aqui é geralmente o mais conveniente para entrada multi-linha, mas qualquer método serve.) O código que você escreveu só funciona em alguns shells, somente se o script é passado como entrada para o shell a partir de um arquivo regular; Se você esperava que o segundo whoami fosse passado como entrada para sudo … , pense novamente, lembrando que na maioria das vezes o script não é passado para a entrada padrão do shell.

#!/bin/bash
whoami
sudo su -l root <<'EOF'
whoami
EOF

Observe que nesta década você pode usar sudo -i root . A execução de sudo su é um truque do passado.

    
por 13.04.2017 / 01:37