Por que o bashrc verifica se o shell atual é interativo?

62

Na minha instalação do Arch, /etc/bash.bashrce /etc/skel/.bashrccontenha estas linhas:

# If not running interactively, don't do anything
[[ $- != *i* ]] && return

No Debian, /etc/bash.bashrctem:

# If not running interactively, don't do anything
[ -z "$PS1" ] && return

E /etc/skel/.bashrc:

# If not running interactively, don't do anything
case $- in
    *i*) ;;
      *) return;;
esac

De acordo com man bash, no entanto, os shells não interativos nem lêem esses arquivos:

   When  bash  is  started  non-interactively,  to run a shell script, for
   example, it looks for the variable BASH_ENV in the environment, expands
   its  value if it appears there, and uses the expanded value as the name
   of a file to read and execute.  Bash behaves as if the  following  com
   mand were executed:
          if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi
   but  the value of the PATH variable is not used to search for the file
   name.

Se bem entendi, os *.bashrcarquivos serão lidos apenas se BASH_ENVestiverem definidos para apontá-los. Isso é algo que não pode acontecer por acaso e só ocorrerá se alguém tiver definido explicitamente a variável de acordo.

Isso parece quebrar a possibilidade de os scripts originarem .bashrcautomaticamente o usuário, configurando BASH_ENValgo que pode ser útil. Dado que o bash nunca lerá esses arquivos quando executado de maneira não interativa, a menos que seja explicitamente instruído a fazê-lo, por que os *bashrcarquivos padrão não o permitem?

terdon
fonte
5
Para uma resposta prática, consulte SCP falha sem erro
Gilles 'SO- stop be evil'

Respostas:

69

Esta é uma pergunta que eu ia postar aqui há algumas semanas. Como o terdon , entendi que o a .bashrcé fornecido apenas para shells Bash interativos, portanto não há necessidade de .bashrcverificar se ele está sendo executado em um shell interativo. Confusamente, todas as distribuições que eu uso (Ubuntu, RHEL e Cygwin) tiveram algum tipo de verificação (teste $-ou $PS1) para garantir que o shell atual seja interativo. Eu não gosto de programação de cultos de carga, então comecei a entender o propósito deste código no meu .bashrc.

Bash tem um caso especial para reservatórios remotos

Depois de pesquisar o problema, descobri que os shells remotos são tratados de maneira diferente. Enquanto shells Bash não interativos normalmente não executam ~/.bashrccomandos na inicialização, um caso especial é feito quando o shell é chamado pelo daemon de shell remoto :

O Bash tenta determinar quando está sendo executado com sua entrada padrão conectada a uma conexão de rede, como quando executada pelo daemon de shell remoto, geralmente rshdou pelo daemon de shell seguro sshd. Se o Bash determinar que está sendo executado dessa maneira, ele lê e executa comandos de ~ / .bashrc, se esse arquivo existe e é legível. Isso não será feito se chamado como sh. A --norcopção pode ser usada para inibir esse comportamento e a --rcfileopção pode forçar a leitura de outro arquivo, mas rshdnem sshdgeralmente invoca o shell com essas opções ou permite que sejam especificadas.

Exemplo

Insira o seguinte no início de um controle remoto .bashrc. (Se .bashrcfor originado por .profileou .bash_profile, desative-o temporariamente durante o teste):

echo bashrc
fun()
{
    echo functions work
}

Execute os seguintes comandos localmente:

$ ssh remote_host 'echo $- $0'
bashrc
hBc bash
  • No iin $-indica que o shell não é interativo .
  • Não levando -em $0indica que a casca não é um shell de login .

As funções de shell definidas no controle remoto .bashrctambém podem ser executadas:

$ ssh remote_host fun
bashrc
functions work

Percebi que o ~/.bashrc é originado quando um comando é especificado como argumento para . Isso faz sentido: quando é usado para iniciar um shell de logon regular ou é executado (e só é originado se explicitamente feito por um desses arquivos).sshssh.profile.bash_profile.bashrc

O principal benefício que posso ver de ter .bashrcoriginado ao executar um comando remoto (não interativo) é que as funções do shell podem ser executadas. No entanto, a maioria dos comandos de um típico .bashrcé relevante apenas em um shell interativo, por exemplo, os aliases não são expandidos, a menos que o shell seja interativo.

Transferências remotas de arquivos podem falhar

Isso geralmente não é um problema quando rshou sshé usado para iniciar um shell de logon interativo ou quando shells não interativos são usados ​​para executar comandos. No entanto, pode ser um problema para programas como rcp, scpe sftpque usam shells remotos para transferir dados.

