符號連結(Symbolic)、package.json的bin屬性與Shebang
以全域性方式安裝的npm包,如gulp
、webpack
等為什麼可以像mkdir
、copy
這樣的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
參考: