Existe uma diferença entre "." E "source" no bash, afinal?

38

Eu estava procurando a diferença entre o "." e comandos internos "source" e algumas fontes (por exemplo, nesta discussão e na página de manual do bash ) sugerem que esses são exatamente os mesmos.

No entanto, após um problema com variáveis ​​de ambiente, realizei um teste. Eu criei um arquivo testenv.shque contém:

#!/bin/bash
echo $MY_VAR

No prompt de comando, executei o seguinte:

> chmod +x testenv.sh
> MY_VAR=12345
> ./testenv.sh

> source testenv.sh
12345
> MY_VAR=12345 ./testenv.sh
12345

[observe que o 1º formulário retornou uma string vazia]

Então, essa pequena experiência sugere que há é uma diferença afinal, onde para o comando "source", o ambiente filho herda todas as variáveis do pai um, onde para o "" isso não.

Estou faltando alguma coisa ou esse é um recurso não documentado / reprovado do bash ?

[GNU bash, versão 4.1.5 (1) -release (x86_64-pc-linux-gnu)]

ysap
fonte

Respostas:

68

Resposta curta

Na sua pergunta, o segundo comando não usa nem o .shell interno nem o sourceinterno. Em vez disso, você está realmente executando o script em um shell separado, invocando-o pelo nome, como faria com qualquer outro arquivo executável. Isso fornece a ele um conjunto separado de variáveis ​​(embora, se você exportar uma variável em seu shell pai, ela será uma variável de ambiente para qualquer processo filho e, portanto , será incluída nas variáveis ​​de um shell filho ). Se você alterar /para um espaço, isso o executaria com o .built-in, que é equivalente a source.

Explicação estendida

Esta é a sintaxe do sourceshell interno, que executa o conteúdo de um script no shell atual (e, portanto, com as variáveis ​​do shell atual):

source testenv.sh

Esta é a sintaxe do .built-in, que faz o mesmo que source:

. testenv.sh

No entanto, essa sintaxe executa o script como um arquivo executável, iniciando um novo shell para executá-lo:

./testenv.sh

Isso não está usando o .built-in. Em vez disso, .faz parte do caminho para o arquivo que você está executando. De um modo geral, você pode executar qualquer arquivo executável em um shell, invocando-o com um nome que contenha pelo menos um /caractere. Executar um arquivo no diretório atual, precedê-lo ./é, portanto, a maneira mais fácil. A menos que o diretório atual está na sua PATH, você não pode executar o script com o comando testenv.sh. Isso evita que as pessoas executem acidentalmente arquivos no diretório atual quando pretendem executar um comando do sistema ou algum outro arquivo existente em algum diretório listado na PATHvariável de ambiente.

Como a execução de um arquivo pelo nome (em vez de com sourceou .) o executa em um novo shell, ele terá seu próprio conjunto de variáveis ​​de shell. O novo shell herda as variáveis ​​de ambiente do processo de chamada (que neste caso é o seu shell interativo) e essas variáveis ​​de ambiente se tornam variáveis ​​do shell no novo shell. No entanto, para que uma variável do shell seja passada para o novo shell, um dos seguintes deve ser o caso:

  1. A variável shell foi exportada, fazendo com que ela seja uma variável de ambiente. Use o exportshell interno para isso. No seu exemplo, você pode usar export MY_VAR=12345para definir e exportar a variável em uma etapa ou, se já estiver definida, pode simplesmente usar export MY_VAR.

  2. A variável shell é explicitamente configurada e transmitida para o comando que você está executando, fazendo com que seja uma variável de ambiente pela duração do comando que está sendo executado. Isso geralmente faz com que:

    MY_VAR=12345 ./testenv.sh

    Se MY_VARé uma variável do shell que não foi exportada, você pode até executar testenv.shcom MY_VARpassado como uma variável de ambiente configurando-a para si mesma :

    MY_VAR="$MY_VAR" ./testenv.sh

./ Sintaxe para scripts requer uma linha Hashbang para funcionar (corretamente)