Acontece que o shell padrão do usuário remoto (como o Bash) é implicitamente iniciado ao usar o scpcomando. Não há menção a isso na página de manual - apenas uma menção scpusada sshpara sua transferência de dados. Isso tem a conseqüência de que, se .bashrccontiver algum comando impresso na saída padrão, a transferência de arquivos falhará , por exemplo, scp falhará sem erros .

Veja também este relatório de bug relacionado ao Red Hat de 15 anos atrás, o scp quebra quando existe um comando echo no / etc / bashrc (que acabou sendo fechado como WONTFIX).

Por que scpe sftpfalhar

O SCP (cópia segura) e o SFTP (Protocolo seguro de transferência de arquivos) têm seus próprios protocolos para as extremidades local e remota para trocar informações sobre os arquivos que estão sendo transferidos. Qualquer texto inesperado da extremidade remota é (incorretamente) interpretado como parte do protocolo e a transferência falha. De acordo com uma FAQ do Snail Book

O que muitas vezes acontece, porém, é que há declarações em qualquer sistema ou arquivos de inicialização do shell por usuário no servidor ( .bashrc, .profile, /etc/csh.cshrc, .login, etc.) que mensagens de texto de saída no login, destinados a serem lidos por seres humanos (como fortune, echo "Hi there!", etc.)

Esse código deve produzir apenas saída em logons interativos, quando houver uma ttyanexada à entrada padrão. Se ele não fizer esse teste, ele inserirá essas mensagens de texto onde elas não pertencem: nesse caso, poluindo o fluxo do protocolo entre scp2/ sftpe sftp-server.

A razão pela qual os arquivos de inicialização do shell são relevantes, é que sshd emprega o shell do usuário ao iniciar qualquer programa em nome do usuário (usando, por exemplo, / bin / sh -c "command"). Esta é uma tradição do Unix e tem vantagens:

  • A configuração usual do usuário (alias de comando, variáveis ​​de ambiente, umask etc.) entra em vigor quando comandos remotos são executados.
  • A prática comum de definir o shell de uma conta como / bin / false para desativá-lo impedirá o proprietário de executar qualquer comando, caso a autenticação ainda tenha êxito acidental por algum motivo.

Detalhes do protocolo SCP

Para aqueles interessados ​​nos detalhes de como o SCP funciona, encontrei informações interessantes em Como o protocolo SCP funciona, que inclui detalhes sobre Executando o scp com perfis de shell falante no lado remoto? :

Por exemplo, isso pode acontecer se você adicionar isso ao seu perfil de shell no sistema remoto:

eco ""

Por que simplesmente trava? Isso vem da maneira como scpno modo de origem aguarda a confirmação da primeira mensagem de protocolo. Se não for 0 binário, espera que seja uma notificação de um problema remoto e aguarde que mais caracteres formem uma mensagem de erro até que a nova linha chegue. Como você não imprimiu outra nova linha após a primeira, seu local scppermanece em um loop, bloqueado read(2). Enquanto isso, depois que o perfil do shell foi processado no lado remoto, scpfoi iniciado o modo coletor, que também é bloqueado read(2), aguardando um zero binário que indica o início da transferência de dados.

Conclusão / TLDR

A maioria das instruções típicas .bashrcé útil apenas para um shell interativo - não ao executar comandos remotos com rshou ssh. Na maioria dessas situações, a definição de variáveis ​​de shell, aliases e funções de definição não é desejada - e imprimir qualquer texto para padronizar é ativamente prejudicial ao transferir arquivos usando programas como scpou sftp. Sair depois de verificar se o shell atual é não interativo é o comportamento mais seguro para .bashrc.

Anthony G - justiça para Monica
fonte
bom artigo. mas eu deveria fazer com isso? Eu tenho o check-in pelo .bashrc, mas ainda o scp sai com o código 0 e não copia nada para a caixa remota
ses
@ses Seria melhor fazer uma nova pergunta em que você possa descrever sua situação com mais detalhes.
Anthony G - justice para Monica
16

A página de manual esquece de mencionar que bashtambém fontes .bashrcpara shells remotos não interativos, como em

ssh hostname command

http://git.savannah.gnu.org/cgit/bash.git/tree/shell.c#n1010

 COMMAND        EXECUTE BASHRC
 --------------------------------
 bash -c foo        NO
 bash foo           NO
 foo                NO
 rsh machine ls     YES (for rsh, which calls 'bash -c')
 rsh machine foo    YES (for shell started by rsh) NO (for foo!)
 echo ls | bash     NO
 login              NO
 bash               YES

