使用`sh`和`source`有什么区别?

81

shsource有什么区别?

source: source filename [arguments]
    Read and execute commands from FILENAME and return.  The pathnames
    in $PATH are used to find the directory containing FILENAME.  If any
    ARGUMENTS are supplied, they become the positional parameters when
    FILENAME is executed.

对于 man sh

NAME
       bash - GNU Bourne-Again SHell

SYNOPSIS
       bash [options] [file]

COPYRIGHT
       Bash is Copyright (C) 1989-2004 by the Free Software Foundation, Inc.

DESCRIPTION
       Bash  is  an sh-compatible command language interpreter that executes commands read from the standard input or from a file.  Bash also incorporates
       useful features from the Korn and C shells (ksh and csh).

       Bash is intended to be a conformant implementation of the IEEE POSIX Shell and Tools specification (IEEE Working Group 1003.2).

2
请注意shbash之间的区别 - tripleee
5个回答

115
当你调用source.(其中一个是另一个的别名。source命令不是POSIX标准 - 类似于bashism),你将一个shell脚本加载并执行到当前的shell进程中。因此,你可以:
  • 读取在被引用脚本中设置的变量,
  • 使用在其中定义的函数。
  • 甚至执行分支和/或子进程,如果脚本这样做的话。
当你调用sh时,你启动了一个fork(子进程或子进程),它运行一个新的/bin/sh会话(通常是一个符号链接到bash)。在这种情况下,子脚本设置的环境变量将在子脚本终止时被丢弃。

注意sh 可能是一个符号链接到另一个

实用示例

例如,如果你想以特定的方式改变当前工作目录,你不能这样做

$ cat <<eof >myCd2Doc.sh
#!/bin/sh
cd /usr/share/doc
eof

$ chmod +x myCd2Doc.sh

这个不会做你期望的事情:
$ cd /tmp
$ pwd
/tmp
$ ~/myCd2Doc.sh
$ pwd
/tmp

因为当前工作目录是环境的一部分,myCd2Doc.sh将在一个子shell中运行。
但是:
$ source ~/myCd2Doc.sh
$ pwd
/usr/share/doc

同样,声明一个函数的方式也是一样的:
$ cat >~/myCd2Doc.source <<eof
# Shell source file
myCd2Doc() {
    cd /usr/share/doc
}
eof

$ . ~/myCd2Doc.source
$ cd /tmp
$ pwd
/tmp
$ myCd2Doc
$ pwd
/usr/share/doc

快来看看mycd函数吧!(带有基于关联数组自动补全)。

执行级别$SHLVL

$ cd /tmp
printf %b '\43\41/bin/bash\necho This is level \44SHLVL.\n' >qlvl.sh

$ bash qlvl.sh 
This is level 2.

$ source qlvl.sh 
This is level 1.

递归(当一个脚本从自身运行时)

$ cat <<"eoqlvl2" >qlvl2.sh 
#!/bin/bash

export startLevel recursionLimit=5
echo This is level $SHLVL started:${startLevel:=$SHLVL}.
(( SHLVL < recursionLimit )) && ./qlvl2.sh
eoqlvl2
$ chmod +x qlvl2.sh

$ ./qlvl2.sh 
This is level 2 started:2.
This is level 3 started:2.
This is level 4 started:2.
This is level 5 started:2.

$ source qlv2.sh 
This is level 1 started:1.
This is level 2 started:1.
This is level 3 started:1.
This is level 4 started:1.
This is level 5 started:1.

再进一点
$ sed '$a ps --sid $SID fw' qlvl.sh >qlvl3.sh
$ chmod +x qlvl3.sh 
$ export SID
$ read SID < <(ps ho sid $$)
$ echo $SID $$
8983 8983

(当前的PID$$ == 进程ID)与SID会话ID)是相同的标识符。但这并不总是正确的。)
$ ./qlvl3.sh 
This is level 2.
  PID TTY      STAT   TIME COMMAND
 8983 pts/10   Ss     0:00 /bin/bash
10266 pts/10   S+     0:00  \_ /bin/bash ./qlvl3.sh
10267 pts/10   R+     0:00      \_ ps --sid 8983 fw

$ . qlvl3.sh 
This is level 1.
  PID TTY      STAT   TIME COMMAND
 8983 pts/10   Ss     0:00 /bin/bash
10428 pts/10   R+     0:00  \_ ps --sid 8983 fw

Dot(点)是source(源)的别名。所以这两个命令之间唯一的区别是斜杠(slash)被空格(space)替代。
还有一个最后的测试:
$ printf %b '\43\41/bin/bash\necho Ending this.\nsle' \
    'ep 1;exit 0\n' >finalTest.sh