A propósito, observe que, quando você invoca um executável por nome, como acima (e não com os built-ins .ou ou sourceshell), qual programa shell é usado para executá-lo geralmente não é determinado por qual shell você o está executando. . Em vez de:

  • Para arquivos binários, o kernel pode ser configurado para executar arquivos desse tipo específico. Ele examina os dois primeiros bytes do arquivo em busca de um "número mágico" que indica que tipo de executável binário é. É assim que os binários executáveis ​​são capazes de executar.

    Isso é, obviamente, extremamente importante, porque um script não pode ser executado sem um shell ou outro intérprete, que é um binário executável! Além disso, muitos comandos e aplicativos são binários compilados em vez de scripts.

    ( #!é a representação em texto do "número mágico" indicando um texto executável.)

  • Para arquivos que devem ser executados em um shell ou em outra linguagem interpretada, a primeira linha se parece com:

    #!/bin/sh

    /bin/shpode ser substituído por qualquer outro shell ou intérprete destinado a executar o programa. Por exemplo, um programa Python pode começar com a linha:

    #!/usr/bin/python

    Essas linhas são chamadas hashbang, shebang e vários outros nomes semelhantes. Veja esta entrada do FOLDOC , este artigo da Wikipedia e #! / Bin / sh é lida pelo intérprete? Para maiores informações.

  • Se um arquivo de texto estiver marcado como executável e você o executar a partir do seu shell (como ./filename), mas não começar #!, o kernel falhará ao executá-lo. No entanto, vendo que isso aconteceu, seu shell tentará executá-lo passando seu nome para algum shell. Existem poucos requisitos sobre o que é o shell ( "o shell deve executar um comando equivalente a ter um shell invocado ..." ). Na prática , algumas conchas - incluindo bash* - executam outra instância de si mesmas, enquanto outras usam/bin/sh. Eu recomendo que você evite isso e use uma linha hashbang (ou execute o script passando-o para o intérprete desejado, por exemplo, bash filename).

    * Manual do GNU Bash , 3.7.2 Pesquisa e Execução de Comandos : "Se esta execução falhar porque o arquivo não está no formato executável e o arquivo não é um diretório, é considerado um shell script e o shell o executa como descrito em scripts de shell ".

Eliah Kagan
fonte
2
O que eu achei útil sourceé que as funções se tornaram disponíveis no bash sem a necessidade de carregar ou iniciar novamente. Exemplo #!/bin/bash function olakease {echo olakease;}. Depois de carregá-lo, source file.shvocê pode ligar diretamente olakeasedo bash. Eu realmente gosto daquilo. Fonte executa então cargas monte de coisas, o ponto .é apenas para execução e é algo como o usobash file.sh
m3nda
2
@ erm3nda também .tem esse comportamento: . file.she source file.shfaz exatamente a mesma coisa, incluindo as funções de retenção definidas em file.sh. (Talvez você está pensando em ./file.sh, o que é diferente, mas que não. Não usar o .builtin, em vez disso, .é parte do caminho.)
Elias Kagan
Oh !, não li atentamente o arquivo. [Space]. Muito obrigado mach.
M3nda
13

Sim, você está perdendo alguma coisa.

Eu acho que você está confundindo o '.' isso significa diretório atual, como em ./testenv.she '.' isso significa source(que é um comando interno). Então, no caso em que '.' significa sourceque seria . ./testenv.sh. Faz sentido?

Então tente o seguinte:

MY_VAR=12345 
. ./testenv.sh
user1477
fonte
2
O ./comando diz exatamente onde o arquivo, sem ele, o bash procurará o PATH primeiro e, em seguida, tente o diretório atual se não o encontrar. Se o bash estiver em execução no modo POSIX, e você não fornecer um caminho para o arquivo (como ./), ele somente pesquisará em PATH e falhará em encontrar o arquivo se o diretório atual não estiver em PATH.
Geirha
@geirha Sim, você está certo, source(e .) realmente verifica $ PATH primeiro, mesmo que eles não estejam realmente executando o script no sentido usual. Meu (antigo) comentário estava incorreto.
Eliah Kagan
Curto e direto ao ponto +1
David Morales