使用“source file.sh”、“./file.sh”、“sh file.sh”、“../file.sh”执行shell脚本有什么区别?

Ram*_*ddy 14 command-line bash scripts

看看代码:

#!/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"
Run Code Online (Sandbox Code Playgroud)

此代码用于查找用户在同一台​​ PC 上打开的终端数量。现在有两个用户登录,比如 x 和 y。我目前以 y 身份登录,用户 x 中打开了 3 个终端。如果我使用上面提到的不同方式在 y 中执行此代码,结果是:

$ ./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
Run Code Online (Sandbox Code Playgroud)

注意:我将 1 和 uid 1000 传递给所有这些可执行文件。

现在你能解释一下所有这些之间的区别吗?

ter*_*don 24

唯一的主要区别在于获取脚本和执行脚本之间的区别。source foo.sh将获取它,并且您展示的所有其他示例都在执行。更详细地:

  1. ./file.sh

    这将执行file.sh当前目录 ( ./) 中名为的脚本。通常,当您运行 时command,shell 将在您的目录中$PATH查找名为command. 如果您提供完整路径,例如/usr/bin/command./command,则$PATH忽略 并执行该特定文件。

  2. ../file.sh

    ./file.sh除了不是在当前目录中查找file.sh,而是在父目录 ( ../)中查找之外,这与基本相同。

  3. sh file.sh

    这相当于sh ./file.sh. 如上,它将运行file.sh在当前目录中调用的脚本。不同之处在于您使用shshell显式运行它。在 Ubuntu 系统上,这dash不是bash. 通常,脚本有一个shebang 行,给出它们应该运行的程序。用不同的方法调用它们会覆盖它。例如:

     $ 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
    
    Run Code Online (Sandbox Code Playgroud)

该脚本将简单地打印用于运行它的 shell 的名称。让我们看看它在以不同方式调用时返回什么:

    $ bash foo.sh
    bash
    $ sh foo.sh 
    sh
    $ zsh foo.sh
    zsh
Run Code Online (Sandbox Code Playgroud)

因此,调用脚本 withshell script将覆盖 shebang 行(如果存在)并使用您告诉它的任何 shell 运行脚本。

  1. source file.sh 或者 . file.sh

令人惊讶的是,这被称为采购脚本。该关键字source是 shell 内置.命令的别名。这是在当前 shell 中执行脚本的一种方式。通常,当执行脚本时,它会在自己的 shell 中运行,这与当前的 shell 不同。为了显示:

    $ cat foo.sh
    #!/bin/bash
    foo="Script"
    echo "Foo (script) is $foo"
Run Code Online (Sandbox Code Playgroud)

现在,如果我foo在父 shell中将变量设置为其他值,然后运行脚本,脚本将打印不同的值foo(因为它也在脚本中设置)但foo父 shell 中的值将保持不变:

    $ 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
Run Code Online (Sandbox Code Playgroud)

但是,如果我获取脚本而不是执行它,它将在同一个 shell 中运行,因此foo父级中的值将被更改:

    $ 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
Run Code Online (Sandbox Code Playgroud)

因此,在您希望脚本影响正在运行它的 shell 的少数情况下,会使用采购。它通常用于定义 shell 变量并在脚本完成后使它们可用。


考虑到所有这些,您得到不同答案的原因首先是您的脚本没有按照您的想法行事。它计算bash出现在 的输出中的次数ps这不是打开终端的数量,而是正在运行的 shell的数量(实际上,它甚至不是,但这是另一个讨论)。为了澄清起见,我将您的脚本稍微简化为:

#!/bin/bash
logname=terdon
not=`ps -au$logname | grep -c bash`
echo  "The number of shells opened by $logname is $not"
Run Code Online (Sandbox Code Playgroud)

并以各种方式运行它,只打开一个终端:

  1. 直接启动,./foo.sh

     $ ./foo.sh
     The number of shells opened by terdon is 1
    
    Run Code Online (Sandbox Code Playgroud)

在这里,您使用的是shebang 行。这意味着脚本由那里设置的任何内容直接执行。这会影响脚本在ps. bash foo.sh它不会被列为,而只会显示为foo.sh,这意味着您grep会错过它。实际上有 3 个 bash 实例在运行:父进程、运行脚本的 bash和运行ps命令的另一个bash 实例。最后一点很重要,使用命令替换 (`command`$(command))启动命令会导致启动并运行该命令的父 shell 的副本。但是,由于ps显示其输出的方式,这里没有显示这些内容。

  1. 使用显式 (bash) shell 直接启动

     $ bash foo.sh 
     The number of shells opened by terdon is 3
    
    Run Code Online (Sandbox Code Playgroud)

在这里,因为您正在运行bash foo.sh,所以ps将显示bash foo.sh并计算的输出。所以,这里我们有父进程、bash正在运行的脚本克隆的 shell(运行ps),因为现在ps将显示它们中的每一个,因为您的命令将包含单词bash.

  1. 直接用不同的 shell ( sh) 启动

     $ sh foo.sh
     The number of shells opened by terdon is 1
    
    Run Code Online (Sandbox Code Playgroud)

这是不同的,因为您正在运行脚本,sh而不是bash。因此,唯一的bash实例是您启动脚本的父 shell。上面提到的所有其他 shell 都由它来运行sh

  1. 采购(通过.source,同样的事情)

     $ . ./foo.sh 
     The number of shells opened by terdon is 2
    
    Run Code Online (Sandbox Code Playgroud)

正如我上面所解释的,获取脚本会导致它在与父进程相同的 shell 中运行。但是,会启动一个单独的子 shell 来启动ps命令,从而使总数达到两个。


最后要注意的是,计算正在运行的进程的正确方法不是解析ps而是使用pgrep. 如果你只是跑步,所有这些问题都可以避免

pgrep -cu terdon bash
Run Code Online (Sandbox Code Playgroud)

因此,始终打印正确数字的脚本的工作版本是(注意没有命令替换):

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

printf "Open shells:"
pgrep -cu "$user" bash
Run Code Online (Sandbox Code Playgroud)

对于所有其他启动方式,这将在获取时返回 1(因为将启动一个新的 bash 来运行脚本)和 2。sh由于子进程不是 ,因此在启动时仍会返回 1 bash