$ bash finalTest.sh 
Ending this.

$ source finalTest.sh
Ending this.

你可能会注意到这两种语法之间的行为有所不同。;-)

2
还应该注意,任何没有被导出的环境变量将不会对在新 shell 下调用的脚本可用。 - Will Vousden
(别名和shell函数也是如此。) - Will Vousden
@WillVousden:似乎别名根本无法导出,您可以导出变量和/或函数,但不能导出别名。 - F. Hauri - Give Up GitHub
是的,我认为别名无法被导出。 - Will Vousden
1
曾经在Linux系统上,sh通常被链接到bash,但现在不再是这样了。即使是这样,当您通过符号链接调用sh时,bash的行为也会有所不同。 - tripleee
显示剩余3条评论

17

主要区别在于它们在不同的进程中执行。

因此,如果你source一个名为foo的文件,该文件包含一个cd命令,那么源 shell(例如在终端中交互运行的 shell)会受到影响(并且其当前目录将更改)。

如果你执行sh foocd不会影响源 shell,只会影响新创建的 sh 进程运行foo命令。

阅读Advanced Bash Scripting Guide

这种区别不仅适用于Linux,每个Posix实现都有这个区别。


2
不要阅读高级Bash(实际上它们指的是Bug)脚本编写指南。 - gniourf_gniourf
1
你为什么认为ABSG对新手不合适?它教授了很多有用的东西...你建议使用哪个替代指南? - Basile Starynkevitch
1
然后建议一个更好的教程文档。我认为这对新手来说已经足够好了... - Basile Starynkevitch
实际上它解释得非常好。 http://www.tldp.org/LDP/abs/html/internal.html#SOURCEREF - confiq
2
一个选择是 http://mywiki.wooledge.org/BashGuide,还有更多链接在 http://wiki.bash-hackers.org/scripting/tutoriallist 上。 - tripleee
显示剩余2条评论

6
正如其他人所提到的,当您运行sh test.sh时,test.sh对您的 shell 环境所做的任何更改都不会在进程结束后保留。

但是,请注意,在执行为子进程(即使用sh test.sh)的代码中,环境的任何元素如果没有被导出(例如,变量、别名和 shell 函数),将无法供test.sh中的代码使用。

例如:

$ cat > test.sh
echo $foo
$ foo=bar
$ sh test.sh
$ . test.sh
bar

示例2:

lap@my-ThinkPad:~$ cat test.sh
#!/bin/sh
cd /etc
lap@my-ThinkPad:~$ sh test.sh 
lap@my-ThinkPad:~$ pwd
/home/savoury
lap@my-ThinkPad:~$ source test.sh 
lap@my-ThinkPad:/etc$ pwd
/etc
lap@my-ThinkPad:/etc$ 

3

(或 .)- 在当前 shell 中运行并更改其属性/环境。

sh 会 fork 并在子 shell 中运行,因此无法更改属性/环境。

例如:

我的 shell 脚本是 -

elite12!rg6655:~/sh_pr [33]$ cat changeDir.sh
#!/bin/bash
cd /home/elt/rg6655/sh_pr/justdir
pwd
echo $$

我的当前 Shell -

elite12!rg6655:~/sh_pr [32]$ echo $$
3272

我当前 shell 的进程 ID 是 3272

正在使用 source 运行 -

elite12!rg6655:~/sh_pr [34]$ source changeDir.sh
/home/elt/rg6655/sh_pr/justdir
3272
elite12!rg6655:~/sh_pr/justdir

观察两件事 - 1)进程ID(3272)与我的shell相同,这证实源在当前shell中执行。 2)cd命令起作用,目录已更改为justdir。

使用sh运行 -

elite12!rg6655:~/sh_pr [31]$ sh changeDir.sh
/home/elt/rg6655/sh_pr/justdir
13673
elite12!rg6655:~/sh_pr

在这种情况下,进程ID(13673)不同,但目录保持不变,这意味着它在不同的进程或子shell中运行。

1
当您使用sh命令执行程序时:
  • 您的终端将使用sh或Bourne Shell来执行该程序。
  • 一个新进程被创建,因为Bash会精确地复制自己。这个子进程与其父进程具有相同的环境,只是进程ID号不同。(这个过程称为分叉)
  • 您需要拥有执行权限才能执行它(因为它正在分叉)
当您使用source命令时:
  • 您使用默认解释器执行程序
  • 您在当前终端中执行进程(技术上是*nix命令解释器)
  • 由于程序将在当前终端中执行,因此您不需要给它执行权限

你不需要执行权限来通过将其作为参数传递给 sh 来调用它(例如 sh test.sh)。只有在直接调用它时才需要执行权限(例如 ./test.sh)。 - Will Vousden

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接