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

10

Veja o 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, há dois usuários conectados, digamos xe y. Eu estou atualmente logado como y e existem 3 terminais abertos no usuário x. Se eu executar este código em y usando diferentes maneiras, como mencionado acima, os resultados sã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 explicar as diferenças entre todos eles?

    
por Ramana Reddy 25.03.2015 / 11:59

1 resposta

15

A única grande diferença é entre o fornecimento e a execução de um script. source foo.sh irá obtê-lo e todos os outros exemplos mostrados estão sendo executados. Mais detalhadamente:

  1. ./file.sh

    Isso executará um script chamado file.sh que está no diretório atual ( ./ ). Normalmente, quando você executa command , o shell irá procurar pelos diretórios no seu $PATH para um arquivo executável chamado command . Se você fornecer um caminho completo, como /usr/bin/command ou ./command , o $PATH será ignorado e esse arquivo específico será executado.

  2. ../file.sh

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

  3. sh file.sh

    Este equivalente a sh ./file.sh , como acima, executará o script chamado file.sh no diretório atual. A diferença é que você está executando explicitamente com o shell sh . Nos sistemas Ubuntu, isso é dash e não bash . Geralmente, os scripts têm uma linha shebang que fornece o programa para o qual 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 imprimirá 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, chamar chamando um script com shell script substituirá a linha shebang (se presente) e executará o script com qualquer shell que você disser.

  4. source file.sh ou . file.sh

    Isso é chamado, surpreendentemente, sourcing o script. A palavra-chave source é um alias para o comando shell builtin . . Esta é uma maneira de executar o script dentro do shell atual. Normalmente, quando um script é executado, ele é executado em seu próprio shell, que é diferente do atual. Para ilustrar:

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

    Agora, se eu definir a variável foo como outra coisa no shell pai e depois executar o script, o script imprimirá um valor diferente de foo (porque também é definido no script), mas o valor de foo no 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 usar o script em vez de executá-lo, ele será executado no mesmo shell para que o valor de foo no pai seja 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
    

    Assim, o sourcing é usado nos poucos casos em que você deseja que um script afete o shell do qual está sendo executado. É normalmente usado para definir variáveis de shell e disponibilizá-las depois que o script é concluído.

Com tudo isso em mente, a razão pela qual você obtém respostas diferentes é, antes de mais nada, que seu script não faz o que você acha que faz. Ele conta o número de vezes que bash aparece 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 isso é outra discussão). Para esclarecer, simplifiquei um pouco seu roteiro 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 formas 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 pelo que está 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 foo.sh , o que significa que seu grep não o verá. Na verdade, existem 3 instâncias bash em execução: o processo pai, o bash executando o script e outro que executa o comando ps . Este último é importante, a ativação de um comando com substituição de comando ( 'command' ou $(command) ) resulta em uma cópia do shell pai que está sendo ativado e que executa o comando. Aqui, no entanto, nenhum deles é mostrado devido à maneira como ps mostra 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, porque você está executando com bash foo.sh , a saída de ps mostrará bash foo.sh e será contada. Então, aqui temos o processo pai, o bash executando o script e o shell clonado (executando o ps ) todos mostrados porque agora ps mostrará cada um deles porque seu comando será inclua 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 sh e não bash . Portanto, a única bash instance é o shell pai no qual você iniciou o script. Todos os outros shells mencionados acima estão sendo executados por sh .

  4. Terceirização (por . ou source , mesma coisa)

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

    Como expliquei acima, a obtenção de um script faz com que ele seja executado no mesmo shell do processo pai. No entanto, um subshell separado é iniciado para iniciar o comando ps e isso eleva o total para dois.

Como nota final, a maneira correta de contar os processos em execução não é analisar ps , mas usar 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 é (note 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 lançado com sh , pois o processo filho não é bash .

    
por terdon 25.03.2015 / 14:41