¿Cuáles son las diferencias entre la ejecución de scripts de shell utilizando "source file.sh", "./file.sh", "sh file.sh", ". ./file.sh "?

13

Echa un vistazo al 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 se usa para averiguar el número de terminales abiertos por un usuario en la misma PC. Ahora hay dos usuarios conectados, digamos x e y. Actualmente estoy conectado como y y hay 3 terminales abiertas en el usuario x. Si ejecuto este código en y usando diferentes formas como se mencionó anteriormente, los resultados son:

$ ./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: Pasé 1 y uid 1000 a todos estos ejecutables.

Ahora, ¿puedes explicar las diferencias entre todos estos?

Ramana Reddy
fuente
La diferencia es qué shell se ejecuta. sh no es bash
j0h
2
Las dos últimas ejecuciones también son diferentes porque está ejecutando en el mismo contexto. Más aquí
Zaka Elab
Estoy tratando de contar el número de instancias de bash (aquí es igual al número de terminales) abierto por el otro usuario (no el mismo usuario donde iniciamos sesión) y podría explicar por qué ha llegado un número diferente en cada caso
Ramana Reddy
@RamanaReddy el otro usuario puede haber ejecutado un script o iniciado una nueva pestaña. ¿Quién sabe?
muru

Respuestas:

21

La única diferencia importante es entre el abastecimiento y la ejecución de un script. source foo.shlo buscará y todos los demás ejemplos que muestres se están ejecutando. Con más detalle:

  1. ./file.sh

    Esto ejecutará un script llamado file.shque está en el directorio actual ( ./). Normalmente, cuando ejecuta command, el shell buscará en los directorios de su $PATHarchivo ejecutable llamado command. Si proporciona una ruta completa, como /usr/bin/commando ./command, $PATHse ignora y se ejecuta ese archivo específico.

  2. ../file.sh

    Esto es básicamente lo mismo que, ./file.shexcepto que en lugar de buscar en el directorio actual file.sh, está buscando en el directorio padre ( ../).

  3. sh file.sh

    Esto equivale a sh ./file.sh, como arriba, ejecutará el script llamado file.shen el directorio actual. La diferencia es que lo está ejecutando explícitamente con el shshell. En los sistemas Ubuntu, eso es dashy no bash. Por lo general, los scripts tienen una línea shebang que le da al programa el que deben ejecutarse. Llamarlos con uno diferente anula eso. Por ejemplo:

    $ 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

    Ese script simplemente imprimirá el nombre del shell utilizado para ejecutarlo. Veamos qué devuelve cuando se llama de diferentes maneras:

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

    Entonces, llamar a llamar a un script con shell scriptanulará la línea shebang (si está presente) y ejecutará el script con cualquier shell que le diga.

  4. source file.sh o . file.sh

    Esto se llama, sorprendentemente, el abastecimiento del guión. La palabra clave sourcees un alias para el .comando incorporado de shell . Esta es una forma de ejecutar el script dentro del shell actual. Normalmente, cuando se ejecuta un script, se ejecuta en su propio shell, que es diferente del actual. Para ilustrar:

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

    Ahora, si configuro la variable fooen otra cosa en el shell principal y luego ejecuto el script, el script imprimirá un valor diferente de foo(porque también se establece dentro del script) pero el valor de fooen el shell principal no cambiará:

    $ 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

    Sin embargo, si obtengo el script en lugar de ejecutarlo, se ejecutará en el mismo shell, por lo que se cambiará el valor de fooen el padre:

    $ 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

    Por lo tanto, el abastecimiento se usa en los pocos casos en los que desea que un script afecte al shell desde el que lo está ejecutando. Normalmente se usa para definir variables de shell y tenerlas disponibles una vez que finaliza el script.


Con todo eso en mente, la razón por la que obtienes respuestas diferentes es, en primer lugar, que tu guión no hace lo que crees que hace. Cuenta el número de veces que bashaparece en la salida de ps. Este no es el número de terminales abiertos , es el número de shells en ejecución (de hecho, ni siquiera es eso, pero esa es otra discusión). Para aclarar, simplifiqué un poco su guión para esto:

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

