Quais são as diferenças entre executar scripts shell usando “arquivo de origem.sh”, “./arquivo.sh”, “sh arquivo.sh”, “. ./file.sh ”?

13

Dê uma olhada no código:

#!/bin/bash
read -p "Eneter 1 for UID and 2 for LOGNAME" choice
if [ $choice -eq 1 ]
then
        read -p "Enter UID:  " uid
        logname=`cat /etc/passwd | grep $uid | cut -f1 -d:`
else
        read -p "Enter Logname:  " logname
fi
not=`ps -au$logname | grep -c bash`
echo  "The number of terminals opened by $logname are $not"

Este código é usado para descobrir o número de terminais abertos por um usuário no mesmo PC. Agora, existem dois usuários conectados, digamos x e y. No momento, estou logado como y e há 3 terminais abertos no usuário x. Se eu executar esse código em y usando maneiras diferentes, como mencionado acima, os resultados serão:

$ ./file.sh
The number of terminals opened by x are 3

$ bash file.sh
The number of terminals opened by x are 5

$ sh file.sh
The number of terminals opened by x are 3

$ source file.sh
The number of terminals opened by x are 4

$ . ./file.sh
The number of terminals opened by x are 4

Nota: Passei 1 e uid 1000 para todos esses executáveis.

Agora você pode, por favor, explicar as diferenças entre todos estes?

Ramana Reddy
fonte
a diferença é qual shell é executado. sh não é bash
j0h 25/03
2
As duas últimas execuções também são diferentes porque você está executando no mesmo contexto. Mais aqui
Zaka Elab 25/03
Estou tentando contar o número de instâncias do bash (aqui é igual ao número de terminais) aberto pelo outro usuário (não o mesmo usuário em que efetuamos login) e você poderia explicar por que um número diferente ocorreu em cada caso
Ramana Reddy
@RamanaReddy o outro usuário pode ter executado um script ou iniciado uma nova guia. Quem sabe?
Muru

Respostas:

21

A única grande diferença é entre a fonte e a execução de um script. source foo.shirá fornecê-lo e todos os outros exemplos que você mostrar estão executando. Em mais detalhes:

  1. ./file.sh

    Isso executará um script chamado file.shque está no diretório atual ( ./). Normalmente, quando você executa command, o shell procura nos diretórios do seu $PATHarquivo executável chamado command. Se você fornecer um caminho completo, como /usr/bin/commandou ./command, o $PATHserá ignorado e esse arquivo específico será executado.

  2. ../file.sh

    É basicamente o mesmo que, ./file.shexceto que, em vez de procurar no diretório atual file.sh, ele está procurando no diretório pai ( ../).

  3. sh file.sh

    Isso é equivalente a sh ./file.sh, como acima, executará o script chamado file.shno diretório atual. A diferença é que você o está executando explicitamente com o shshell. Nos sistemas Ubuntu, isso é dashe não bash. Geralmente, os scripts têm uma linha shebang que fornece o programa em que devem ser executados. Chamá-los com um diferente substitui isso. Por exemplo:

    $ cat foo.sh
    #!/bin/bash  
    ## The above is the shebang line, it points to bash
    ps h -p $$ -o args='' | cut -f1 -d' '  ## This will print the name of the shell

    Esse script simplesmente imprime o nome do shell usado para executá-lo. Vamos ver o que ele retorna quando chamado de maneiras diferentes:

    $ bash foo.sh
    bash
    $ sh foo.sh 
    sh
    $ zsh foo.sh
    zsh

    Portanto, a chamada de um script shell scriptsubstituirá a linha shebang (se houver) e executará o script com qualquer shell que você indicar.

  4. source file.sh ou . file.sh

    Isso é chamado, surpreendentemente, como fonte do script. A palavra source- chave é um alias para o .comando embutido no shell . Essa é uma maneira de executar o script no shell atual. Normalmente, quando um script é executado, ele é executado em seu próprio shell, que é diferente do atual. Ilustrar:

    $ cat foo.sh
    #!/bin/bash
    foo="Script"
    echo "Foo (script) is $foo"

    Agora, se eu definir a variável foopara outra coisa no shell pai e executar o script, o script imprimirá um valor diferente de foo(porque também é definido no script), mas o valor de foono shell pai não será alterado:

    $ foo="Parent"
    $ bash foo.sh 
    Foo (script) is Script  ## This is the value from the script's shell
    $ echo "$foo"          
    Parent                  ## The value in the parent shell is unchanged

    No entanto, se eu originar o script em vez de executá-lo, ele será executado no mesmo shell, portanto o valor de foono pai será alterado:

    $ source ./foo.sh 
    Foo (script) is Script   ## The script's foo
    $ echo "$foo" 
    Script                   ## Because the script was sourced, 
                             ## the value in the parent shell has changed

    Portanto, o sourcing é usado nos poucos casos em que você deseja que um script afete o shell do qual está sendo executado. Geralmente é usado para definir variáveis ​​de shell e disponibilizá-las após a conclusão do script.


