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

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

看一下代码:

#!/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"

此代码用于找出同一台 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

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

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

答案1

唯一的主要区别在于获取脚本和执行脚本。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调用的脚本。不同之处在于您使用shell 明确运行它。在 Ubuntu 系统上,即而不是。通常,脚本有一个file.shshdashbash舍邦线给出了它们应该作为哪个程序运行。使用其他程序调用它们会覆盖该程序。例如:

     $ 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
    

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

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

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

  4. source file.sh或者. file.sh

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

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

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

     $ 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
    

    但是,如果我获取脚本而不是执行它,它将在同一个 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
    

    因此,在少数情况下,当您希望脚本影响运行脚本的 shell 时,可以使用 sourcing。它通常用于定义 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"

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

  1. 直接发射,./foo.sh

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

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

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

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

    在这里,由于您正在运行,因此将显示并计算的bash foo.sh输出。因此,这里有父进程,即运行脚本的进程psbash foo.shbash克隆的 shell (运行ps)全部显示,因为现在ps将显示它们每一个,因为您的命令将包含单词bash

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

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

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

  4. 采购(通过.source,相同的东西)

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

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


最后要注意的是,计算正在运行的进程的正确方法不是解析,ps而是使用pgrep。如果你运行

pgrep -cu terdon bash

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

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

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

如果使用 source 则返回 1,如果使用其他所有方式启动则返回 2(因为将启动新的 bash 来运行脚本)。如果使用 启动,则仍将返回 1,sh因为子进程不是bash

相关内容