符号链接(Symbolic)、package.json的bin属性与Shebang

以全局方式安装的npm包,如gulpwebpack等为什么可以像mkdircopy这样的shell命令(或程序)一样,在任何文件夹都可以通过命令行调用?跟环境变量又有什么关系?什么是符号链接?

在解释这些问题前,先了解下linux上文件执行相关内容,会更容易理解些

◎ Linux文件执行的方式

最开始接触shell时,一般会提及shell脚本文件的不同执行方式。先创建有一条简单命令的shell脚本文件hello.sh

echo "hello world"
复制代码
  • 一种方式是,直接运行shell解释器,把脚本名作为解释器命令的参数
/bin/sh hello.sh 
# or /bin/zsh hello.sh or zsh hello.sh
hello world
复制代码

其中/bin/sh/bin/zsh 为系统支持的不shell类型,直接使用zsh、sh也是等效的,因为shell会根据环境变量($PATH)路径找到相应的/bin/sh/bin/zsh

  • 另一种方式,现在hello.sh做一点小改变
#! /bin/zsh
echo "hello world"
复制代码

改好之后,需要先让hello.sh文件具有执行权限,因为会直接使用hello.sh执行,否则会报permission denied的错误(即没有执行权限)。拥有执行权限后,就可以这样执行了

>> ./hello.sh   # ro /path/to/hello.sh
hello world
复制代码

实际上,现在依然可以使用/bin/sh hello.sh 的形式来执行。问题是,为什么加了 #! /bin/zsh 后可以./hello.sh 这样执行呢?
#! /bin/zsh这行有个术语,叫Shebang(or Hashbang),是shell脚本的标准起始行,#! 后面是指定解释器的绝对路径。它由shell程序解析,作用是告诉shell程序使用#! 后面路径指定的解释器来执行本文件,并把当前文件作为解释器的参数。所以 ./hello.sh 作用与 /bin/zsh hellow.sh(或 /bin/zsh $PWD/hello.sh)等效。

◎ 执行js文件

执行shell脚本的方式,同样适用js文件和其他文件的执行,只要指定相应的解释程序(即程序命令)。如一个简单的node文件,hello.js

console.log('hello world!')
复制代码

可以这样执行(已经安装了node)

>> node hello.js
hello world! 
# or 
>> /usr/local/bin/node hello.js 
# 根据不同系统,可能node命令的路径不一样,这里使用的是Mac 
hello world!
复制代码

也可以使用使用Shebang的方式,hello.js增加一行,告诉shell程序,用node解释器执行该文档,并给文件增加执行权限

#! /usr/local/bin/node
console.log('hello world!')

// 第一行也可以这样
// #! /usr/bin/env node 
// 跟上面的区别是,会使用最先出现在环境变量的node解释器
复制代码

就可以这样执行

>> ./hello.js # or $PWD/hello.js
hello world!
复制代码

既然可以使用./hello.sh(./hello.js)直接执行文件,那是否可以使用hello.sh(hello.js)来直接执行呢?当然是可以的。

一个方式就是把当前目录下的hello.js 移动到当前环境变量的任一目录下(使用echo $PATH 查看当前配置的环境变量)或者把当前目录添加进环境变量,这样就可以直接test.js来执行了,因为shell可以通过环境变量目录检索到test.js这个文件,不仅可以在当前目录直接使用,也可以在其他任何目录使用,跟其他命令一样。 当然这并不是推荐的做法,而且确实不好。

更好的方式是使用符号链接(Symbolic link)

◎ 符号链接与package.json的bin字段

符号链接(Symbolic link)或软链接,是一种特殊的文件,包含一条以绝对路径或相对路径形式指向其他文件或目录的引用,跟快捷方式的功能类似。继续以hello.js作为示例

上面谈到,为了能直接使用文件名hello.js的方式执行,一种方式是让文件处于shell程序通过环境变量可检索到的目录内;另一种即是通过对文件建立符号链接的方式,使用ln命令

  • 创建符号链接
# 创建symbolic命令语法,-s为创建symbolic链接
# ln -s /path/to/file /path/to/symbolic 
ln -s $PWD/hello.js /usr/local/bin/hello.js
复制代码

这里把符号链接文件放入环境变量路径下面,这样在当前用户任意目录下面,都可以通过hello.js命令执行,当然,如果符号链接不在环境变量下,则执行方式还是一样的./hello.js。 符号链接的名字是可以随意的,只要不与现有符号链接重名就好

# 创建全局的符号链接后
>> hello.js
hello world!
# 改为其他名字 
>> ln -s $PWD/hello.js /usr/local/bin/hello
>> hello
hello world!
复制代码
  • 删除符号链接 删除符号链接的方式与删除文件一样,只是删除符号文件,对原文件没有影响
rm /usr/local/bin/hello
复制代码
  • packge.json的bin字段
    有了以上关于符号链接、文件执行、环境变量相关的概念。则npm包命令的执行就很好理解了。以全局方式安装的npm包,npm会在安装的时候在/usr/local/bin/ 目录(一般会在环境变量里面)下面创建bin字段所指定的symbolic,如package.json的bin配置为
{ "bin" : { "myapp" : "./cli.js" } }
复制代码

则会创建 /usr/local/bin/myapp 符号链接指向可执行的cli.js。如果是以本地方式安装,则会在项目下的node_modules/.bin/myapp 下面创建符号链接(这个只能再node_modules/.bin 目录下通过./myapp 方式执行)。myapp命令执行的其实是cli.js,且官方也要求需要写Shebang#! /use/bin/env node,指定解释器为node

参考:

猜你喜欢

转载自juejin.im/post/5c5f9398f265da2dd8686ef9