本文首发于个人博客 Cyy’s Blog
转载请注明出处 https://cyyjs.top/blog/5f689b5196a836000da288fd

作为前端开发,安装npm包时,我们经常会发现,有些包,会在我们系统中安装一些命令行可执行的命令,如vue-clibable-cli等等,那么这些命令是如何实现的呢?

# package.json中的bin

vue-cli为例,我们打开GitHub,查看packages/@vue/cli目录下的package.json文件:

{
  "name": "@vue/cli",
  "version": "4.5.6",
  "description": "Command line interface for rapid Vue.js development",
  "bin": {
    "vue": "bin/vue.js"
  },
  "types": "types/index.d.ts",
  ...
}

会发现,里面有一个bin字段,这个字段的作用是什么呢?

npm官方文档中有介绍:
该字段的作用是用来安装npm的可执行文件,其中bin字段中的key如这里的vue就是命令的名字,对应的值就是具体执行的文件。安装的时候,npm会创建一个软连接到prefix/bin目录下进行全局安装,类似于快捷方式,如果是局部安装,会链接到./node_modules/.bin/目录下。

如果命令名和包名一样,也可以用字符串的形式,如{ bin: "bin/vue.js" }

如上vue-cli全局安装成功后,就就可以在系统/usr/local/bin/目录下找到一个命名为vue的如链接。如果vue-cli是通过yarn全局安装的话,它指向的具体目录应该为/Users/user/.config/yarn/global/node_modules/@vue/cli/bin/vue.js

# 可执行文件

我们打开bin/vue.js文件,会发现它与普通的js文件有一些不太一样:文件第一行多了如下代码:

#!/usr/bin/env node

这段代码是什么意思呢?
这种写法是创造Unix的人搞出来的,沿用至今,意思是用#!指定脚本的解释器,就是指定运行当前文件的命令是什么。

比如:

#!/bin/bash
echo 'hello word!'

告诉系统用bash来执行这个文件。

我们通过which node可以看到我们node命令所在的位置:/usr/local/bin/node
那么为什么vue中使用的是#!/usr/bin/env node而不是#!/usr/local/bin/node

这是出于系统兼容性考虑,为了防止操作系统用户没有将node装在默认的/usr/local/bin路径里,当系统看到这一行的时候,首先会到env设置里查找node的安装路径,再调用对应路径下的解释器程序完成操作。

# 如何直接编写一个命令

通过上面的了解,我们可能会想,通过#!/usr/bin/env node编写一个js文件,能否直接运行呢?我们可以编写一个js文件来试一试:

test.js:

#!/usr/bin/env node
console.log('hello word!')

我们知道在命令行中执行的命令都是全局安装的,或者路径是在环境变量中的,我们可以为此文件建立一个软连到对应的环境变量所在的目录中去,如下:

ln -s ./test.js /usr/bin/test

然后直接执行命令test发现报错了,没有权限:

permission denied: test

因为创建的文件,一般是没有可执行权限的,我们为文件添加执行权限:

chmod +x test.js

然后再次执行,不出意外可以看到能够正确输出hello word!了。

此时我们已经成功创建了一个全局的命令。

# 扩展

既然我们知道#!/usr/bin/env node指定了文件解释器的类型为node,就可以直接用node来执行文件内的代码,那么如果使用别的语言是否可以呢,考虑以下代码:
test.js文件的内容,后缀什么的无所谓,也可以没有后缀

#!/usr/bin/env python
print('Hello python!')

然后在控制台输入test命令,发现正常输出了Hello python!