【问题标题】:What is the difference between using `sh` and `source`?使用 `sh` 和 `source` 有什么区别?
【发布时间】:2012-11-27 00:19:13
【问题描述】:

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).

【问题讨论】:

    标签: linux bash shell unix


    【解决方案1】:

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

    因此,如果您 source 一个文件 foo 执行 cd,则源 shell(例如终端中的交互式 shell)会受到影响(并且其当前目录会改变)

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

    阅读Advanced Bash Scripting Guide

    这种差异并非特定于 Linux;每个 Posix 实现都会有它。

    【讨论】:

    • 不,不要阅读 Advanced Bash(实际上它们的意思是 Bug)脚本指南
    • 为什么你认为 ABSG 对新手来说是错误的?它教了很多有用的东西......你建议什么替代指南?
    • 它教授更糟糕的 bash 实践。看看第一个例子,你就会明白它有多可怕:大写的变量名、猫的无用用法……
    • 然后建议一个替代的更好的教程文档。我认为这对新手来说已经足够了......
    【解决方案2】:

    当您调用 source.(其中一个是另一个的别名。source cmd not POSIX - kind of bashism)时,您将 bash 脚本 加载到 当前 bash 进程中并执行。所以你可以

    • 读取源脚本中设置的变量,
    • 使用其中定义的函数。
    • 如果脚本执行此操作,甚至执行分叉和/或子进程。

    当您调用sh 时,您启动了一个运行/bin/sh 的新会话的fork(子进程或),这通常是一个符号链接到bash。在这种情况下,子脚本设置的环境变量将在子脚本完成时被删除。

    警告sh 可能是指向另一个 shell 的符号链接。

    实用样例

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

    $ 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 将在 subshel​​l 中运行。

    但是:

    $ 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 function!! (使用基于关联数组完成)。

    执行级别$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.
    

    Recursion(当脚本从自身运行时)

    $ 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
    

    .source 的别名。因此,两个命令之间的唯一区别是 slash 替换为 space

    还有一个最终测试:

    $ printf %b '\43\41/bin/bash\necho Ending this.\nsleep 1;exit 0\n' >finalTest.sh
    
    $ bash finalTest.sh 
    Ending this.
    
    $ source finalTest.sh
    Ending this.
    

    ... 您可能会注意到两种语法之间的不同 行为。 ;-)

    【讨论】:

    • 还应注意,任何未导出的环境变量都不可用于在新 shell 下调用的脚本。
    • (别名和shell函数也是如此。)
    • @WillVousden:别名似乎根本无法导出,您可以导出变量和/或函数,但不能导出别名。
    • 是的,我认为不能导出别名。
    • @F.Hauri 关于source. 之间的关系,是反过来的。 source 是标准 POSIX 语法 .bash 特定 别名。请考虑修改您的答案以相应地更正措辞。
    【解决方案3】:

    当你使用 sh 命令执行程序时:

    • 您的终端将使用 sh 或 Bourne Shell 来执行程序。
    • 创建了一个新进程,因为 Bash 对其自身进行了精确复制。这个子进程和它的父进程有相同的环境,只是进程ID号不同。 (这个过程称为分叉)
    • 你需要有执行权限才能执行(因为它是分叉的)

    当你使用源命令时:

    • 您使用默认解释器执行程序
    • 您在当前终端中执行该进程(技术上是您的 *nix 命令已解释)
    • 由于程序将在当前终端执行,因此您无需授予它执行权限

    【讨论】:

    • 您不需要执行权限即可通过将其作为参数传递给sh(例如sh test.sh)来调用。只有当你想直接调用它时才需要它(例如./test.sh)。
    【解决方案4】:

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

    但是,还请注意,当test.sh 中的代码作为子进程执行时(即sh 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$ 
    

    【讨论】:

      【解决方案5】:

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

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

      例如

      我的 shell 脚本是 -

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

      我当前的外壳 -

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

      我当前 shell 的进程 id 是 3272

      使用源代码运行 -

      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 中运行。

      【讨论】:

        猜你喜欢
        • 2020-01-15
        • 2018-12-15
        • 1970-01-01
        • 1970-01-01
        • 2014-04-30
        • 1970-01-01
        • 2012-05-26
        • 1970-01-01
        相关资源
        最近更新 更多