Y ejecútelo de varias maneras con solo un terminal abierto:

  1. Lanzamiento directo, ./foo.sh.

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

    Aquí, estás usando la línea shebang. Esto significa que el script se ejecuta directamente por lo que esté configurado allí. Esto afecta la forma en que se muestra el script en la salida de ps. En lugar de aparecer como bash foo.sh, solo se mostrará como lo foo.shque significa que greplo perderá. En realidad, se están ejecutando 3 instancias de bash: el proceso principal, el bash que ejecuta el script y otro que ejecuta el pscomando . Esto último es importante, el lanzamiento de un comando con sustitución de comando ( `command`o $(command)) da como resultado una copia del shell principal que se inicia y que ejecuta el comando. Aquí, sin embargo, ninguno de estos se muestra debido a la forma en que psmuestra su salida.

  2. Lanzamiento directo con shell explícito (bash)

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

    Aquí, porque está ejecutando con bash foo.sh, la salida de psse mostrará bash foo.shy se contará. Entonces, aquí tenemos el proceso padre, la bashejecución del script y el shell clonado (ejecutando ps), todos mostrados porque ahora psmostrará cada uno de ellos porque su comando incluirá la palabra bash.

  3. Lanzamiento directo con un shell diferente ( sh)

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

    Esto es diferente porque está ejecutando el script con shy no bash. Por lo tanto, la única bashinstancia es el shell principal donde inició su script. Todos los otros depósitos mencionados anteriormente están siendo ejecutados en su shlugar.

  4. Abastecimiento (ya sea por .o source, lo mismo)

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

    Como expliqué anteriormente, el abastecimiento de un script hace que se ejecute en el mismo shell que el proceso padre. Sin embargo, se inicia una subshell separada para iniciar el pscomando y eso lleva el total a dos.


Como nota final, la forma correcta de contar los procesos en ejecución no es analizar pssino usar pgrep. Todos estos problemas se habrían evitado si hubieras corrido

pgrep -cu terdon bash

Entonces, una versión funcional de su script que siempre imprime el número correcto es (tenga en cuenta la ausencia de sustitución de comandos):

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

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

Eso devolverá 1 cuando se obtenga y 2 (porque se lanzará un nuevo bash para ejecutar el script) para todas las otras formas de lanzamiento. Todavía devolverá 1 cuando se inicie, shya que el proceso secundario no lo es bash.

terdon
fuente
Cuando dice que la sustitución de comandos inicia una copia del shell principal, ¿en qué se diferencia esta copia de un shell secundario como cuando ejecuta el script con ./foo.sh?
Didier A.
Y cuando ejecuta pgrep sin sustitución de comando, supongo que se ejecuta desde el mismo shell en el que se ejecuta el script. ¿Tan similar al abastecimiento?
Didier A.
@didibus No estoy seguro de lo que quieres decir. La sustitución de comandos se ejecuta en una subshell; ./foo.shse ejecuta en un nuevo shell que no es una copia del padre. Por ejemplo, si configura foo="bar"en su terminal y luego ejecuta un script que se ejecuta echo $foo, obtendrá una línea vacía ya que el shell del script no habrá heredado el valor de la variable. pgrepes un binario separado, y sí, lo ejecuta el script que está ejecutando.
terdon
Básicamente necesito aclaraciones sobre: ​​"tenga en cuenta la ausencia de sustitución de comandos". ¿Por qué la ejecución del binario pgrep desde un script no agrega un shell adicional, pero la ejecución del binario ps con sustitución de comandos sí? En segundo lugar, necesito una aclaración sobre la "copia del shell principal", ¿es eso como un sub-shell donde las variables de shell del padre se copian al hijo? ¿Por qué la sustitución de comandos hace eso?
Didier A.