Eu escrevi um script que alterna usuários durante a execução e o executei usando o redirecionamento de arquivo para o padrão user-switch.sh
.
#!/bin/bash
whoami
sudo su -l root
whoami
E executá-lo bash
me dá o comportamento que espero
$ bash < user-switch.sh
vagrant
root
No entanto, se eu executar o script com sh
, obterei uma saída diferente
$ sh < user-switch.sh
vagrant
vagrant
Por que está bash < user-switch.sh
fornecendo resultados diferentes de sh < user-switch.sh
?
Notas:
- acontece em duas caixas diferentes executando o Debian Jessie
Responder1
Um script semelhante, sem sudo
, mas com resultados semelhantes:
$ cat script.sh
#!/bin/bash
sed -e 's/^/--/'
whoami
$ bash < script.sh
--whoami
$ dash < script.sh
itvirta
Com bash
, o resto do script vai como entrada para sed
, com dash
, o shell o interpreta.
Executando strace
neles: dash
lê um bloco do script (oito kB aqui, mais que o suficiente para conter o script inteiro) e então 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 dentro do arquivo dash
. (Se o script fosse maior que o tamanho do bloco de 8 kB, a parte restante seria lida por sed
.)
O Bash, por outro lado, volta 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 vier de um tubo, como aqui:
$ cat script.sh | bash
o rebobinamento não pode ser feito, pois os tubos e tomadas não são pesquisáveis. Neste caso, Bash volta a ler a entradaum personagem por vezpara evitar leituras exageradas. (fd_to_buffered_stream()
eminput.c
) Fazer uma chamada completa do sistema para cada byte não é muito eficaz em princípio. Na prática, não acho que as leituras sejam uma grande sobrecarga em comparação, por exemplo, com o fato de que a maioria das coisas que o shell faz envolve a geração de processos totalmente novos.
Uma situação semelhante é esta:
echo -e 'foo\nbar\ndoo' | bash -c 'read a; head -1'
O subshell deve garantir que read
apenas leia a primeira nova linha, para que head
veja a próxima linha. (Isso também funciona dash
.)
Em outras palavras, o Bash se esforça ainda mais para oferecer suporte à leitura da mesma fonte para o script em si e para comandos executados a partir dele. dash
não. O zsh
, e ksh93
empacotado no Debian vai com o Bash nisso.
Responder2
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 irá para onde?Você não pode dizer com segurança.
A maneira como os shells funcionam é que eles leem um pedaço do código-fonte, analisam-no e, se encontrarem um comando completo, executam o comando e prosseguem com o restante do pedaço e o restante do arquivo. Se o pedaço não contiver um comando completo (com um caractere de terminação no final - acho que todos os shells são lidos até o final de uma linha), o shell lê outro pedaço 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 pedaço lido. Esta localização é imprevisível: depende do tamanho do pedaço que o shell escolheu, e isso pode depender não apenas do shell e de sua versão, mas da configuração da máquina, da memória disponível, etc.
Bash procura o final do código-fonte de um comando no script antes de executar o comando. Isso não é algo com que você possa contar, não apenas porque outros shells não fazem isso, mas também porque isso só funciona se o shell estiver lendo 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 deslidos.
Se quiser passar a entrada para um comando no script, você precisará fazê-lo explicitamente, normalmente com umaqui documento. (Um documento aqui geralmente é o mais conveniente para entrada multilinha, mas qualquer método serve.) O código que você escreveu funciona apenas em alguns shells, somente se o script for passado como entrada para o shell a partir de um arquivo normal; se você esperava que o segundo whoami
fosse passado como entrada para sudo …
, pense novamente, tendo em mente 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
. Correr sudo su
é um truque do passado.