Com tudo isso em mente, a razão pela qual você obtém respostas diferentes é, antes de tudo, que seu script não faz o que você pensa. Conta o número de vezes que bashaparece na saída de ps. Este não é o número de terminais abertos , é o número de shells em execução (na verdade, nem é isso, mas essa é outra discussão). Para esclarecer, simplifiquei um pouco o seu script para isso:

#!/bin/bash
logname=terdon
not=`ps -au$logname | grep -c bash`
echo  "The number of shells opened by $logname is $not"

E execute-o de várias maneiras, com apenas um único terminal aberto:

  1. Lançamento direto ./foo.sh,.

    $ ./foo.sh
    The number of shells opened by terdon is 1

    Aqui, você está usando a linha shebang. Isso significa que o script é executado diretamente por tudo o que estiver definido lá. Isso afeta a maneira como o script é mostrado na saída de ps. Em vez de ser listado como bash foo.sh, ele será mostrado apenas como o foo.shque significa que você grepsentirá falta dele. Na verdade, existem três instâncias do bash em execução: o processo pai, o bash executando o script e outro que executa o pscomando . Este último é importante, o lançamento de um comando com substituição de comando ( `command`ou $(command)) resulta em uma cópia do shell pai sendo iniciada e que executa o comando. Aqui, no entanto, nada disso é mostrado devido à maneira que psmostra sua saída.

  2. Lançamento direto com shell explícito (bash)

    $ bash foo.sh 
    The number of shells opened by terdon is 3

    Aqui, como você está executando bash foo.sh, a saída de psserá mostrada bash foo.she contada. Portanto, aqui temos o processo pai, a bashexecução do script e o shell clonado (executando o ps), todos mostrados porque agora psmostrarão cada um deles porque seu comando incluirá a palavra bash.

  3. Lançamento direto com um shell diferente ( sh)

    $ sh foo.sh
    The number of shells opened by terdon is 1

    Isso é diferente porque você está executando o script com she não bash. Portanto, a única bashinstância é o shell pai no qual você iniciou seu script. Todas as outras conchas mencionadas acima estão sendo executadas sh.

  4. Sourcing (por .ou a sourcemesma coisa)

    $ . ./foo.sh 
    The number of shells opened by terdon is 2

    Como expliquei acima, o fornecimento de um script faz com que ele seja executado no mesmo shell que o processo pai. No entanto, um subshell separado é iniciado para iniciar o pscomando e eleva o total para dois.


Como nota final, a maneira correta de contar os processos em execução não é analisar, psmas usá-la pgrep. Todos esses problemas teriam sido evitados se você tivesse acabado de executar

pgrep -cu terdon bash

Portanto, uma versão funcional do seu script que sempre imprime o número correto é (observe a ausência de substituição de comando):

#!/usr/bin/env bash
user="terdon"

printf "Open shells:"
pgrep -cu "$user" bash

Isso retornará 1 quando originado e 2 (porque um novo bash será lançado para executar o script) para todas as outras formas de lançamento. Ele ainda retornará 1 quando iniciado, shpois o processo filho não é bash.

Terdon
fonte
Quando você diz que a substituição de comando inicia uma cópia do shell pai, como essa cópia difere de um sub shell como quando você executa o script com ./foo.sh?
Didier A.
E quando você executa o pgrep sem substituição de comando, presumo que ele esteja sendo executado no mesmo shell em que o script é executado? Tão semelhante ao sourcing?
Didier A.
@didibus Não sei ao certo o que você quer dizer. A substituição de comandos é executada em um subshell; ./foo.shé executado em um novo shell que não é uma cópia do pai. Por exemplo, se você definir foo="bar"em seu terminal e executar um script que seja executado echo $foo, você obterá uma linha vazia, pois o shell do script não herdará o valor da variável. pgrepé um binário separado e, sim, é executado pelo script que você está executando.
terdon 28/07/15
Basicamente, preciso de esclarecimentos sobre: ​​"observe a ausência de substituição de comando". Por que rodar o binário pgrep a partir de um script não adiciona um shell extra, mas rodar o binário ps com substituição de comando faz? Em segundo lugar, preciso de esclarecimentos sobre "cópia do shell pai", é como um sub shell onde as variáveis ​​do shell pai são copiadas para o filho? Por que a substituição de comando faz isso?
Didier A.