leu -e em um sub-script

2

Vamos imaginar que eu tenha script1.sh e script2.sh com as seguintes fontes:

Script 1:

#!/bin/bash
# script 1
echo "Doing some stuff..."
bash script2.sh
echo "Done"

E Script 2:

#!/bin/bash
# script 2
printf "\r  [ 3[0;33m?3[0m ] What is your name? "
read -e name
echo "$name"

E chamar bash script1.sh funciona normalmente.

Estou tentando fazer algo semelhante aqui e executando wget -O - https://raw.githubusercontent.com/caarlos0/dotfiles/install/script/install | bash , mas quando executa o script script/bootstrap , sai no read -e .

Eu tentei a sinalização -i para forçar a interação sem sucesso.

Alguma opinião?

    
por caarlos0 17.03.2015 / 01:21

3 respostas

0

Pense: você está executando bash com um canal para a entrada padrão. E então você está pedindo para ler da entrada padrão. Isso não vai funcionar.

Use um pipe nomeado em vez disso?

ETA: Ou talvez não…

mkfifo ~/tmp-pipe
wget -O - https://raw.githubusercontent.com/caarlos0/dotfiles/install/script/install > ~/tmp-pipe &
bash ~/tmp-pipe
rm ~/tmp-pipe

A essa altura, se você também quiser ter certeza de que não está atropelando algum arquivo existente, é melhor usar um arquivo temporário: haverá menos problemas:

P=$( mktemp )
wget -O - https://raw.githubusercontent.com/caarlos0/dotfiles/install/script/install > $P &
bash $P
rm $P

Sim. Eu não sei.

    
por 17.03.2015 / 01:39
0

A entrada padrão do processo bash é o script que está sendo baixado pelo wget. Portanto, a chamada para read lê uma linha desse script ou não lê nada se o bash já tiver terminado o script.

wget -O - … | bash pode ser um simples, mas não é uma boa ideia. Se o download for interrompido no meio, o bash executará a parte do script que pode ser baixada e o erro do wget poderá ser difícil de detectar. Seria melhor primeiro baixar o arquivo e executá-lo:

wget https://…/install
bash install
rm install

Para evitar criar explicitamente um arquivo temporário, você pode usar a substituição de processo. Isso requer que seu shell interativo suporte a substituição do processo: deve ser bash, ksh93 ou zsh. Isso sofre da mesma desvantagem do pipe em que o script será executado mesmo se o download for interrompido, mas ele não captura a entrada padrão, que continua sendo o terminal.

bash <(wget -O - …)

Se você quiser que seu script suporte o método wget -O - … | bash pipe, você pode fazer o script ler a partir do terminal, colocando exec </dev/tty . No entanto, esta é uma má idéia que fará com que muitos usuários te xingem, porque torna impossível automatizar a execução do script simplesmente alimentando as respostas em sua entrada padrão - em vez disso, seria necessário usar uma ferramenta como expect para executar o script em um terminal, mesmo para uso automatizado.

A opção -i é irrelevante: o bash não está sendo executado de forma interativa porque está executando um script e a entrada padrão não é um terminal porque é redirecionada de um canal. Ativar a opção -i faz com que o bash finja que está sendo usado interativamente, mesmo que esteja sendo passado por um script em sua entrada padrão; isso não muda o fato de que a entrada padrão não é o terminal.

A propósito, isso não tem nada a ver com o fato de que o script é dividido em dois. Você veria exatamente as mesmas coisas se o código do script 2 estivesse no script principal.

    
por 17.03.2015 / 02:43
0

Outros mencionaram que sua entrada padrão está sendo substituída por seu script transmitido. Este é um problema comum, mas é facilmente resolvido:

bash 3<&0 <<\SCRIPT          #ref stdin in fd 3, replace stdin w/ SCRIPT
    echo Doing some stuff... #do your stuff
    printf "\r  [ 3[0;33m?3[0m ] What is your name? "
    read -e name <&3         #read from original stdin
    echo "$name"
    echo Done
SCRIPT

Na sequência acima eu uso um heredoc para substituir seu script canalizado. Porque um pipe ainda não substituiu o stdin de bash com o que quer que fosse antes (geralmente um terminal) o acima é apenas um pouco menos complicado que o seu script precisará ser. Mas eu queria demonstrar como isso funcionaria, e essa é a maneira mais simples.

Quando bash é invocado, todos os processos executados posteriormente herdarão todos os seus fds - a menos que você explicitamente >&- os feche. Portanto, se você fizer apenas um redirecionamento simples no momento da chamada, esse redirecionamento ocorrerá em cascata para todos os filhos. Duping fd 0 a fd 3 acima torna possível mais tarde read <&3 dessa mesma fd sem ter que fazer nada que chateie como </dev/tty - embora a última coisa possa ser uma boa ideia no caso de você ter uma boa razão para interaja com um usuário em um teclado, em oposição ao que esse usuário pode enviar para você.

De qualquer forma, o stdout ainda irá para o mesmo local - assim, todo o material echo/printf será impresso em qualquer fluxo de saída, mas bash receberá stdin do script transmitido e quaisquer outras referências a ele precisarão ser explícitas.

Com o seu cachimbo, você precisará de mais uma camada:

{ wget ... | bash; } 3<&0

Isso funcionará da mesma maneira. Para fazer isso, você não precisa referenciar explicitamente o <&3 stream para o read (ou qualquer outro comando) :

{   {   echo "script$$(){"
        wget ...
        printf "\n} <&3; script$$\n"
} |     bash
} 3<&0

Dessa forma, você realmente define uma função que contém todo o seu script canalizado e redireciona explicitamente o seu stdin salvo para o stdin da função uma vez que você tenha lido tudo, então você o chama. Assim, todos os comandos dentro tratam o <&3 como seu stdin e você não precisa alterar seu conteúdo de forma alguma para referenciá-lo, mas você ainda pode ler tudo em uma entrada padrão.

    
por 17.03.2015 / 09:45