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

看看代码:

#!/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传递给所有这些可执行文件。

现在可以解释所有这些之间的差异吗?

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

  1. ./file.sh

    这将执行一个名为file.sh的脚本,该脚本位于当前目录( ./ )中。 通常,当您运行command ,shell将查看$PATH的目录以查找名为command的可执行文件。 如果给出完整路径,例如/usr/bin/command./command ,则忽略$PATH并执行该特定文件。

  2. ../file.sh

    这与./file.sh基本相同,只是它不是在file.sh的当前目录中file.sh ,而是在父目录( ../ )中查找。

  3. sh file.sh

    这相当于sh ./file.sh ,如上所述它将在当前目录中运行名为file.sh的脚本。 不同之处在于您使用sh shell显式运行它。 在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 

    该脚本将只打印用于运行它的shell的名称。 让我们看看它以不同方式调用时返回的内容:

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

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

  4. source file.sh. file.sh . file.sh

    令人惊讶的是,这就是调用脚本的原因。 关键字source是shell内置的别名. 命令。 这是在当前shell中执行脚本的一种方法。 通常,当执行脚本时,它在自己的shell中运行,该shell与当前的shell不同。 为了显示:

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

    现在,如果我将变量foo设置为父shell中的其他内容然后运行脚本,脚本将打印不同的foo值(因为它也在脚本中设置)但是父shell中的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输出中bash出现的次数。 这不是开放终端的数量 ,而是运行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命令的另一个 bash实例。 最后一点很重要,启动带命令替换的命令( `command`$(command) )会导致正在启动的父shell的副本并运行该命令。 但是,这里没有显示这些因为ps显示其输出的方式。

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

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

    在这里,因为你正在使用bash foo.sh运行, ps的输出将显示bash foo.sh并被计算。 所以,这里我们有父进程,运行脚本的bash 克隆的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 

对于所有其他启动方式,这将在sourced时返回1,并且将返回2(因为将启动新的bash来运行脚本)。 由于子进程不是bash在使用sh启动时它仍将返回1。