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


13

看一下代码:

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

现在,您能解释一下所有这些区别吗?


不同之处在于执行哪个shell。sh不是bash
j0h 2015年

2
最后两个执行也不同,因为您在同一上下文中执行。更多在这里
扎卡电子实验室

我正在尝试计算其他用户(不是我们登录的同一用户)打开的bash实例的数量(此处等于终端数),您能否解释一下每种情况下出现的数量不同的原因
Ramana Reddy

@RamanaReddy,其他用户可能已运行脚本或启动了新选项卡。谁知道?
muru

Answers:


21

唯一的主要区别是在源代码和执行脚本之间。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当前目录中调用的脚本。区别在于您是在sh外壳上显式运行它。在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

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

    $ 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中运行。为了显示:

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

    现在,如果我foo在父外壳程序中将变量设置为其他变量,然后运行脚本,该脚本将打印不同的值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

    但是,如果我提供脚本而不是执行脚本,它将在同一外壳中运行,因此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

    因此,在少数情况下,如果您希望脚本影响运行脚本的外壳,就会使用源代码。它通常用于定义外壳变量,并在脚本完成后使它们可用。


考虑到所有这些,您得到不同答案的原因首先是您的脚本没有执行您认为的操作。它计算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命令的另一个 bash实例。最后一点很重要,使用命令替换(`command`$(command))启动命令会导致启动父外壳程序副本并运行该命令。但是,由于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. 使用其他外壳直接启动(sh

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

    这是不同的,因为您使用sh和不是运行脚本bash。因此,唯一的bash实例是启动脚本的父外壳程序。上面提到的所有其他Shell都由其运行sh

  4. 采购(通过.source,同一件事)

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

    如上文所述,获取脚本会导致脚本在与父进程相同的shell中运行。但是,将启动一个单独的子外壳来启动ps命令,从而使总数增加到两个。


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

pgrep -cu terdon bash

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

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

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

在所有其他启动方式中,返回时将返回1,而将返回2(因为将启动新的bash以运行脚本)。使用sh子进程启动时,它在启动时仍将返回1 bash


当您说命令替换启动了父shell的副本时,该副本与子shell有什么区别,例如使用./foo.sh运行脚本时?
Didier A.

当您在不使用命令替换的情况下运行pgrep时,我假设它是在脚本运行所在的shell中运行的?类似于采购吗?
Didier A.

@didibus我不确定您的意思。命令替换在子外壳中运行;./foo.sh在不是父级副本的新Shell中运行。例如,如果您foo="bar"在终端中进行设置,然后运行执行脚本echo $foo,您将得到一个空行,因为脚本的外壳将不会继承该变量的值。pgrep是一个单独的二进制文件,是的,它由您正在运行的脚本运行。
terdon

基本上,我需要澄清:“注意没有命令替换”。为什么从脚本运行pgrep二进制文件不会添加额外的外壳程序,而通过命令替换来运行ps二进制文件呢?其次,我需要澄清“父外壳的副本”,就像子外壳一样,将父外壳的变量复制到子外壳上吗?为什么命令替换会这样做?
Didier A.
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.