http://git.savannah.gnu.org/cgit/bash.git/tree/shell.c#n1050

          /* If we were run by sshd or we think we were run by rshd, execute
             ~/.bashrc if we are a top-level shell. */
          if ((run_by_ssh || isnetconn (fileno (stdin))) && shell_level < 2)
            {
              maybe_execute_file (SYS_BASHRC, 1);
              maybe_execute_file (bashrc_file, 1);
Mikel
fonte
Você origina seu .bashrc do seu .bash_profile (ou .profile)?
glenn jackman
Sim, mas esse não é o ponto. Um vazio .bash_profileexibirá o mesmo comportamento.
Mikel
Não tenho certeza do que está me mostrando. Você está dizendo que rsh(que eu nunca usei) fontes ~/.bashrc? E que, por extensão, as distros do Linux o bloqueiam bashapenas no caso de alguém tentar executar um script rsh? ssh servernão deve tocar, .bashrcpois é um shell de login de qualquer maneira. A menos que o seu sistema seja um daqueles de onde provém ~ / .bashrc` ~/.profile. E ssh server commandnem deveria fazer isso. Certamente não está no meu sistema.
terdon
2
Não. Eu o vinculei à bashfonte, não à rshfonte. :) O comentário também se aplica ssh, ele foi escrito há muito tempo. Veja a resposta atualizada com mais fonte.
Mikel
Ah, isso é útil, obrigado. Como eu testaria isso? Tentei adicionar um echona parte superior /etc/bash.bashrce ~/.bashrc(antes dos testes interativos do shell) e depois ssh na máquina de destino com ssh server hostnamee enquanto hostnameestava sendo executado, não vi nenhum dos meus ecos. Será que o meu shell_level(seja o que for) não é <2?
terdon
5

Por convenção, .bashrcé o local onde o usuário armazena a configuração de customização para o shell.

Essas configurações personalizadas podem ser variáveis ​​de ambiente, aliases, prompt sofisticado. Com um shell não interativo, essas coisas não têm sentido. Além disso, um shell não interativo pode ser chamado em muitos contextos, você não tem certeza de que essas variáveis ​​de ambiente podem levar a casos negativos falsos ou mesmo vulneráveis ​​à segurança.

Um exemplo mais próximo é um alias como:

alias cp='cp -i'

Em seguida, pendure seu shell não interativo para sempre.

Portanto, a verificação é executada na parte superior .bashrcpara garantir que não tenhamos problemas.


Como o shell pode ser chamado de shell de logon não interativo , bloquear explicitamente a fonte *bashrcnão faz sentido.

Quando o shell foi chamado como shell de login não-interativo , é fonte /etc/profilee, em seguida fonte o primeiro encontrado em ~/.bash_profile, ~/.bash_logine ~/.profile:

Quando o bash é chamado como um shell de login interativo ou como um shell não interativo com a opção --login , ele primeiro lê e executa comandos do arquivo / etc / profile, se esse arquivo existir. Depois de ler esse arquivo, ele procura ~ / .bash_profile, ~ / .bash_login e ~ / .profile, nessa ordem, e lê e executa comandos do primeiro que existe e é legível.

Nada impede que esses arquivos sejam adquiridos por .bashrcsi, portanto, fazer a verificação interna .bashrcé mais seguro e simplificar as coisas.

cuonglm
fonte
Sim, eu sei disso. É por isso que shells não interativos não originam *bashrcarquivos. Minha pergunta é por que vários fornecedores de Linux se dão ao trabalho de impedir explicitamente que shells não interativos leiam esses arquivos se esses arquivos não são lidos por shells não interativos de qualquer maneira.
terdon
11
@terdon: Como um shell pode ser chamado de shell de logon não interativo , o shell de logon será fonte .bash_profile, o que pode ser fonte .bashrc.
cuonglm
Não, em shells não interativos, a única coisa que é lida é $BASH_ENV. Ambos *profilee *bashrcsão ignorados. Ou, pelo menos, é o que a página de manual diz. No entanto, como Mikel mostrou, a página de manual pode estar mentindo.
terdon
@terdon: Leia minha resposta atualizada com o link nele. Você tentou bash -l -c :invocar um shell de logon não interativo?
cuonglm
Uau. Você está absolutamente correto. Eu li essa parte cerca de --login100 vezes, mas, aparentemente, nunca havia se registrado. Obrigado!
terdon