使用“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
将获取它,并且您显示的所有其他示例正在执行。 更详细:
-
./file.sh
这将执行一个名为
file.sh
的脚本,该脚本位于当前目录(./
)中。 通常,当您运行command
,shell将查看$PATH
的目录以查找名为command
的可执行文件。 如果给出完整路径,例如/usr/bin/command
或./command
,则忽略$PATH
并执行该特定文件。 -
../file.sh
这与
./file.sh
基本相同,只是它不是在file.sh
的当前目录中file.sh
,而是在父目录(../
)中查找。 -
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运行脚本。 -
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"
只需打开一个终端,就可以通过各种方式运行它:
-
直接启动,。/
./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
显示其输出的方式。 -
使用显式(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
。 -
使用不同的shell(
sh
)直接启动$ sh foo.sh The number of shells opened by terdon is 1
这是不同的,因为您使用
sh
而不是bash
运行脚本。 因此,唯一的bash
实例是您启动脚本的父shell。 上面提到的所有其他shell都由sh
运行。 -
采购(通过
.
或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。