⽤sh、.、source执⾏Shell脚本到底有何不同?
要解答这个看似简单的问题,需要先复习⼀下Linux系统⾥“命令”这个词的含义。
Linux系统中的命令有两种:⼀是内置命令,是Shell与⽣俱来的⼀部分,⽐如最基础的cd、echo、kill等;⼆是外部命令,包含已编译的实⽤程序以及Shell脚本两种,它们两者⼜可以统称为可执⾏⽂件(executables)。我们平时常⽤的⼤多数看起来像“内置(⾃带)命令”的命令,其实都是/usr/bin及其他⽬录下的已编译程序,如ls、ps、grep等。
可见,外部命令的实体并不存在于Shell中,⽽是在调⽤时才从对应的位置加载。如果⽤户调⽤命令时没有提供路径的话,Shell通过PATH 环境变量来定位外部命令的路径。
# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/usr/java/jdk1.8.0_172/bin:/usr/java/jdk1.8.0_172/jre/bin
如果在PATH给定的路径仍然不到命令,Shell就会返回"command not found"。这也就解释了为什么在Linux下安装完JDK之后,总是要将$JAVA_HOME/bin写⼊PATH——⽤户肯定不想每次调⽤JDK提供的命令时都要先cd到JDK的安装路径,或者把路径写得清清楚楚。
按照上⽂所述,我们平时⾃⼰写的Shell脚本也是外部命令。下⾯在/tmp/test⽬录下直接创建⼀个⽂件:touch my_script.sh,并在其中写⼏句简单的话。
#!/bin/sh
echo "Hello World!"
echo "Parameter 1 is: $1"
echo "MYVAR is: $MYVAR"
然后以不同的⽅式执⾏这个脚本。
[root@aes ~]# MYVAR=littlemagic
[root@aes ~]# sh /tmp/test/my_script.sh bla
Hello World!
Parameter 1 is: bla
MYVAR is:
[root@aes ~]# /tmp/test/my_script.sh bla
-bash: /tmp/test/my_script.sh: Permission denied
[root@aes ~]# source /tmp/test/my_script.sh bla
Hello World!
Parameter 1 is: bla
shell最简单脚本MYVAR is: littlemagic
[root@aes ~]# cd /tmp/test
[root@aes test]# sh my_script.sh bla
Hello World!
Parameter 1 is: bla
MYVAR is:
[root@aes test]# ./my_script.sh bla
-bash: ./my_script.sh: Permission denied
[root@aes test]# my_script.sh bla
-bash: my_script.sh: command not found
[root@aes test]# source my_script.sh bla
Hello World!
Parameter 1 is: bla
MYVAR is: littlemagic
这段⽰例的信息量蛮⼤的,下⾯以Q&A的形式逐个解决问题。
Q1:./有什么特殊含义没?
并没有,只是表⽰相对路径(即当前⽬录)⽽已,./my_script.sh即在当前⽬录/tmp/test执⾏my_script.
sh脚本。⽤绝对路径和⽤相对路径执⾏脚本是等价的,因此可以⼲脆将它们打包统称为“./⽅式”,以与“sh⽅式”区分开来。
Q2:为什么./⽅式提⽰没权限,⽽sh⽅式执⾏成功?
在./⽅式下,当前Shell进程会使⽤fork系统调⽤产⽣⼀个⼦Shell进程,并使⽤execve系统调⽤让OS在⼦Shell进程中执⾏脚本。execve系统调⽤必然会检查脚本⽂件的权限,⽽新touch出来的⽂件权限是-rw-r--r--,并没有可执⾏(x)权限,所以会报Permission denied。如果把权限改正,那么它的执⾏结果与sh⽅式是相同的。
[root@aes test]# chmod a+x my_script.sh
[root@aes test]# ./my_script.sh bla
Hello World!
Parameter 1 is: bla
MYVAR is:
sh⽅式的不同点在于,OS会通过sh命令的调⽤直接产⽣⼀个新的Shell进程,并将my_script.sh当作命令⾏参数传进去。新的Shell进程就会读取、解释并执⾏该脚本,⽽OS不关⼼该⽂件到底是什么,⾃然也就不要求可执⾏权限,只要求读权限就可以了。
Q3:为什么在当前路径下直接写my_script.sh会报command not found?
Linux与Windows不同,如果⽤户不指定路径,那么就会直接跳过对当前路径的检查,直接按照PATH来查。⽽PATH⾥⾃然是没有当前路径的,command not found就顺理成章了。也就是说,加./是为了告诉Shell:“我已经确定我要执⾏的东西在当前路径了”。
看官可能会问,⼲脆在PATH⾥直接加上当前路径(即.),不就可以免去打./的⿇烦了吗?Linux⾮常不推荐这样做,安全风险极⼤。举个极端的例⼦:⼀个普通⽤户在⾃⼰的家⽬录新建了⼀个名为ls的脚本,但⾥⾯的内容是rm -rf /*。然后root⽤户来到该⽤户的家⽬录,并执⾏ls命令,如果PATH⾥有当前路径的话,结果可想⽽知。
Q4:为什么source执⾏可以打出MYVAR变量的值,其他两种不⾏?
source是Shell(准确地说是Bash)的内置命令,在Bourne Shell中的等价命令是⼀个点.,即点命令。⽤source命令执⾏脚本⽂件时,是在当前Shell进程中执⾏,⽽不是像./与sh⽅式⼀样在新的Shell进程中执⾏,因此早先设置的变量在脚本⾥是可以读取到的。
source⼀般不⽤来执⾏业务脚本,最常见⽤途是在某些初始化脚本修改之后使其⽴即⽣效,即source /etc/profile这样。
Bonus Part:shebang对脚本执⾏的影响
shebang是指脚本⽂件中以字符#!开头的第⼀⾏,它⽤来指定这个脚本该⽤哪种解释器来解释。上⽂中出现的#!/bin/sh就表⽰应该使⽤
sh(在这⾥就是Bash)来解释它。
需要注意,只有./⽅式执⾏脚本才会读取shebang并调⽤指定的解释器,⽽“sh⽅式”(sh当然可以换成任意其他的解释器)会忽略shebang。举个例⼦,新建⼀个Python脚本,但是shebang仍然故意错写:
#!/bin/sh
print "Hello World!"
如果执⾏./my_script.py的话,会报语法错误,因为Bash不能解释Python;执⾏python my_script.py是正常的,因为会直接⽤Python解释器。若把shebang改回正确的#!/usr/bin/python,那么两种⽅式都能正常执⾏。
实际上,shebang甚⾄可以写成任意外部命令(当然不推荐这样做)。举个有趣的栗⼦,创建脚本rm_self.sh:
#!/bin/rm
echo "I am still here!"
执⾏sh rm_self.sh,会输出"I am still here!",并且rm_self.sh⽂件本⾝还在。但若是执⾏./rm_self.sh,则没有任何输出,并且rm_self.sh ⽂件本⾝消失了。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。