十四、創(chuàng)建跨平臺(tái) shell 腳本
原文:
exploringjs.com/nodejs-shell-scripting/ch_creating-shell-scripts.html
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
-
14.1 所需的知識(shí)
- 14.1.1 本章的下一步是什么
-
14.2 Node.js ESM 模塊作為 Unix 上獨(dú)立的 shell 腳本
-
14.2.1 Unix 上的 Node.js shell 腳本
-
14.2.2 Unix 上的 Hashbangs
-
14.2.3 在 Unix 上使文件可執(zhí)行
-
14.2.4 直接運(yùn)行
hello.mjs
-
-
14.3 使用 shell 腳本創(chuàng)建一個(gè) npm 包
-
14.3.1 設(shè)置包的目錄
-
14.3.2 添加依賴項(xiàng)
-
14.3.3 向包添加內(nèi)容
-
14.3.4 在不安裝的情況下運(yùn)行 shell 腳本
-
-
14.4 npm 如何安裝 shell 腳本
-
14.4.1 在 Unix 上安裝
-
14.4.2 在 Windows 上安裝
-
-
14.5 將示例包發(fā)布到 npm 注冊(cè)表
-
14.5.1 哪些文件被發(fā)布?哪些文件被忽略?
-
14.5.2 檢查包是否正確配置
-
14.5.3
npm publish
: 將包上傳到 npm 注冊(cè)表 -
14.5.4 在發(fā)布之前自動(dòng)執(zhí)行任務(wù)
-
-
14.6 Unix 上任意擴(kuò)展名的獨(dú)立 Node.js shell 腳本
-
14.6.1 Unix:通過自定義可執(zhí)行文件設(shè)置任意文件擴(kuò)展名
-
14.6.2 Unix:通過 shell prolog 設(shè)置任意文件擴(kuò)展名
-
-
14.7 Windows 上獨(dú)立的 Node.js shell 腳本
-
14.7.1 Windows:配置文件擴(kuò)展名
.mjs
-
14.7.2 Windows 命令 shell:通過 shell prolog 運(yùn)行 Node.js 腳本
-
14.7.3 Windows PowerShell: 通過 shell prolog 運(yùn)行 Node.js 腳本
-
-
14.8 為 Linux、macOS 和 Windows 創(chuàng)建本機(jī)二進(jìn)制文件
-
14.9 Shell 路徑:確保 shell 找到腳本
-
14.9.1 Unix:
$PATH
-
14.9.2 在 Windows 上更改 PATH 變量(命令 shell、PowerShell)
-
在本章中,我們將學(xué)習(xí)如何通過 Node.js ESM 模塊實(shí)現(xiàn) shell 腳本。有兩種常見的方法可以這樣做:
-
我們可以編寫一個(gè)獨(dú)立的腳本并自己安裝它。
-
我們可以把我們的腳本放在一個(gè) npm 包中,并使用包管理器來安裝它。這也給了我們選擇將包發(fā)布到 npm 注冊(cè)表的選項(xiàng),這樣其他人也可以安裝它。
14.1?所需知識(shí)
你應(yīng)該對(duì)以下兩個(gè)主題有一定的了解:
-
ECMAScript 模塊,如“JavaScript for impatient programmers”中的章節(jié)“模塊”中所解釋的。
-
npm 軟件包,如§5“軟件包:JavaScript 的軟件分發(fā)單元”中所解釋的。
14.1.1?本章的下一步
Windows 實(shí)際上不支持用 JavaScript 編寫的獨(dú)立的 shell 腳本。因此,我們首先要了解如何為 Unix 編寫帶有文件擴(kuò)展名的獨(dú)立腳本。這些知識(shí)將幫助我們創(chuàng)建包含 shell 腳本的軟件包。后來,我們會(huì)學(xué)到:
-
在 Windows 上編寫?yīng)毩⒌?shell 腳本的技巧。
-
在 Unix 上編寫?yīng)毩⒌?shell 腳本不帶文件擴(kuò)展名的技巧。
通過軟件包安裝 shell 腳本是§13“安裝 npm 軟件包和運(yùn)行 bin 腳本”的主題。
14.2?Node.js ESM 模塊作為 Unix 上獨(dú)立的 shell 腳本
讓我們將一個(gè) ESM 模塊轉(zhuǎn)換為 Unix shell 腳本,這樣我們就可以在不在軟件包中的情況下運(yùn)行它。原則上,我們可以選擇 ESM 模塊的兩個(gè)文件擴(kuò)展名:
-
.mjs
文件總是被解釋為 ESM 模塊。 -
只有在最接近的
package.json
中有以下條目時(shí),.js
文件才會(huì)被解釋為 ESM 模塊:"type": "module"
然而,由于我們想創(chuàng)建一個(gè)獨(dú)立的腳本,我們不能依賴于package.json
是否存在。因此,我們必須使用文件擴(kuò)展名.mjs
(我們稍后會(huì)介紹解決方法)。
以下文件名為hello.mjs
:
import * as os from 'node:os';
const {username} = os.userInfo();
console.log(`Hello ${username}!`);
我們已經(jīng)可以運(yùn)行這個(gè)文件:
node hello.mjs
14.2.1?Unix 上的 Node.js shell 腳本
我們需要做兩件事,這樣我們才能像這樣運(yùn)行hello.mjs
:
./hello.mjs
這些事情是:
-
在
hello.mjs
開頭添加哈希標(biāo)記行 -
使
hello.mjs
可執(zhí)行
14.2.2?Unix 上的哈希標(biāo)記
在 Unix shell 腳本中,第一行是哈希標(biāo)記 - 元數(shù)據(jù),告訴 shell 如何執(zhí)行文件。例如,這是 Node.js 腳本最常見的哈希標(biāo)記:
#!/usr/bin/env node
這一行被稱為“哈希標(biāo)記”,因?yàn)樗跃?hào)和感嘆號(hào)開頭。它也經(jīng)常被稱為“shebang”。
如果一行以井號(hào)開頭,在大多數(shù) Unix shell(sh、bash、zsh 等)中它是一個(gè)注釋。因此,這些 shell 會(huì)忽略哈希標(biāo)記。Node.js 也會(huì)忽略它,但只有當(dāng)它是第一行時(shí)。
為什么我們不使用這個(gè)哈希標(biāo)記呢?
#!/usr/bin/node
并非所有的 Unix 都將 Node.js 二進(jìn)制文件安裝在那個(gè)路徑。那么這個(gè)路徑呢?
#!node
然而,并非所有的 Unix 都允許相對(duì)路徑。這就是為什么我們通過絕對(duì)路徑引用env
并用它來為我們運(yùn)行node
。
有關(guān) Unix 哈希標(biāo)記的更多信息,請(qǐng)參見 Alex Ewerl?f 的“Node.js shebang”。
14.2.2.1?將參數(shù)傳遞給 Node.js 二進(jìn)制文件
如果我們想要傳遞參數(shù),比如命令行選項(xiàng)給 Node.js 二進(jìn)制文件怎么辦?
在許多 Unix 上使用env
的一個(gè)解決方案是使用選項(xiàng)-S
,這樣可以防止它將其所有參數(shù)解釋為一個(gè)二進(jìn)制文件的名稱:
#!/usr/bin/env -S node --disable-proto=throw
在 macOS 上,即使沒有-S
,上一個(gè)命令也可以工作;在 Linux 上通常不行。
14.2.2.2?哈希標(biāo)記陷阱:在 Windows 上創(chuàng)建哈希標(biāo)記
如果我們?cè)?Windows 上使用文本編輯器創(chuàng)建一個(gè) ESM 模塊,該模塊應(yīng)該在 Unix 或 Windows 上作為腳本運(yùn)行,我們必須添加一個(gè)哈希標(biāo)記。如果我們這樣做,第一行將以 Windows 行終止符\r\n
結(jié)束:
#!/usr/bin/env node\r\n
在 Unix 上運(yùn)行帶有這樣一個(gè)哈希標(biāo)記的文件會(huì)產(chǎn)生以下錯(cuò)誤:
env: node\r: No such file or directory
也就是說,env
認(rèn)為可執(zhí)行文件的名稱是node\r
。有兩種方法可以解決這個(gè)問題。
首先,一些編輯器會(huì)自動(dòng)檢查文件中已經(jīng)使用的行終止符,并繼續(xù)使用它們。例如,Visual Studio Code 在右下角的狀態(tài)欄中顯示當(dāng)前的行終止符(它稱之為“行尾序列”):
-
LF
(換行)用于 Unix 行終止符\n
-
CRLF
(回車換行)用于 Windows 行終止符\r\n
我們可以通過點(diǎn)擊狀態(tài)信息來切換選擇行終止符。
其次,我們可以創(chuàng)建一個(gè)最小的文件my-script.mjs
,其中只有 Unix 行終止符,我們?cè)?Windows 上從不編輯它:
#!/usr/bin/env node
import './main.mjs';
14.2.3 在 Unix 上使文件可執(zhí)行
為了成為一個(gè) shell 腳本,hello.mjs
還必須是可執(zhí)行的(文件的權(quán)限),除了具有哈希標(biāo)記:
chmod u+x hello.mjs
請(qǐng)注意,我們使文件對(duì)于創(chuàng)建它的用戶(u
)是可執(zhí)行的(x
),而不是對(duì)于所有人。
14.2.4 直接運(yùn)行hello.mjs
hello.mjs
現(xiàn)在是可執(zhí)行的,看起來像這樣:
#!/usr/bin/env node
import * as os from 'node:os';
const {username} = os.userInfo();
console.log(`Hello ${username}!`);
因此,我們可以這樣運(yùn)行它:
./hello.mjs
遺憾的是,沒有辦法告訴node
將任意擴(kuò)展名的文件解釋為 ESM 模塊。這就是為什么我們必須使用擴(kuò)展名.mjs
。解決方法是可能的,但復(fù)雜,我們稍后會(huì)看到。
14.3 創(chuàng)建一個(gè)帶有 shell 腳本的 npm 包
在本節(jié)中,我們將使用 shell 腳本創(chuàng)建一個(gè) npm 包。然后我們將研究如何安裝這樣一個(gè)包,以便它的腳本可以在您系統(tǒng)的命令行上使用(Unix 或 Windows)。
完成的包可以在這里找到:
-
在 GitHub 上為
rauschma/demo-shell-scripts
-
在 npm 上為
@rauschma/demo-shell-scripts
14.3.1 設(shè)置包的目錄
這些命令在 Unix 和 Windows 上都適用:
mkdir demo-shell-scripts
cd demo-shell-scripts
npm init --yes
現(xiàn)在有以下文件:
demo-shell-scripts/
package.json
14.3.1.1 未發(fā)布包的package.json
一個(gè)選項(xiàng)是創(chuàng)建一個(gè)包并不將其發(fā)布到 npm 注冊(cè)表。我們?nèi)匀豢梢栽谖覀兊南到y(tǒng)上安裝這樣一個(gè)包(如后面所述)。在這種情況下,我們的package.json
如下所示:
{
"private": true,
"license": "UNLICENSED"
}
解釋:
-
將包設(shè)為私有意味著不需要名稱或版本,并且不能意外發(fā)布。
-
"UNLICENSED"
拒絕他人以任何條件使用該包。
14.3.1.2 發(fā)布包的package.json
如果我們想將我們的包發(fā)布到 npm 注冊(cè)表,我們的package.json
如下所示:
{
"name": "@rauschma/demo-shell-scripts",
"version": "1.0.0",
"license": "MIT"
}
對(duì)于您自己的包,您需要用適合您的包名替換"name"
的值:
-
或者一個(gè)全局唯一的名稱。這樣的名稱應(yīng)該只用于重要的包,因?yàn)槲覀儾幌M柚蛊渌耸褂迷撁Q。
-
或者作用域名稱:要發(fā)布一個(gè)包,你需要一個(gè) npm 賬戶(如何獲得一個(gè)賬戶將在后面解釋)。你的賬戶名可以作為包名的作用域。例如,如果你的賬戶名是
jane
,你可以使用以下包名:"name": "@jane/demo-shell-scripts"
14.3.2 添加依賴項(xiàng)
接下來,我們安裝一個(gè)我們想在其中一個(gè)腳本中使用的依賴項(xiàng) - 包lodash-es
(Lodash的 ESM 版本):
npm install lodash-es
這個(gè)命令:
-
創(chuàng)建目錄
node_modules
。 -
將包
lodash-es
安裝到其中。 -
在
package.json
中添加以下屬性:"dependencies": { "lodash-es": "?.17.21" }
-
創(chuàng)建文件
package-lock.json
。
如果我們只在開發(fā)過程中使用一個(gè)包,我們可以將其添加到"devDependencies"
而不是"dependencies"
,npm 只有在我們?cè)诎哪夸浿羞\(yùn)行npm install
時(shí)才會(huì)安裝它,而不是如果我們將它安裝為一個(gè)依賴項(xiàng)。單元測(cè)試庫是一個(gè)典型的開發(fā)依賴。
這是我們可以安裝開發(fā)依賴的兩種方式:
-
通過
npm install some-package
。 -
我們可以使用
npm install some-package --save-dev
,然后手動(dòng)將some-package
的條目從"dependencies"
移動(dòng)到"devDependencies"
。
第二種方法意味著我們可以很容易地推遲決定一個(gè)包是一個(gè)依賴還是一個(gè)開發(fā)依賴。
14.3.3 向包添加內(nèi)容
讓我們添加一個(gè) readme 文件和兩個(gè) shell 腳本homedir.mjs
和versions.mjs
:
demo-shell-scripts/
package.json
package-lock.json
README.md
src/
homedir.mjs
versions.mjs
我們必須告訴 npm 關(guān)于這兩個(gè) shell 腳本,這樣它才能為我們安裝它們。這就是package.json
中的"bin"
屬性的作用:
"bin": {
"homedir": "./src/homedir.mjs",
"versions": "./src/versions.mjs"
}
如果我們安裝這個(gè)包,兩個(gè)名為homedir
和versions
的 shell 腳本將變得可用。
你可能更喜歡使用.js
作為 shell 腳本的文件擴(kuò)展名。然后,你需要在package.json
中添加以下兩個(gè)屬性,而不是之前的屬性:
"type": "module",
"bin": {
"homedir": "./src/homedir.js",
"versions": "./src/versions.js"
}
第一個(gè)屬性告訴 Node.js 應(yīng)該將.js
文件解釋為 ESM 模塊(而不是 CommonJS 模塊 - 這是默認(rèn)值)。
homedir.mjs
的樣子如下:
#!/usr/bin/env node
import {homedir} from 'node:os';
console.log('Homedir: ' + homedir());
這個(gè)模塊以前面提到的 hashbang 開始,這是在 Unix 上使用它時(shí)所必需的。它從內(nèi)置模塊node:os
中導(dǎo)入函數(shù)homedir()
,調(diào)用它并將結(jié)果記錄到控制臺(tái)(即標(biāo)準(zhǔn)輸出)。
請(qǐng)注意,homedir.mjs
不需要可執(zhí)行;npm 在安裝時(shí)確保"bin"
腳本的可執(zhí)行性(我們很快就會(huì)看到如何做到這一點(diǎn))。
versions.mjs
的內(nèi)容如下:
#!/usr/bin/env node
import {pick} from 'lodash-es';
console.log(
pick(process.versions, ['node', 'v8', 'unicode'])
);
我們從 Lodash 中導(dǎo)入pick()
函數(shù),并用它來顯示process.versions
對(duì)象的三個(gè)屬性。
14.3.4 在不安裝的情況下運(yùn)行 shell 腳本
我們可以這樣運(yùn)行,例如,homedir.mjs
:
cd demo-shell-scripts/
node src/homedir.mjs
14.4 npm 如何安裝 shell 腳本
14.4.1 在 Unix 上安裝
例如,homedir.mjs
這樣的腳本在 Unix 上不需要可執(zhí)行,因?yàn)?npm 通過可執(zhí)行符號(hào)鏈接來安裝它:
-
如果我們?nèi)职惭b包,鏈接將被添加到
$PATH
中列出的目錄中。 -
如果我們將包作為依賴項(xiàng)本地安裝,鏈接將被添加到
node_modules/.bin/
中
14.4.2 在 Windows 上安裝
要在 Windows 上安裝homedir.mjs
,npm 會(huì)創(chuàng)建三個(gè)文件:
-
homedir.bat
是一個(gè)使用node
來執(zhí)行homedir.mjs
的命令 shell 腳本。 -
homedir.ps1
對(duì) PowerShell 也是一樣的。 -
homedir
對(duì) Cygwin、MinGW 和 MSYS 也是一樣的。
npm 會(huì)將這些文件添加到一個(gè)目錄中:
-
如果我們?nèi)职惭b包,文件將被添加到列在
%Path%
中的目錄中。 -
如果我們將包作為依賴項(xiàng)本地安裝,文件將被添加到
node_modules/.bin/
中
14.5 將示例包發(fā)布到 npm 注冊(cè)表
讓我們將包@rauschma/demo-shell-scripts
(之前創(chuàng)建的)發(fā)布到 npm。在使用npm publish
上傳包之前,我們應(yīng)該檢查一切是否配置正確。
14.5.1 發(fā)布了哪些文件?哪些文件被忽略了?
在發(fā)布時(shí)排除和包含文件時(shí)使用以下機(jī)制:
-
頂層文件
.gitignore
中列出的文件會(huì)被排除。- 我們可以用與
.gitignore
相同的格式覆蓋.npmignore
。
- 我們可以用與
-
package.json
屬性"files"
包含一個(gè)數(shù)組,其中包含要包括的文件的名稱。這意味著我們可以選擇列出要排除的文件(在.npmignore
中)或要包括的文件。 -
一些文件和目錄默認(rèn)被排除在外 - 例如:
-
node_modules
-
.*.swp
-
._*
-
.DS_Store
-
.git
-
.gitignore
-
.npmignore
-
.npmrc
-
npm-debug.log
除了這些默認(rèn)值,點(diǎn)文件(文件名以點(diǎn)開頭的文件)也會(huì)被包括進(jìn)來。
-
-
以下文件永遠(yuǎn)不會(huì)被排除:
-
package.json
-
README.md
及其變體 -
CHANGELOG
及其變體 -
LICENSE
,LICENCE
-
npm 文檔中有關(guān)于發(fā)布時(shí)包含和排除的更多細(xì)節(jié)。
14.5.2 檢查包是否正確配置
在上傳包之前,我們可以檢查幾件事情。
14.5.2.1 檢查將要上傳的文件
npm install
的dry run會(huì)在不上傳任何內(nèi)容的情況下運(yùn)行該命令:
npm publish --dry-run
這會(huì)顯示將要上傳的文件以及有關(guān)包的幾項(xiàng)統(tǒng)計(jì)數(shù)據(jù)。
我們也可以創(chuàng)建一個(gè)包的存檔,就像它在 npm 注冊(cè)表上存在一樣:
npm pack
此命令在當(dāng)前目錄中創(chuàng)建文件rauschma-demo-shell-scripts-1.0.0.tgz
。
14.5.2.2 全局安裝軟件包-而不上傳
我們可以使用以下兩個(gè)命令之一在全局安裝我們的軟件包而不將其發(fā)布到 npm 注冊(cè)表:
npm link
npm install . -g
要查看是否有效,我們可以打開一個(gè)新的 shell 并檢查這兩個(gè)命令是否可用。我們還可以列出所有全局安裝的軟件包:
npm ls -g
14.5.2.3 本地安裝軟件包(作為依賴項(xiàng))-而不上傳
要將我們的包安裝為依賴項(xiàng),我們必須執(zhí)行以下命令(當(dāng)我們?cè)谀夸?code>demo-shell-scripts中時(shí)):
cd ..
mkdir sibling-directory
cd sibling-directory
npm init --yes
npm install ../demo-shell-scripts
現(xiàn)在我們可以運(yùn)行,例如,homedir
,使用以下兩個(gè)命令之一:
npx homedir
./node_modules/.bin/homedir
14.5.3 npm publish
:將軟件包上傳到 npm 注冊(cè)表
在我們上傳軟件包之前,我們需要?jiǎng)?chuàng)建一個(gè) npm 用戶帳戶。 npm 文檔描述了如何做到這一點(diǎn)。
然后我們最終可以發(fā)布我們的軟件包:
npm publish --access public
我們必須指定公共訪問權(quán)限,因?yàn)槟J(rèn)值是:
-
對(duì)于未經(jīng)范圍限定的軟件包,使用
public
-
對(duì)于受范圍限制的軟件包使用
restricted
。此設(shè)置使軟件包private-這是一個(gè)付費(fèi)的 npm 功能,主要由公司使用,并且與package.json
中的"private":true
不同。引用 npm:“使用 npm 私有軟件包,您可以使用 npm 注冊(cè)表來托管僅對(duì)您和選擇的協(xié)作者可見的代碼,允許您在項(xiàng)目中管理和使用私有代碼以及公共代碼。”
選項(xiàng)--access
只在第一次發(fā)布時(shí)有效。之后,我們可以省略它,并且需要使用npm access
來更改訪問級(jí)別。
我們可以通過publishConfig.access
在package.json
中更改初始npm publish
的默認(rèn)值:
"publishConfig": {
"access": "public"
}
14.5.3.1 每次上傳都需要一個(gè)新版本
一旦我們使用特定版本上傳了軟件包,我們就不能再使用該版本,我們必須增加版本的三個(gè)組件中的任何一個(gè):
major.minor.patch
-
如果我們進(jìn)行了重大更改,則增加
major
。 -
如果我們進(jìn)行了向后兼容的更改,則增加
minor
。 -
如果我們進(jìn)行了不會(huì)真正改變 API 的小修復(fù),則增加
patch
。
14.5.4 每次發(fā)布前自動(dòng)執(zhí)行任務(wù)
可能有一些步驟我們想要在上傳軟件包之前每次執(zhí)行-例如:
-
運(yùn)行單元測(cè)試
-
將 TypeScript 代碼編譯為 JavaScript 代碼
這可以通過package.json
屬性“scripts”
自動(dòng)完成。該屬性可以如下所示:
"scripts": {
"build": "tsc",
"test": "mocha --ui qunit",
"dry": "npm publish --dry-run",
"prepublishOnly": "npm run test && npm run build"
}
mocha
是一個(gè)單元測(cè)試庫。tsc
是 TypeScript 編譯器。
在npm publish
之前運(yùn)行以下軟件包腳本:
-
"prepare"
被運(yùn)行:-
在
npm pack
之前 -
在
npm publish
之前 -
在本地
npm install
沒有參數(shù)的情況下
-
-
"prepublishOnly"
僅在npm publish
之前運(yùn)行。
有關(guān)此主題的更多信息,請(qǐng)參見§15“通過 npm 軟件包腳本運(yùn)行跨平臺(tái)任務(wù)”。
14.6 在 Unix 上使用任意擴(kuò)展名的獨(dú)立 Node.js shell 腳本
14.6.1 Unix:通過自定義可執(zhí)行文件使用任意文件名擴(kuò)展名
Node.js 二進(jìn)制文件node
使用文件擴(kuò)展名來檢測(cè)文件是哪種類型的模塊。目前沒有命令行選項(xiàng)來覆蓋它。默認(rèn)值是 CommonJS,這不是我們想要的。
但是,我們可以創(chuàng)建我們自己的可執(zhí)行文件來運(yùn)行 Node.js,并將其命名為node-esm
,然后我們可以將我們以前的獨(dú)立腳本hello.mjs
重命名為hello
(沒有任何擴(kuò)展名),如果我們將第一行更改為:
#!/usr/bin/env node-esm
以前,env
的參數(shù)是node
。
這是 Andrea Giammarchi 提出的node-esm 的實(shí)現(xiàn):
#!/usr/bin/env sh
input_file=$1
shift
exec node --input-type=module - $@ < $input_file
此可執(zhí)行文件通過標(biāo)準(zhǔn)輸入將腳本內(nèi)容發(fā)送到node
。命令行選項(xiàng)--input-type=module
告訴 Node.js 它接收的文本是一個(gè) ESM 模塊。
我們還使用以下 Unix shell 功能:
-
$1
包含傳遞給node-esm
的第一個(gè)參數(shù)-腳本的路徑。 -
我們通過
shift
刪除參數(shù)$0
(node-esm
的路徑)并將剩余的參數(shù)傳遞給node
。 -
exec
用node
運(yùn)行替換當(dāng)前進(jìn)程。這確保腳本以與node
相同的代碼退出。 -
連字符(
-
)將 Node 的參數(shù)與腳本的參數(shù)分開。
在使用node-esm
之前,我們必須確保它是可執(zhí)行的,并且可以通過$PATH
找到。如何做到這一點(diǎn)將在后面解釋。
14.6.2?Unix:通過 shell prolog 任意文件擴(kuò)展名
我們已經(jīng)看到,我們無法為文件指定模塊類型,只能為標(biāo)準(zhǔn)輸入指定。因此,我們可以編寫一個(gè) Unix shell 腳本hello
,使用 Node.js 將自身作為 ESM 模塊運(yùn)行(基于sambal.org 的工作):
#!/bin/sh
':' // ; cat "$0" | node --input-type=module - $@ ; exit $?
import * as os from 'node:os';
const {username} = os.userInfo();
console.log(`Hello ${username}!`);
我們?cè)谶@里使用的大多數(shù) shell 功能在本章的開頭都有描述。$?
包含上次執(zhí)行的 shell 命令的退出代碼。這使hello
能夠以與node
相同的代碼退出。
此腳本使用的關(guān)鍵技巧是第二行既是 Unix shell 腳本代碼又是 JavaScript 代碼:
-
作為 shell 腳本代碼,它運(yùn)行引用命令
':'
,除了擴(kuò)展其參數(shù)和執(zhí)行重定向外,什么也不做。它的唯一參數(shù)是路徑//
。然后將當(dāng)前文件的內(nèi)容傳遞給node
二進(jìn)制文件。 -
作為 JavaScript 代碼,它是字符串
':'
(被解釋為表達(dá)式語句并且什么也不做),然后是一個(gè)注釋。
將 shell 代碼從 JavaScript 中隱藏的另一個(gè)好處是,當(dāng)處理和顯示語法時(shí),JavaScript 編輯器不會(huì)感到困惑。
14.7?在 Windows 上獨(dú)立的 Node.js shell 腳本
14.7.1?Windows:配置文件擴(kuò)展名.mjs
在 Windows 上創(chuàng)建獨(dú)立的 Node.js shell 腳本的一個(gè)選項(xiàng)是使用文件擴(kuò)展名.mjs
并配置文件以便通過node
運(yùn)行。遺憾的是,這僅適用于命令 shell,而不適用于 PowerShell。
另一個(gè)缺點(diǎn)是我們無法以這種方式傳遞參數(shù)給腳本:
>more args.mjs
console.log(process.argv);
>.\args.mjs one two
[
'C:\\Program Files\\nodejs\\node.exe',
'C:\\Users\\jane\\args.mjs'
]
>node args.mjs one two
[
'C:\\Program Files\\nodejs\\node.exe',
'C:\\Users\\jane\\args.mjs',
'one',
'two'
]
我們?nèi)绾闻渲?Windows,使命令 shell 直接運(yùn)行諸如args.mjs
之類的文件?
文件關(guān)聯(lián)指定在 shell 中輸入其名稱時(shí)打開文件的應(yīng)用程序。如果我們將文件擴(kuò)展名.mjs
與 Node.js 二進(jìn)制文件關(guān)聯(lián),我們可以在 shell 中運(yùn)行 ESM 模塊。其中一種方法是通過設(shè)置應(yīng)用程序,如 Tim Fisher 在“如何更改 Windows 中的文件關(guān)聯(lián)”中所解釋的那樣。
如果我們還將.MJS
添加到變量%PATHEXT%
中,甚至在引用 ESM 模塊時(shí)可以省略文件擴(kuò)展名。此環(huán)境變量可以通過設(shè)置應(yīng)用程序永久更改-搜索“variables”。
14.7.2?Windows 命令 shell:通過 shell prolog 運(yùn)行 Node.js 腳本
在 Windows 上,我們面臨的挑戰(zhàn)是沒有像 hashbangs 這樣的機(jī)制。因此,我們必須使用類似于我們?cè)?Unix 上用于無擴(kuò)展名文件的解決方法:創(chuàng)建一個(gè)通過 Node.js 在自身內(nèi)部運(yùn)行 JavaScript 代碼的腳本。
命令 shell 腳本的文件擴(kuò)展名是.bat
。我們可以通過script.bat
或script
運(yùn)行名為script.bat
的腳本。
如果我們將其轉(zhuǎn)換為命令 shell 腳本hello.bat
,則hello.mjs
看起來是這樣的:
:: /*
@echo off
more +5 %~f0 | node --input-type=module - %*
exit /b %errorlevel%
*/
import * as os from 'node:os';
const {username} = os.userInfo();
console.log(`Hello ${username}!`);
將此代碼作為文件通過node
運(yùn)行需要兩個(gè)不存在的功能:
-
使用命令行選項(xiàng)來覆蓋默認(rèn)情況下將無擴(kuò)展名的文件解釋為 ESM 模塊。
-
跳過文件開頭的行。
因此,我們別無選擇,只能將文件的內(nèi)容傳遞給node
。我們還使用以下命令 shell 功能:
-
%~f0
包含當(dāng)前腳本的完整路徑,包括其文件擴(kuò)展名。相比之下,%0
包含用于調(diào)用腳本的命令。因此,前者的 shell 變量使我們能夠通過hello
或hello.bat
調(diào)用腳本。 -
%*
包含命令的參數(shù)-我們將其傳遞給node
。 -
%errorlevel%
包含上次執(zhí)行的命令的退出代碼。我們使用該值以與node
指定的相同代碼退出。
14.7.3 Windows PowerShell:通過 shell prolog 運(yùn)行 Node.js 腳本
我們可以使用與上一節(jié)中使用的類似的技巧,將hello.mjs
轉(zhuǎn)換為 PowerShell 腳本hello.ps1
,如下所示:
Get-Content $PSCommandPath | Select-Object -Skip 3 | node --input-type=module - $args
exit $LastExitCode
<#
import * as os from 'node:os';
const {username} = os.userInfo();
console.log(`Hello ${username}!`);
// #>
我們可以通過以下方式運(yùn)行此腳本:
.\hello.ps1
.\hello
但是,在我們這樣做之前,我們需要設(shè)置一個(gè)允許我們運(yùn)行 PowerShell 腳本的執(zhí)行策略(有關(guān)執(zhí)行策略的更多信息):
-
Windows 客戶端上的默認(rèn)策略是“受限”,不允許我們運(yùn)行任何腳本。
-
策略
RemoteSigned
允許我們運(yùn)行未簽名的本地腳本。下載的腳本必須經(jīng)過簽名。這是 Windows 服務(wù)器上的默認(rèn)設(shè)置。
以下命令讓我們運(yùn)行本地腳本:
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
14.8 為 Linux、macOS 和 Windows 創(chuàng)建本機(jī)二進(jìn)制文件
npm 包pkg
將 Node.js 包轉(zhuǎn)換為本機(jī)二進(jìn)制文件,即使在未安裝 Node.js 的系統(tǒng)上也可以運(yùn)行。它支持以下平臺(tái):Linux、macOS 和 Windows。
14.9 Shell 路徑:確保 shell 找到腳本
在大多數(shù) shell 中,我們可以輸入文件名而不直接引用文件,它們會(huì)在幾個(gè)目錄中搜索具有該名稱的文件并運(yùn)行它。這些目錄通常在一個(gè)特殊的 shell 變量中列出:
-
在大多數(shù) Unix shell 中,我們通過
$PATH
訪問它。 -
在 Windows 命令 shell 中,我們通過
%Path%
訪問它。 -
在 PowerShell 中,我們通過
$Env:PATH
訪問它。
我們需要 PATH 變量有兩個(gè)目的:
-
如果我們想要安裝我們自定義的 Node.js 可執(zhí)行文件
node-esm
。 -
如果我們想要運(yùn)行一個(gè)獨(dú)立的 shell 腳本而不直接引用其文件。
14.9.1 Unix:$PATH
大多數(shù) Unix shell 都有一個(gè)名為$PATH
的變量,列出了當(dāng)我們輸入命令時(shí) shell 查找可執(zhí)行文件的所有路徑。它的值可能如下所示:
$ echo $PATH
/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin
以下命令適用于大多數(shù) shell(來源),并且在我們離開當(dāng)前 shell 之前更改$PATH
:
export PATH="$PATH:$HOME/bin"
如果兩個(gè) shell 變量中有一個(gè)包含空格,則需要引號(hào)。
14.9.1.1 永久更改$PATH
在 Unix 上,$PATH
的配置取決于 shell。您可以通過以下方式找出自己正在運(yùn)行的 shell:
echo $0
MacOS 使用 Zsh,永久配置$PATH
的最佳位置是啟動(dòng)腳本$HOME/.zprofile
- 像這樣:
path+=('/Library/TeX/texbin')
export PATH
14.9.2 更改 Windows 上的 PATH 變量(命令 shell,PowerShell)
在 Windows 上,可以通過“設(shè)置”應(yīng)用程序永久配置命令 shell 和 PowerShell 的默認(rèn)環(huán)境變量-搜索“variables”。
評(píng)論文章來源地址http://www.zghlxwxcb.cn/news/detail-818879.html
十五、通過 npm 包腳本運(yùn)行跨平臺(tái)任務(wù)
原文:
exploringjs.com/nodejs-shell-scripting/ch_package-scripts.html
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
-
15.1 npm 包腳本
-
15.1.1 運(yùn)行包腳本的縮寫 npm 命令
-
15.1.2 用于運(yùn)行包腳本的 shell
-
15.1.3 防止自動(dòng)運(yùn)行包腳本
-
15.1.4 在 Unix 上為包腳本獲取選項(xiàng)完成
-
15.1.5 列出和組織包腳本
-
-
15.2 包腳本的種類
-
15.2.1 預(yù)先和后置腳本
-
15.2.2 生命周期腳本
-
-
15.3 運(yùn)行包腳本的 shell 環(huán)境
-
15.3.1 當(dāng)前目錄
-
15.3.2 shell 路徑
-
-
15.4 在包腳本中使用環(huán)境變量
-
15.4.1 獲取和設(shè)置環(huán)境變量
-
15.4.2 通過
.env
文件設(shè)置環(huán)境變量
-
-
15.5 包腳本的參數(shù)
-
15.6 npm 日志級(jí)別(產(chǎn)生多少輸出)
-
15.6.1 日志級(jí)別和打印到終端的信息
-
15.6.2 日志級(jí)別和寫入 npm 日志的信息
-
15.6.3 配置日志
-
15.6.4 在
npm install
期間運(yùn)行的生命周期腳本的輸出 -
15.6.5 觀察 npm 日志的工作方式
-
-
15.7 跨平臺(tái) shell 腳本
-
15.7.1 路徑和引用
-
15.7.2 鏈接命令
-
15.7.3 包腳本的退出代碼
-
15.7.4 管道和重定向輸入和輸出
-
15.7.5 適用于兩個(gè)平臺(tái)的命令
-
15.7.6 運(yùn)行 bin 腳本和包內(nèi)模塊
-
15.7.7
node --eval
和node --print
-
-
15.8 常見操作的輔助包
-
15.8.1 從命令行運(yùn)行包腳本
-
15.8.2 并行或順序運(yùn)行多個(gè)腳本
-
15.8.3 文件系統(tǒng)操作
-
15.8.4 將文件或目錄放入垃圾箱
-
15.8.5?復(fù)制文件樹
-
15.8.6?監(jiān)視文件
-
15.8.7?其他功能
-
15.8.8?HTTP 服務(wù)器
-
-
15.9?擴(kuò)展包腳本的功能
-
15.9.1?
per-env
: 根據(jù)$NODE_ENV
在腳本之間切換 -
15.9.2?定義特定操作系統(tǒng)的腳本
-
-
15.10?本章的來源
package.json
有一個(gè)屬性"scripts"
,讓我們定義包腳本,執(zhí)行與包相關(guān)的任務(wù),如編譯構(gòu)件或運(yùn)行測(cè)試的小型 shell 腳本。本章解釋了它們以及我們?nèi)绾尉帉懰鼈儯顾鼈冊(cè)?Windows 和 Unix(macOS,Linux 等)上都能工作。
15.1?npm 包腳本
npm 包腳本通過package.json
的屬性"scripts"
來定義:
{
···
"scripts": {
"tsc": "tsc",
"tscwatch": "tsc --watch",
"tscclean": "shx rm -rf ./dist/*"
},
···
}
"scripts"
的值是一個(gè)對(duì)象,其中每個(gè)屬性定義了一個(gè)包腳本:
-
屬性鍵定義了腳本的名稱。
-
屬性值定義了腳本運(yùn)行時(shí)要執(zhí)行的操作。
如果我們輸入:
npm run <script-name>
然后 npm 在 shell 中執(zhí)行名稱為script-name
的腳本。例如,我們可以使用:
npm run tscwatch
在 shell 中運(yùn)行以下命令:
tsc --watch
在本章中,我們偶爾會(huì)使用npm
選項(xiàng)-s
,它是--silent
的縮寫,并告訴npm run
產(chǎn)生更少的輸出:
npm -s run <script-name>
此選項(xiàng)在日志部分中有更詳細(xì)的介紹。
15.1.1?運(yùn)行包腳本的更短的 npm 命令
一些包腳本可以通過更短的 npm 命令運(yùn)行:
命令 | 等效 |
---|---|
npm test , npm t
|
npm run test |
npm start |
npm run start |
npm stop |
npm run stop |
npm restart |
npm run restart |
-
npm start
: 如果沒有包腳本"start"
,npm 會(huì)運(yùn)行node server.js
。 -
npm restart
: 如果沒有包腳本"restart"
,npm 會(huì)運(yùn)行"prerestart"
,"stop"
,"start"
,"postrestart"
。- 更多關(guān)于
npm restart
的信息。
- 更多關(guān)于
15.1.2?用于運(yùn)行包腳本的 shell 是哪個(gè)?
默認(rèn)情況下,npm 通過cmd.exe
在 Windows 上運(yùn)行包腳本,在 Unix 上通過/bin/sh
運(yùn)行。我們可以通過npm 配置設(shè)置script-shell
來更改。
然而,這樣做很少是一個(gè)好主意:許多現(xiàn)有的跨平臺(tái)腳本都是為sh
和cmd.exe
編寫的,將停止工作。
15.1.3?防止包腳本自動(dòng)運(yùn)行
一些腳本名稱保留用于生命周期腳本,當(dāng)我們執(zhí)行特定的 npm 命令時(shí),npm 會(huì)運(yùn)行它們。
例如,當(dāng)我們執(zhí)行npm install
(不帶參數(shù))時(shí),npm 會(huì)運(yùn)行腳本"postinstall"
。生命周期腳本將在后面更詳細(xì)地介紹。
如果配置設(shè)置ignore-scripts
為true
,npm 永遠(yuǎn)不會(huì)自動(dòng)運(yùn)行腳本,只有在我們直接調(diào)用它們時(shí)才會(huì)運(yùn)行。
15.1.4?在 Unix 上為包腳本獲取 tab 補(bǔ)全
在 Unix 上,npm 支持通過npm completion
為命令和包腳本名稱進(jìn)行 tab 補(bǔ)全。我們可以通過將以下行添加到我們的.profile
/ .zprofile
/ .bash_profile
/等來安裝它。
. <(npm completion)
如果您需要非 Unix 平臺(tái)的 tab 補(bǔ)全,請(qǐng)搜索“npm tab completion PowerShell”等。
15.1.5?列出和組織包腳本
沒有名稱的npm run
會(huì)列出可用的腳本。如果存在以下腳本:
"scripts": {
"tsc": "tsc",
"tscwatch": "tsc --watch",
"serve": "serve ./site/"
}
然后它們會(huì)像這樣列出:
% npm run
Scripts available via `npm run-script`:
tsc
tsc
tscwatch
tsc --watch
serve
serve ./site/
15.1.5.1?添加分隔符
如果有很多包腳本,我們可以濫用腳本名稱作為分隔符(腳本 "help"
將在下一小節(jié)中解釋):
"scripts": {
"help": "scripts-help -w 40",
"\n========== Building ==========": "",
"tsc": "tsc",
"tscwatch": "tsc --watch",
"\n========== Serving ==========": "",
"serve": "serve ./site/"
},
現(xiàn)在腳本列出如下:
% npm run
Scripts available via `npm run-script`:
help
scripts-help -w 40
========== Building ==========
tsc
tsc
tscwatch
tsc --watch
========== Serving ==========
serve
serve ./site/
請(qǐng)注意,在 Unix 和 Windows 上都可以使用換行符(\n
)的技巧。
15.1.5.2?打印幫助信息
包腳本 "help"
通過 package @rauschma/scripts-help
的 bin 腳本 scripts-help
打印幫助信息。我們通過 package.json
屬性 "scripts-help"
提供描述("tscwatch"
的值被縮寫以適應(yīng)單行):
"scripts-help": {
"tsc": "Compile the TypeScript to JavaScript.",
"tscwatch": "Watch the TypeScript source code [...]",
"serve": "Serve the generated website via a local server."
}
幫助信息如下所示:
% npm -s run help
Package “demo”
╔══════╤══════════════════════════╗
║ help │ scripts-help -w 40 ║
╚══════╧══════════════════════════╝
Building
╔══════════╤══════════════════════════════════════════╗
║ tsc │ Compile the TypeScript to JavaScript. ║
╟──────────┼──────────────────────────────────────────╢
║ tscwatch │ Watch the TypeScript source code and ║
║ │ compile it incrementally when and if ║
║ │ there are changes. ║
╚══════════╧══════════════════════════════════════════╝
Serving
╔═══════╤══════════════════════════════════════════╗
║ serve │ Serve the generated website via a local ║
║ │ server. ║
╚═══════╧══════════════════════════════════════════╝
15.2?包腳本的種類
如果某些名稱用于腳本,則在某些情況下會(huì)自動(dòng)運(yùn)行:
-
Pre 腳本 和 post 腳本 在腳本之前和之后運(yùn)行。
-
生命周期腳本 在用戶執(zhí)行諸如
npm install
之類的操作時(shí)運(yùn)行。
所有其他腳本都被稱為 直接運(yùn)行腳本。
15.2.1?Pre 和 post 腳本
每當(dāng) npm 運(yùn)行包腳本 PS
時(shí),它會(huì)自動(dòng)運(yùn)行以下腳本 - 如果它們存在的話:
-
prePS
之前(pre 腳本) -
postPS
之后(post 腳本)
以下腳本包含預(yù)先腳本 prehello
和后置腳本 posthello
:
"scripts": {
"hello": "echo hello",
"prehello": "echo BEFORE",
"posthello": "echo AFTER"
},
這是我們運(yùn)行 hello
時(shí)會(huì)發(fā)生的事情:
% npm -s run hello
BEFORE
hello
AFTER
15.2.2?生命周期腳本
npm 在執(zhí)行 npm publish
等命令期間運(yùn)行 生命周期腳本:
-
npm publish
(上傳包到 npm 注冊(cè)表) -
npm pack
(為注冊(cè)表包、包目錄等創(chuàng)建歸檔) -
npm install
(無參數(shù)使用,用于安裝從 npm 注冊(cè)表以外的來源下載的包的依賴項(xiàng))
如果任何生命周期腳本失敗,整個(gè)命令將立即停止并顯示錯(cuò)誤。
生命周期腳本有哪些用例?
-
編譯 TypeScript:如果一個(gè)包包含 TypeScript 代碼,我們通常會(huì)在使用之前將其編譯為 JavaScript 代碼。雖然后者的代碼通常不會(huì)被檢入版本控制,但它必須上傳到 npm 注冊(cè)表,以便從 JavaScript 中使用該包。生命周期腳本讓我們?cè)?
npm publish
上傳包之前編譯 TypeScript 代碼。這確保了在 npm 注冊(cè)表中,JavaScript 代碼始終與我們的 TypeScript 代碼同步。它還確保我們的 TypeScript 代碼沒有靜態(tài)類型錯(cuò)誤,因?yàn)楫?dāng)遇到這些錯(cuò)誤時(shí),編譯(因此發(fā)布)會(huì)停止。 -
運(yùn)行測(cè)試:我們還可以使用生命周期腳本在發(fā)布包之前運(yùn)行測(cè)試。如果測(cè)試失敗,包將不會(huì)被發(fā)布。
這些是最重要的生命周期腳本(有關(guān)所有生命周期腳本的詳細(xì)信息,請(qǐng)參閱 npm 文檔):
-
"prepare"
:-
在創(chuàng)建包歸檔(
.tgz
文件)之前運(yùn)行:-
在
npm publish
期間 -
在
npm pack
期間
-
-
在從 git 或本地路徑安裝包時(shí)運(yùn)行。
-
在沒有參數(shù)使用
npm install
或者全局安裝包時(shí)運(yùn)行。
-
-
"prepack"
在創(chuàng)建包歸檔(.tgz
文件)之前運(yùn)行:-
在
npm publish
期間 -
在
npm pack
期間
-
-
"prepublishOnly"
僅在npm publish
期間運(yùn)行。 -
"install"
在沒有參數(shù)使用npm install
或者全局安裝包時(shí)運(yùn)行。- 請(qǐng)注意,我們還可以創(chuàng)建一個(gè)預(yù)先腳本
"preinstall"
和/或一個(gè)后置腳本"postinstall"
。它們的名稱使得在 npm 運(yùn)行它們時(shí)更清晰。
- 請(qǐng)注意,我們還可以創(chuàng)建一個(gè)預(yù)先腳本
以下表格總結(jié)了這些生命周期腳本何時(shí)運(yùn)行:
prepublishOnly |
prepack |
prepare |
install |
|
---|---|---|---|---|
npm publish |
? |
? |
? |
|
npm pack |
? |
? |
||
npm install |
? |
? |
||
全局安裝 | ? |
? |
||
通過 git、路徑安裝 | ? |
注意: 自動(dòng)執(zhí)行事務(wù)總是有點(diǎn)棘手。我通常遵循以下規(guī)則:
-
我為自己自動(dòng)化(例如通過
prepublishOnly
)。 -
我不為其他人自動(dòng)化(例如通過
postinstall
)。
15.3 包腳本運(yùn)行的 shell 環(huán)境
在本節(jié)中,我們偶爾會(huì)使用
node -p <expr>
這個(gè)命令調(diào)用expr
中的 JavaScript 代碼,并將結(jié)果打印到終端 - 例如:
% node -p "'hello everyone!'.toUpperCase()"
HELLO EVERYONE!
15.3.1 當(dāng)前目錄
當(dāng)包腳本運(yùn)行時(shí),當(dāng)前目錄始終是包目錄,與我們?cè)谄涓夸浀哪夸洏渲械奈恢脽o關(guān)。我們可以通過將以下腳本添加到package.json
來確認(rèn):
"cwd": "node -p \"process.cwd()\""
讓我們?cè)?Unix 上嘗試cwd
:
% cd /Users/robin/new-package/src/util
% npm -s run cwd
/Users/robin/new-package
以這種方式改變當(dāng)前目錄有助于編寫包腳本,因?yàn)槲覀兛梢允褂孟鄬?duì)于包目錄的路徑。
15.3.2 shell PATH
當(dāng)模塊M
從以包P
的名稱開頭的模塊導(dǎo)入時(shí),Node.js 會(huì)遍歷node_modules
目錄,直到找到P
的目錄:
-
M
的父目錄中的第一個(gè)node_modules
(如果存在) -
M
的父目錄的父目錄中的第二個(gè)node_modules
(如果存在) -
依此類推,直到達(dá)到文件系統(tǒng)的根目錄。
也就是說,M
繼承了其祖先目錄的node_modules
目錄。
類似的繼承方式也發(fā)生在 bin 腳本中,當(dāng)我們安裝一個(gè)包時(shí),它們存儲(chǔ)在node_modules/.bin
中。npm run
會(huì)臨時(shí)將條目添加到 shell PATH 變量(Unix 上為$PATH
,Windows 上為%Path%
):
-
包目錄中的
node_modules/.bin
-
包目錄的父目錄中的
node_modules/.bin
-
等等。
要查看這些添加,我們可以使用以下包腳本:
"bin-dirs": "node -p \"JS\""
JS
代表一行 JavaScript 代碼:
(process.env.PATH ?? process.env.Path)
.split(path.delimiter)
.filter(p => p.includes('.bin'))
在 Unix 上,如果我們運(yùn)行bin-dirs
,我們會(huì)得到以下輸出:
% npm -s run bin-dirs
[
'/Users/robin/new-package/node_modules/.bin',
'/Users/robin/node_modules/.bin',
'/Users/node_modules/.bin',
'/node_modules/.bin'
]
在 Windows 上,我們得到:
>npm -s run bin-dirs
[
'C:\\Users\\charlie\\new-package\\node_modules\\.bin',
'C:\\Users\\charlie\\node_modules\\.bin',
'C:\\Users\\node_modules\\.bin',
'C:\\node_modules\\.bin'
]
15.4 在包腳本中使用環(huán)境變量
在諸如 Make、Grunt 和 Gulp 之類的任務(wù)運(yùn)行器中,變量很重要,因?yàn)樗鼈冇兄跍p少冗余。遺憾的是,雖然包腳本沒有自己的變量,但我們可以通過使用環(huán)境變量(也稱為shell 變量)來解決這個(gè)缺陷。
我們可以使用以下命令列出特定于平臺(tái)的環(huán)境變量:
-
Unix:
env
-
Windows 命令 shell:
SET
-
兩個(gè)平臺(tái):
node -p process.env
在 macOS 上,結(jié)果看起來像這樣:
TERM_PROGRAM=Apple_Terminal
SHELL=/bin/zsh
TMPDIR=/var/folders/ph/sz0384m11vxf5byk12fzjms40000gn/T/
USER=robin
PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
PWD=/Users/robin/new-package
HOME=/Users/robin
LOGNAME=robin
···
在 Windows 命令 shell 中,結(jié)果看起來像這樣:
Path=C:\Windows;C:\Users\charlie\AppData\Roaming\npm;···
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
PROMPT=$P$G
TEMP=C:\Users\charlie\AppData\Local\Temp
TMP=C:\Users\charlie\AppData\Local\Temp
USERNAME=charlie
USERPROFILE=C:\Users\charlie
···
此外,npm 在運(yùn)行包腳本之前會(huì)臨時(shí)添加更多的環(huán)境變量。為了查看最終結(jié)果是什么樣子,我們可以使用以下命令:
npm run env
這個(gè)命令調(diào)用了一個(gè)內(nèi)置的包腳本。讓我們嘗試一下這個(gè)package.json
:
{
"name": "@my-scope/new-package",
"version": "1.0.0",
"bin": {
"hello": "./hello.mjs"
},
"config": {
"stringProp": "yes",
"arrayProp": ["a", "b", "c"],
"objectProp": {
"one": 1,
"two": 2
}
}
}
所有 npm 的臨時(shí)變量的名稱都以npm_
開頭。讓我們按字母順序打印這些變量:
npm run env | grep npm_ | sort
npm_
變量具有分層結(jié)構(gòu)。在npm_lifecycle_
下,我們找到了當(dāng)前運(yùn)行的包腳本的名稱和定義:
npm_lifecycle_event: 'env',
npm_lifecycle_script: 'env',
在 Windows 上,npm_lifecycle_script
在這種情況下會(huì)SET
。
在前綴npm_config_
下,我們可以看到一些 npm 的配置設(shè)置(在 npm 文檔中有描述)。以下是一些例子:
npm_config_cache: '/Users/robin/.npm',
npm_config_global_prefix: '/usr/local',
npm_config_globalconfig: '/usr/local/etc/npmrc',
npm_config_local_prefix: '/Users/robin/new-package',
npm_config_prefix: '/usr/local'
npm_config_user_agent: 'npm/8.15.0 node/v18.7.0 darwin arm64 workspaces/false',
npm_config_userconfig: '/Users/robin/.npmrc',
前綴npm_package_
讓我們可以訪問package.json
的內(nèi)容。其頂層看起來像這樣:
npm_package_json: '/Users/robin/new-package/package.json',
npm_package_name: '@my-scope/new-package',
npm_package_version: '1.0.0',
在npm_package_bin_
下,我們可以找到package.json
屬性"bin"
的屬性:
npm_package_bin_hello: 'hello.mjs',
npm_package_config_
條目讓我們可以訪問"config"
的屬性:
npm_package_config_arrayProp: 'a\n\nb\n\nc',
npm_package_config_objectProp_one: '1',
npm_package_config_objectProp_two: '2',
npm_package_config_stringProp: 'yes',
這意味著"config"
讓我們?cè)O(shè)置可以在包腳本中使用的變量。下一小節(jié)將進(jìn)一步探討這一點(diǎn)。
請(qǐng)注意,對(duì)象被轉(zhuǎn)換為“嵌套”條目(第 2 行和第 3 行),而數(shù)組(第 1 行)和數(shù)字(第 2 行和第 3 行)被轉(zhuǎn)換為字符串。
這些是剩下的npm_
環(huán)境變量:
npm_command: 'run-script',
npm_execpath: '/usr/local/lib/node_modules/npm/bin/npm-cli.js',
npm_node_execpath: '/usr/local/bin/node',
15.4.1 獲取和設(shè)置環(huán)境變量
以下的package.json
演示了我們?nèi)绾卧诎_本中訪問通過"config"
定義的變量:
{
"scripts": {
"hi:unix": "echo $?npm_package_config_hi",
"hi:windows": "echo %?npm_package_config_hi%"
},
"config": {
"hi": "HELLO"
}
}
遺憾的是,沒有內(nèi)置的跨平臺(tái)方式可以從包腳本中訪問環(huán)境變量。
然而,有一些帶有 bin 腳本的包可以幫助我們。
Package env-var
讓我們獲取環(huán)境變量:
"scripts": {
"hi": "env-var echo {{npm_package_config_hi}}"
}
Package cross-env
讓我們?cè)O(shè)置環(huán)境變量:
"scripts": {
"build": "cross-env FIRST=one SECOND=two node ./build.mjs"
}
15.4.2?通過.env
文件設(shè)置環(huán)境變量
還有一些包可以讓我們通過.env
文件設(shè)置環(huán)境變量。這些文件具有以下格式:
# Comment
SECRET_HOST="https://example.com"
SECRET_KEY="123456789" # another comment
使用與package.json
分開的文件使我們能夠?qū)?shù)據(jù)排除在版本控制之外。
這些是支持.env
文件的包:
-
Package
dotenv
支持 JavaScript 模塊的.env
文件。我們可以預(yù)加載它:node -r dotenv/config app.mjs
我們可以導(dǎo)入它:
import dotenv from 'dotenv'; dotenv.config(); console.log(process.env);
-
Package
node-env-run
讓我們通過 shell 命令使用.env
文件:# Loads `.env` and runs an arbitrary shell script. # If there are CLI options, we need to use `--`. nodenv --exec node -- -p process.env.SECRET # Loads `.env` and uses `node` to run `script.mjs`. nodenv script.mjs
-
Package
env-cmd
是前一個(gè)包的替代品:# Loads `.env` and runs an arbitrary shell script env-cmd node -p process.env.SECRET
該包還具有更多功能:在變量集之間切換,更多文件格式等。
15.5?包腳本的參數(shù)
讓我們探討如何將參數(shù)傳遞給我們通過包腳本調(diào)用的 shell 命令。我們將使用以下package.json
:
{
···
"scripts": {
"args": "log-args"
},
"dependencies": {
"log-args": "1.0.0"
}
}
bin 腳本log-args
如下所示:
for (const [key,value] of Object.entries(process.env)) {
if (key.startsWith('npm_config_arg')) {
console.log(`${key}=${JSON.stringify(value)}`);
}
}
console.log(process.argv.slice(2));
位置參數(shù)按預(yù)期工作:
% npm -s run args three positional arguments
[ 'three', 'positional', 'arguments' ]
npm run
使用選項(xiàng)并為它們創(chuàng)建環(huán)境變量。它們不會(huì)添加到process.argv
中:
% npm -s run args --arg1='first arg' --arg2='second arg'
npm_config_arg2="second arg"
npm_config_arg1="first arg"
[]
如果我們希望選項(xiàng)出現(xiàn)在process.argv
中,我們必須使用選項(xiàng)終結(jié)符--
。該終結(jié)符通常在包腳本名稱之后插入:
% npm -s run args -- --arg1='first arg' --arg2='second arg'
[ '--arg1=first arg', '--arg2=second arg' ]
但我們也可以在該名稱之前插入它:
% npm -s run -- args --arg1='first arg' --arg2='second arg'
[ '--arg1=first arg', '--arg2=second arg' ]
15.6?npm 日志級(jí)別(產(chǎn)生多少輸出)
npm 支持以下日志級(jí)別:
日志級(jí)別 |
npm 選項(xiàng) |
別名 |
---|---|---|
靜默 | --loglevel silent |
-s --silent |
錯(cuò)誤 | --loglevel error |
|
警告 | --loglevel warn |
-q --quiet |
注意 | --loglevel notice |
|
http | --loglevel http |
|
時(shí)間 | --loglevel timing |
|
信息 | --loglevel info |
-d |
詳細(xì) | --loglevel verbose |
-dd --verbose |
荒謬 | --loglevel silly |
-ddd |
日志記錄指的是兩種活動(dòng):
-
將信息打印到終端
-
將信息寫入 npm 日志
以下各小節(jié)描述:
-
日志級(jí)別如何影響這些活動(dòng)。原則上,
silent
記錄最少,而silly
記錄最多。 -
如何配置日志記錄。前表顯示了如何通過命令行選項(xiàng)臨時(shí)更改日志級(jí)別,但還有更多設(shè)置。我們可以將它們臨時(shí)或永久更改。
15.6.1?日志級(jí)別和打印到終端的信息
默認(rèn)情況下,包腳本在終端輸出方面相對(duì)冗長(zhǎng)。例如,以下package.json
文件:
{
"name": "@my-scope/new-package",
"version": "1.0.0",
"scripts": {
"hello": "echo Hello",
"err": "more does-not-exist.txt"
},
···
}
如果日志級(jí)別高于silent
且包腳本在沒有錯(cuò)誤的情況下退出,則會(huì)發(fā)生以下情況:
% npm run hello
> @my-scope/new-package@1.0.0 hello
> echo Hello
Hello
如果日志級(jí)別高于silent
且包腳本失敗,則會(huì)發(fā)生以下情況:
% npm run err
> @my-scope/new-package@1.0.0 err
> more does-not-exist.txt
does-not-exist.txt: No such file or directory
使用日志級(jí)別silent
,輸出變得不那么混亂:
% npm -s run hello
Hello
% npm -s run err
does-not-exist.txt: No such file or directory
一些錯(cuò)誤被-s
吞沒:
% npm -s run abc
%
我們至少需要日志級(jí)別error
才能看到它們:
% npm --loglevel error run abc
npm ERR! Missing script: "abc"
npm ERR!
npm ERR! To see a list of scripts, run:
npm ERR! npm run
npm ERR! A complete log of this run can be found in:
npm ERR! /Users/robin/.npm/_logs/2072-08-30T14_59_40_474Z-debug-0.log
不幸的是,日志級(jí)別silent
也會(huì)抑制npm run
的輸出(無參數(shù)):
% npm -s run
%
15.6.2?日志級(jí)別和寫入 npm 日志的信息
默認(rèn)情況下,日志將被寫入 npm 緩存目錄,我們可以通過npm config
獲取其路徑:
% npm config get cache
/Users/robin/.npm
日志目錄的內(nèi)容如下:
% ls -1 /Users/robin/.npm/_logs
2072-08-28T11_44_38_499Z-debug-0.log
2072-08-28T11_45_45_703Z-debug-0.log
2072-08-28T11_52_04_345Z-debug-0.log
日志中的每一行都以行索引和日志級(jí)別開頭。這是一個(gè)使用日志級(jí)別notice
寫入的日志的示例。有趣的是,即使是比notice
更詳細(xì)的日志級(jí)別(如silly
)也會(huì)顯示出來:
0 verbose cli /usr/local/bin/node /usr/local/bin/npm
1 info using npm@8.15.0
···
33 silly logfile done cleaning log files
34 timing command:run Completed in 9ms
···
如果npm run
返回錯(cuò)誤,相應(yīng)的日志以這種方式結(jié)束:
34 timing command:run Completed in 7ms
35 verbose exit 1
36 timing npm Completed in 28ms
37 verbose code 1
如果沒有錯(cuò)誤,相應(yīng)的日志記錄以這種方式結(jié)束:
34 timing command:run Completed in 7ms
35 verbose exit 0
36 timing npm Completed in 26ms
37 info ok
15.6.3?配置日志記錄
npm config list --long
打印各種設(shè)置的默認(rèn)值。這些是與日志記錄相關(guān)的設(shè)置的默認(rèn)值:
% npm config list --long | grep log
loglevel = "notice"
logs-dir = null
logs-max = 10
如果logs-dir
的值為null
,npm 將使用 npm 緩存目錄內(nèi)的目錄_logs
(如前所述)。
-
logs-dir
允許我們覆蓋默認(rèn)設(shè)置,使 npm 將其日志寫入我們選擇的目錄。 -
logs-max
允許我們配置 npm 在刪除舊文件之前寫入日志目錄的文件數(shù)。如果將logs-max
設(shè)置為 0,則不會(huì)寫入任何日志。 -
loglevel
允許我們配置 npm 的日志級(jí)別。
要永久更改這些設(shè)置,我們還可以使用npm config
- 例如:
-
獲取當(dāng)前的日志級(jí)別:
npm config get loglevel
-
永久設(shè)置當(dāng)前的日志級(jí)別:
npm config set loglevel silent
-
永久重置日志級(jí)別為內(nèi)置默認(rèn)值:
npm config delete loglevel
我們還可以通過命令行選項(xiàng)臨時(shí)更改設(shè)置 - 例如:
npm --loglevel silent run build
其他更改設(shè)置的方式(例如使用環(huán)境變量)由npm 文檔解釋。
15.6.4?在npm install
期間運(yùn)行的生命周期腳本的輸出
在npm install
期間運(yùn)行的生命周期腳本的輸出(無參數(shù))是隱藏的。我們可以通過(臨時(shí)或永久)將foreground-scripts
設(shè)置為true
來更改這一點(diǎn)。
15.6.5?npm 日志工作的觀察
-
只有日志級(jí)別為
silent
時(shí),使用npm run
時(shí)才會(huì)關(guān)閉額外的輸出。 -
日志級(jí)別對(duì)于是否創(chuàng)建日志文件以及寫入到日志文件中的內(nèi)容沒有影響。
-
錯(cuò)誤消息不會(huì)被寫入日志。
15.7?跨平臺(tái) shell 腳本
用于包腳本的兩個(gè)最常用的 shell 是:
-
Unix 上的
sh
-
Windows 上的
cmd.exe
在本節(jié)中,我們研究了在兩個(gè) shell 中都有效的構(gòu)造。
15.7.1?路徑和引用
提示:
-
使用由斜杠分隔的相對(duì)路徑段:Windows 接受斜杠作為分隔符,即使在該平臺(tái)上通常使用反斜杠。
-
雙引號(hào)參數(shù):雖然
sh
支持單引號(hào),但 Windows 命令 shell 不支持。不幸的是,當(dāng)我們?cè)诎_本定義中使用雙引號(hào)時(shí),我們必須對(duì)其進(jìn)行轉(zhuǎn)義:"dir": "mkdir \"\my dir""
15.7.2?鏈接命令
有兩種方式可以鏈接在兩個(gè)平臺(tái)上都有效的命令:
-
&&
之后的命令僅在前一個(gè)命令成功時(shí)執(zhí)行(退出代碼為 0)。 -
||
之后的命令僅在前一個(gè)命令失敗時(shí)執(zhí)行(退出代碼不為 0)。
忽略退出代碼的鏈接在不同平臺(tái)上有所不同:
-
Unix:
;
-
Windows 命令 shell:
&
以下交互演示了在 Unix 上&&
和||
的工作方式(在 Windows 上,我們會(huì)使用dir
而不是ls
):
% ls unknown && echo "SUCCESS" || echo "FAILURE"
ls: unknown: No such file or directory
FAILURE
% ls package.json && echo "SUCCESS" || echo "FAILURE"
package.json
SUCCESS
15.7.3?包腳本的退出代碼
退出代碼可以通過 shell 變量訪問:
-
Unix:
$?
-
Windows 命令 shell:
%errorlevel%
npm run
返回與上次執(zhí)行的 shell 腳本相同的退出代碼:
{
···
"scripts": {
"hello": "echo Hello",
"err": "more does-not-exist.txt"
}
}
以下交互發(fā)生在 Unix 上:
% npm -s run hello ; echo $?
Hello
0
% npm -s run err ; echo $?
does-not-exist.txt: No such file or directory
1
15.7.4?管道和重定向輸入和輸出
-
在命令之間進(jìn)行管道傳輸:
|
-
將輸出寫入文件:
cmd > stdout-saved-to-file.txt
-
從文件中讀取輸入:
cmd < stdin-from-file.txt
15.7.5?在兩個(gè)平臺(tái)上都有效的命令
以下命令在兩個(gè)平臺(tái)上都存在(但在選項(xiàng)方面有所不同):
-
cd
-
echo
。在 Windows 上要注意:雙引號(hào)會(huì)被打印出來,而不是被忽略。 -
exit
-
mkdir
-
more
-
rmdir
-
sort
15.7.6?運(yùn)行 bin 腳本和包內(nèi)部模塊
以下的package.json
演示了在依賴項(xiàng)中調(diào)用 bin 腳本的三種方式:
{
"scripts": {
"hi1": "./node_modules/.bin/cowsay Hello",
"hi2": "cowsay Hello",
"hi3": "npx cowsay Hello"
},
"dependencies": {
"cowsay": "1.5.0"
}
}
解釋:
-
hi1
:依賴項(xiàng)中的 bin 腳本安裝在目錄node_modules/.bin
中。 -
hi2
:正如我們所見,npm 在執(zhí)行包腳本時(shí)會(huì)將node_modules/.bin
添加到 shell PATH 中。這意味著我們可以像全局安裝一樣使用本地 bin 腳本。 -
hi3
:當(dāng)npx
運(yùn)行腳本時(shí),它還會(huì)將node_modules/.bin
添加到 shell PATH 中。
在 Unix 上,我們可以直接調(diào)用包本地腳本 - 如果它們有 hashbangs 并且是可執(zhí)行的。然而,在 Windows 上這種方法行不通,這就是為什么最好通過node
來調(diào)用它們:
"build": "node ./build.mjs"
15.7.7?node --eval
和node --print
當(dāng)一個(gè)包腳本的功能變得太復(fù)雜時(shí),通常最好通過 Node.js 模塊來實(shí)現(xiàn)它 - 這樣可以輕松編寫跨平臺(tái)代碼。
但是,我們也可以使用node
命令來運(yùn)行小的 JavaScript 片段,這對(duì)于以跨平臺(tái)的方式執(zhí)行小任務(wù)非常有用。相關(guān)的選項(xiàng)是:
-
node --eval <expr>
評(píng)估 JavaScript 表達(dá)式expr
。- 縮寫:
node -e
- 縮寫:
-
node --print <expr>
評(píng)估 JavaScript 表達(dá)式expr
并將結(jié)果打印到終端。- 縮寫:
node -p
- 縮寫:
以下命令在 Unix 和 Windows 上都適用(只有注釋是 Unix 特定的):
# Print a string to the terminal (cross-platform echo)
node -p "'How are you?'"
# Print the value of an environment variable
# (Alas, we can’t change variables via `process.env`)
node -p process.env.USER # only Unix
node -p process.env.USERNAME # only Windows
node -p "process.env.USER ?? process.env.USERNAME"
# Print all environment variables
node -p process.env
# Print the current working directory
node -p "process.cwd()"
# Print the path of the current home directory
node -p "os.homedir()"
# Print the path of the current temporary directory
node -p "os.tmpdir()"
# Print the contents of a text file
node -p "fs.readFileSync('package.json', 'utf-8')"
# Write a string to a file
node -e "fs.writeFileSync('file.txt', 'Text content', 'utf-8')"
如果我們需要特定于平臺(tái)的行終止符,我們可以使用os.EOL
,例如,我們可以在前一個(gè)命令中用'Text content'
替換:
`line 1${os.EOL}line2${os.EOL}`
觀察:
-
如果 JavaScript 代碼包含括號(hào),將其放在雙引號(hào)中很重要,否則 Unix 會(huì)報(bào)錯(cuò)。
-
所有內(nèi)置模塊都可以通過變量訪問。這就是為什么我們不需要導(dǎo)入
os
或fs
。 -
fs
支持更多的文件系統(tǒng)操作。這些在§8“在 Node.js 上使用文件系統(tǒng)”中有文檔記錄。
15.8?常見操作的輔助軟件包
15.8.1?從命令行運(yùn)行軟件包腳本
npm-quick-run提供了一個(gè) bin 腳本nr
,讓我們可以使用縮寫來運(yùn)行軟件包腳本,例如:
-
nr m -w
執(zhí)行"npm run mocha -- -w"
(如果"mocha"
是以“m”開頭的第一個(gè)軟件包腳本)。 -
nr c:o
運(yùn)行軟件包腳本"cypress:open"
。 -
等等。
15.8.2?同時(shí)或順序運(yùn)行多個(gè)腳本
同時(shí)運(yùn)行 shell 腳本:
-
Unix:
&
-
Windows 命令 shell:
start
以下兩個(gè)軟件包為我們提供了跨平臺(tái)的選項(xiàng)和相關(guān)功能:
-
concurrently同時(shí)運(yùn)行多個(gè) shell 命令,例如:
concurrently "npm run clean" "npm run build"
-
npm-run-all提供了幾種功能,例如:
-
調(diào)用軟件包腳本的更方便的方式。以下兩個(gè)命令是等價(jià)的:
npm-run-all clean lint build npm run clean && npm run lint && npm run build
-
同時(shí)運(yùn)行軟件包腳本:
npm-run-all --parallel lint build
-
使用通配符運(yùn)行多個(gè)腳本,例如,
watch:*
代表所有以watch:
開頭的軟件包腳本(watch:html
、watch:js
等):npm-run-all "watch:*" npm-run-all --parallel "watch:*"
-
15.8.3?文件系統(tǒng)操作
Package shx
讓我們可以使用“Unix 語法”來運(yùn)行各種文件系統(tǒng)操作。它在 Unix 和 Windows 上的所有操作都有效。
創(chuàng)建目錄:
"create-asset-dir": "shx mkdir ./assets"
刪除目錄:
"remove-asset-dir": "shx rm -rf ./assets"
清空目錄(雙引號(hào)是為了安全起見,關(guān)于通配符*
):
"tscclean": "shx rm -rf \"./dist/*\""
復(fù)制文件:
"copy-index": "shx cp ./html/index.html ./out/index.html"
刪除文件:
"remove-index": "shx rm ./out/index.html"
shx
基于 JavaScript 庫 ShellJS,其存儲(chǔ)庫列出了所有支持的命令。除了我們已經(jīng)看到的 Unix 命令之外,它還模擬:cat
、chmod
、echo
、find
、grep
、head
、ln
、ls
、mv
、pwd
、sed
、sort
、tail
、touch
、uniq
等。
15.8.4?將文件或目錄放入垃圾箱
Package trash-cli
適用于 macOS(10.12+)、Linux 和 Windows(8+)。它將文件和目錄放入垃圾箱,并支持路徑和 glob 模式。以下是使用它的示例:
trash tmp-file.txt
trash tmp-dir
trash "*.jpg"
15.8.5?復(fù)制文件樹
Package copyfiles
讓我們可以復(fù)制文件樹。
copyfiles
的用例如下:在 TypeScript 中,我們可以導(dǎo)入非代碼資產(chǎn),如 CSS 和圖像。TypeScript 編譯器將代碼編譯到“dist”(輸出)目錄,但忽略非代碼資產(chǎn)。這個(gè)跨平臺(tái)的 shell 命令將它們復(fù)制到 dist 目錄:
copyfiles --up 1 "./ts/**/*.{css,png,svg,gif}" ./dist
TypeScript 編譯:
my-pkg/ts/client/picker.ts -> my-pkg/dist/client/picker.js
copy-assets
復(fù)制:
my-pkg/ts/client/picker.css -> my-pkg/dist/client/picker.css
my-pkg/ts/client/icon.svg -> my-pkg/dist/client/icon.svg
15.8.6?監(jiān)視文件
Package onchange
監(jiān)視文件并在每次更改時(shí)運(yùn)行 shell 命令,例如:
onchange 'app/**/*.js' 'test/**/*.js' -- npm test
一個(gè)常見的替代方案(還有許多其他):
nodemon
15.8.7?其他功能
- cli-error-notifier 如果腳本失?。ň哂蟹橇阃顺龃a),則顯示本機(jī)桌面通知。它支持許多操作系統(tǒng)。
15.8.8?HTTP 服務(wù)器
在開發(fā)過程中,通常需要一個(gè) HTTP 服務(wù)器。以下包(以及許多其他包)可以幫助:
-
http-server
-
live-server
-
serve
15.9?擴(kuò)展包腳本的功能
15.9.1?per-env
: 根據(jù) $NODE_ENV
在不同腳本之間切換
The bin script per-env
允許我們運(yùn)行一個(gè)包腳本 SCRIPT
,并根據(jù)環(huán)境變量 NODE_ENV
的值自動(dòng)在 SCRIPT:development
、SCRIPT:staging
和 SCRIPT:production
之間切換:
{
"scripts": {
// If NODE_ENV is missing, the default is "development"
"build": "per-env",
"build:development": "webpack -d --watch",
"build:staging": "webpack -p",
"build:production": "webpack -p"
},
// Processes spawned by `per-env` inherit environment-specific
// variables, if defined.
"per-env": {
"production": {
"DOCKER_USER": "my",
"DOCKER_REPO": "project"
}
}
}
15.9.2?定義特定操作系統(tǒng)的腳本
The bin script cross-os
根據(jù)當(dāng)前操作系統(tǒng)在不同腳本之間切換。
{
"scripts": {
"user": "cross-os user"
},
"cross-os": {
"user": {
"darwin": "echo $USER",
"win32": "echo %USERNAME%",
"linux": "echo $USER"
}
},
···
}
支持的屬性值有:darwin
、freebsd
、linux
、sunos
、win32
。
15.10?本章的來源
-
npm documentation
-
Node.js documentation
-
“Awesome npm scripts” 由 Ryan Zimmerman 和 Michael Kühnel 編寫
-
“Three Things You Didn’t Know You Could Do with npm Scripts” 由 Dominik Kundel
-
“Helpers and tips for npm run scripts” 由 Michael Kühnel
Comments
第五部分:在腳本中處理常見任務(wù)
原文:
exploringjs.com/nodejs-shell-scripting/pt_scripts.html
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
接下來:16 使用 util.parseArgs()
解析命令行參數(shù)
十六、使用 util.parseArgs()解析命令行參數(shù)
原文:
exploringjs.com/nodejs-shell-scripting/ch_node-util-parseargs.html
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
-
16.1?本章中隱含的導(dǎo)入
-
16.2?處理命令行參數(shù)的步驟
-
16.3?解析命令行參數(shù)
-
16.3.1 基礎(chǔ)知識(shí)
-
16.3.2 多次使用選項(xiàng)
-
16.3.3 更多使用長(zhǎng)選項(xiàng)和短選項(xiàng)的方式
-
16.3.4 引用值
-
16.3.5?選項(xiàng)終結(jié)符
-
16.3.6?嚴(yán)格的
parseArgs()
-
-
16.4?
parseArgs
tokens-
16.4.1?令牌示例
-
16.4.2?使用令牌實(shí)現(xiàn)子命令
-
在這一章中,我們將探討如何使用 Node.js 模塊node:util
中的parseArgs()
函數(shù)來解析命令行參數(shù)。
16.1?本章中隱含的導(dǎo)入
在本章的每個(gè)示例中都隱含了以下兩個(gè)導(dǎo)入:
import * as assert from 'node:assert/strict';
import {parseArgs} from 'node:util';
第一個(gè)導(dǎo)入是用于測(cè)試斷言,用于檢查值。第二個(gè)導(dǎo)入是用于本章主題parseArgs()
函數(shù)。
16.2?處理命令行參數(shù)的步驟
處理命令行參數(shù)涉及以下步驟:
-
用戶輸入一個(gè)文本字符串。
-
shell 將字符串解析為一系列單詞和操作符。
-
如果調(diào)用一個(gè)命令,它會(huì)得到零個(gè)或多個(gè)單詞作為參數(shù)。
-
我們的 Node.js 代碼通過存儲(chǔ)在
process.argv
中的數(shù)組接收這些單詞。process
是 Node.js 上的全局變量。 -
我們使用
parseArgs()
將數(shù)組轉(zhuǎn)換為更方便處理的形式。
讓我們使用以下的 shell 腳本args.mjs
和 Node.js 代碼來看看process.argv
是什么樣子的:
#!/usr/bin/env node
console.log(process.argv);
我們從一個(gè)簡(jiǎn)單的命令開始:
% ./args.mjs one two
[ '/usr/bin/node', '/home/john/args.mjs', 'one', 'two' ]
如果我們?cè)?Windows 上通過 npm 安裝命令,同樣的命令在 Windows 命令 shell 上會(huì)產(chǎn)生以下結(jié)果:
[
'C:\\Program Files\\nodejs\\node.exe',
'C:\\Users\\jane\\args.mjs',
'one',
'two'
]
無論我們?nèi)绾握{(diào)用 shell 腳本,process.argv
始終以用于運(yùn)行我們的代碼的 Node.js 二進(jìn)制文件的路徑開始。接下來是我們腳本的路徑。數(shù)組以傳遞給腳本的實(shí)際參數(shù)結(jié)束。換句話說:腳本的參數(shù)始終從索引 2 開始。
因此,我們改變我們的腳本,使其看起來像這樣:
#!/usr/bin/env node
console.log(process.argv.slice(2));
讓我們嘗試更復(fù)雜的參數(shù):
% ./args.mjs --str abc --bool home.html main.js
[ '--str', 'abc', '--bool', 'home.html', 'main.js' ]
這些參數(shù)包括:
-
選項(xiàng)
--str
,其值是文本abc
。這樣的選項(xiàng)稱為字符串選項(xiàng)。 -
選項(xiàng)
--bool
,它沒有關(guān)聯(lián)的值 - 它是一個(gè)標(biāo)志,要么存在要么不存在。這樣的選項(xiàng)稱為布爾選項(xiàng)。 -
兩個(gè)所謂的位置參數(shù),沒有名稱:
home.html
和main.js
。
常見的使用參數(shù)的兩種方式:
-
主要參數(shù)是位置參數(shù),選項(xiàng)提供額外的 - 通常是可選的 - 信息。
-
只使用選項(xiàng)。
作為 JavaScript 函數(shù)調(diào)用,前面的示例看起來像這樣(在 JavaScript 中,選項(xiàng)通常放在最后):
argsMjs('home.html', 'main.js', {str: 'abc', bool: false});
16.3?解析命令行參數(shù)
16.3.1?基礎(chǔ)知識(shí)
如果我們希望parseArgs()
解析帶有參數(shù)的數(shù)組,我們首先需要告訴它我們的選項(xiàng)是如何工作的。假設(shè)我們的腳本有:
-
一個(gè)布爾選項(xiàng)
--verbose
-
一個(gè)接收非負(fù)整數(shù)的選項(xiàng)
--times
。parseArgs()
對(duì)數(shù)字沒有特殊支持,所以我們必須將其作為字符串選項(xiàng)。 -
一個(gè)字符串選項(xiàng)
--color
我們將這些選項(xiàng)描述為 parseArgs()
如下:
const options = {
'verbose': {
type: 'boolean',
short: 'v',
},
'color': {
type: 'string',
short: 'c',
},
'times': {
type: 'string',
short: 't',
},
};
只要 options
的屬性鍵是有效的 JavaScript 標(biāo)識(shí)符,你可以選擇是否引用它。兩者都有利弊。在本章中,它們總是被引用。這樣,具有非標(biāo)識(shí)符名稱的選項(xiàng)(如 my-new-option
)看起來與具有標(biāo)識(shí)符名稱的選項(xiàng)相同。
options
中的每個(gè)條目都可以具有以下屬性(通過 TypeScript 類型定義):
type Options = {
type: 'boolean' | 'string', // required
short?: string, // optional
multiple?: boolean, // optional, default `false`
};
-
.type
指定選項(xiàng)是布爾值還是字符串。 -
.short
定義選項(xiàng)的短版本。必須是單個(gè)字符。我們很快將看到如何使用短版本。 -
.multiple
指示選項(xiàng)是否最多可以使用一次或零次或多次。稍后我們將看到這意味著什么。
以下代碼使用 parseArgs()
和 options
解析帶有參數(shù)的數(shù)組:
assert.deepEqual(
parseArgs({options, args: [
'--verbose', '--color', 'green', '--times', '5'
]}),
{
values: {__proto__:null,
verbose: true,
color: 'green',
times: '5'
},
positionals: []
}
);
存儲(chǔ)在 .values
中的對(duì)象的原型是 null
。這意味著我們可以使用 in
運(yùn)算符來檢查屬性是否存在,而不必?fù)?dān)心繼承的屬性,如 .toString
。
如前所述,--times
的值為 5,被處理為字符串。
我們傳遞給 parseArgs()
的對(duì)象具有以下 TypeScript 類型:
type ParseArgsProps = {
options?: {[key: string], Options}, // optional, default: {}
args?: Array<string>, // optional
// default: process.argv.slice(2)
strict?: boolean, // optional, default `true`
allowPositionals?: boolean, // optional, default `false`
};
-
.args
:要解析的參數(shù)。如果省略此屬性,parseArgs()
將使用process.argv
,從索引 2 開始。 -
.strict
:如果為true
,則如果args
不正確,將拋出異常。稍后詳細(xì)介紹。 -
.allowPositionals
:args
是否包含位置參數(shù)?
這是 parseArgs()
的結(jié)果類型:
type ParseArgsResult = {
values: {[key: string]: ValuesValue}, // an object
positionals: Array<string>, // always an Array
};
type ValuesValue = boolean | string | Array<boolean|string>;
-
.values
包含可選參數(shù)。我們已經(jīng)看到字符串和布爾值作為屬性值。當(dāng)我們探索具有.multiple
為true
的選項(xiàng)定義時(shí),我們將看到數(shù)組值屬性。 -
.positionals
包含位置參數(shù)。
兩個(gè)連字符用于引用選項(xiàng)的長(zhǎng)版本。一個(gè)連字符用于引用短版本:
assert.deepEqual(
parseArgs({options, args: ['-v', '-c', 'green']}),
{
values: {__proto__:null,
verbose: true,
color: 'green',
},
positionals: []
}
);
請(qǐng)注意,.values
包含選項(xiàng)的長(zhǎng)名稱。
我們通過解析混合了可選參數(shù)的位置參數(shù)來結(jié)束本小節(jié):
assert.deepEqual(
parseArgs({
options,
allowPositionals: true,
args: [
'home.html', '--verbose', 'main.js', '--color', 'red', 'post.md'
]
}),
{
values: {__proto__:null,
verbose: true,
color: 'red',
},
positionals: [
'home.html', 'main.js', 'post.md'
]
}
);
16.3.2?多次使用選項(xiàng)
如果我們多次使用一個(gè)選項(xiàng),默認(rèn)情況下只有最后一次計(jì)數(shù)。它會(huì)覆蓋所有先前的出現(xiàn):
const options = {
'bool': {
type: 'boolean',
},
'str': {
type: 'string',
},
};
assert.deepEqual(
parseArgs({
options, args: [
'--bool', '--bool', '--str', 'yes', '--str', 'no'
]
}),
{
values: {__proto__:null,
bool: true,
str: 'no'
},
positionals: []
}
);
然而,如果我們?cè)谶x項(xiàng)的定義中將 .multiple
設(shè)置為 true
,parseArgs()
將以數(shù)組形式給出所有選項(xiàng)值:
const options = {
'bool': {
type: 'boolean',
multiple: true,
},
'str': {
type: 'string',
multiple: true,
},
};
assert.deepEqual(
parseArgs({
options, args: [
'--bool', '--bool', '--str', 'yes', '--str', 'no'
]
}),
{
values: {__proto__:null,
bool: [ true, true ],
str: [ 'yes', 'no' ]
},
positionals: []
}
);
16.3.3?更多使用長(zhǎng)和短選項(xiàng)的方式
考慮以下選項(xiàng):
const options = {
'verbose': {
type: 'boolean',
short: 'v',
},
'silent': {
type: 'boolean',
short: 's',
},
'color': {
type: 'string',
short: 'c',
},
};
以下是使用多個(gè)布爾選項(xiàng)的簡(jiǎn)潔方式:
assert.deepEqual(
parseArgs({options, args: ['-vs']}),
{
values: {__proto__:null,
verbose: true,
silent: true,
},
positionals: []
}
);
我們可以通過等號(hào)直接附加長(zhǎng)字符串選項(xiàng)的值。這稱為內(nèi)聯(lián)值。
assert.deepEqual(
parseArgs({options, args: ['--color=green']}),
{
values: {__proto__:null,
color: 'green'
},
positionals: []
}
);
短選項(xiàng)不能有內(nèi)聯(lián)值。
16.3.4?引用值
到目前為止,所有選項(xiàng)值和位置值都是單詞。如果我們想使用包含空格的值,我們需要用雙引號(hào)或單引號(hào)引起來。然而,并非所有 shell 都支持后者。
16.3.4.1?Shell 如何解析帶引號(hào)的值
為了檢查 shell 如何解析帶引號(hào)的值,我們?cè)俅问褂媚_本 args.mjs
:
#!/usr/bin/env node
console.log(process.argv.slice(2));
在 Unix 上,雙引號(hào)和單引號(hào)之間的區(qū)別如下:
-
雙引號(hào):我們可以用反斜杠轉(zhuǎn)義引號(hào)(否則原樣傳遞),并且可以插入變量:
% ./args.mjs "say \"hi\"" "\t\n" "$USER" [ 'say "hi"', '\\t\\n', 'rauschma' ]
-
單引號(hào):所有內(nèi)容都原樣傳遞,我們無法轉(zhuǎn)義引號(hào):
% ./args.mjs 'back slash\' '\t\n' '$USER' [ 'back slash\\', '\\t\\n', '$USER' ]
以下交互演示了雙引號(hào)和單引號(hào)的選項(xiàng)值:
% ./args.mjs --str "two words" --str 'two words'
[ '--str', 'two words', '--str', 'two words' ]
% ./args.mjs --str="two words" --str='two words'
[ '--str=two words', '--str=two words' ]
% ./args.mjs -s "two words" -s 'two words'
[ '-s', 'two words', '-s', 'two words' ]
在 Windows 命令 shell 中,單引號(hào)沒有任何特殊含義:
>node args.mjs "say \"hi\"" "\t\n" "%USERNAME%"
[ 'say "hi"', '\\t\\n', 'jane' ]
>node args.mjs 'back slash\' '\t\n' '%USERNAME%'
[ "'back", "slash\\'", "'\\t\\n'", "'jane'" ]
在 Windows 命令 shell 中引用的選項(xiàng)值:
>node args.mjs --str 'two words' --str "two words"
[ '--str', "'two", "words'", '--str', 'two words' ]
>node args.mjs --str='two words' --str="two words"
[ "--str='two", "words'", '--str=two words' ]
>>node args.mjs -s "two words" -s 'two words'
[ '-s', 'two words', '-s', "'two", "words'" ]
在 Windows PowerShell 中,我們可以用單引號(hào)引用,變量名不會(huì)在引號(hào)內(nèi)插值,而且單引號(hào)無法轉(zhuǎn)義:
> node args.mjs "say `"hi`"" "\t\n" "%USERNAME%"
[ 'say hi', '\\t\\n', '%USERNAME%' ]
> node args.mjs 'backtick`' '\t\n' '%USERNAME%'
[ 'backtick`', '\\t\\n', '%USERNAME%' ]
16.3.4.2?parseArgs()
如何處理帶引號(hào)的值
這是 parseArgs()
如何處理帶引號(hào)的值:
const options = {
'times': {
type: 'string',
short: 't',
},
'color': {
type: 'string',
short: 'c',
},
};
// Quoted external option values
assert.deepEqual(
parseArgs({
options,
args: ['-t', '5 times', '--color', 'light green']
}),
{
values: {__proto__:null,
times: '5 times',
color: 'light green',
},
positionals: []
}
);
// Quoted inline option values
assert.deepEqual(
parseArgs({
options,
args: ['--color=light green']
}),
{
values: {__proto__:null,
color: 'light green',
},
positionals: []
}
);
// Quoted positional values
assert.deepEqual(
parseArgs({
options, allowPositionals: true,
args: ['two words', 'more words']
}),
{
values: {__proto__:null,
},
positionals: [ 'two words', 'more words' ]
}
);
16.3.5?選項(xiàng)終結(jié)符
parseArgs()
支持所謂的選項(xiàng)終結(jié)符:如果 args
的一個(gè)元素是雙連字符(--
),那么剩余的參數(shù)都被視為位置參數(shù)。
選項(xiàng)終止符在哪里需要?一些可執(zhí)行文件調(diào)用其他可執(zhí)行文件,例如node 可執(zhí)行文件。然后,可以使用選項(xiàng)終止符將調(diào)用者的參數(shù)與被調(diào)用者的參數(shù)分開。
這是parseArgs()
處理選項(xiàng)終止符的方式:
const options = {
'verbose': {
type: 'boolean',
},
'count': {
type: 'string',
},
};
assert.deepEqual(
parseArgs({options, allowPositionals: true,
args: [
'how', '--verbose', 'are', '--', '--count', '5', 'you'
]
}),
{
values: {__proto__:null,
verbose: true
},
positionals: [ 'how', 'are', '--count', '5', 'you' ]
}
);
16.3.6 嚴(yán)格的parseArgs()
如果選項(xiàng).strict
為true
(這是默認(rèn)值),那么parseArgs()
如果發(fā)生以下情況之一,將拋出異常:
-
args
中使用的選項(xiàng)名稱不在options
中。 -
args
中的選項(xiàng)類型錯(cuò)誤。目前,僅當(dāng)字符串選項(xiàng)缺少參數(shù)時(shí)才會(huì)發(fā)生這種情況。 -
args
中有位置參數(shù),即使.allowPositions
為false
(這是默認(rèn)值)。
以下代碼演示了每種情況:
const options = {
'str': {
type: 'string',
},
};
// Unknown option name
assert.throws(
() => parseArgs({
options,
args: ['--unknown']
}),
{
name: 'TypeError',
message: "Unknown option '--unknown'",
}
);
// Wrong option type (missing value)
assert.throws(
() => parseArgs({
options,
args: ['--str']
}),
{
name: 'TypeError',
message: "Option '--str <value>' argument missing",
}
);
// Unallowed positional
assert.throws(
() => parseArgs({
options,
allowPositionals: false, // (the default)
args: ['posarg']
}),
{
name: 'TypeError',
message: "Unexpected argument 'posarg'. " +
"This command does not take positional arguments",
}
);
16.4 parseArgs
標(biāo)記
parseArgs()
在兩個(gè)階段處理args
數(shù)組:
-
階段 1:它將
args
解析為標(biāo)記數(shù)組:這些標(biāo)記大多是帶有類型信息的args
元素:它是一個(gè)選項(xiàng)嗎?它是一個(gè)位置參數(shù)嗎?等等。但是,如果選項(xiàng)有一個(gè)值,那么標(biāo)記將存儲(chǔ)選項(xiàng)名稱和選項(xiàng)值,因此包含兩個(gè)args
元素的數(shù)據(jù)。 -
階段 2:它將標(biāo)記組裝成通過結(jié)果屬性
.values
返回的對(duì)象。
如果將config.tokens
設(shè)置為true
,則可以訪問標(biāo)記。然后,parseArgs()
返回的對(duì)象包含一個(gè)名為.tokens
的屬性,其中包含標(biāo)記。
這些是標(biāo)記的屬性:
type Token = OptionToken | PositionalToken | OptionTerminatorToken;
interface CommonTokenProperties {
/** Where in `args` does the token start? */
index: number;
}
interface OptionToken extends CommonTokenProperties {
kind: 'option';
/** Long name of option */
name: string;
/** The option name as mentioned in `args` */
rawName: string;
/** The option’s value. `undefined` for boolean options. */
value: string | undefined;
/** Is the option value specified inline (e.g. --level=5)? */
inlineValue: boolean | undefined;
}
interface PositionalToken extends CommonTokenProperties {
kind: 'positional';
/** The value of the positional, args[token.index] */
value: string;
}
interface OptionTerminatorToken extends CommonTokenProperties {
kind: 'option-terminator';
}
16.4.1 標(biāo)記示例
例如,考慮以下選項(xiàng):
const options = {
'bool': {
type: 'boolean',
short: 'b',
},
'flag': {
type: 'boolean',
short: 'f',
},
'str': {
type: 'string',
short: 's',
},
};
布爾選項(xiàng)的標(biāo)記如下:
assert.deepEqual(
parseArgs({
options, tokens: true,
args: [
'--bool', '-b', '-bf',
]
}),
{
values: {__proto__:null,
bool: true,
flag: true,
},
positionals: [],
tokens: [
{
kind: 'option',
name: 'bool',
rawName: '--bool',
index: 0,
value: undefined,
inlineValue: undefined
},
{
kind: 'option',
name: 'bool',
rawName: '-b',
index: 1,
value: undefined,
inlineValue: undefined
},
{
kind: 'option',
name: 'bool',
rawName: '-b',
index: 2,
value: undefined,
inlineValue: undefined
},
{
kind: 'option',
name: 'flag',
rawName: '-f',
index: 2,
value: undefined,
inlineValue: undefined
},
]
}
);
請(qǐng)注意,對(duì)于選項(xiàng)bool
,有三個(gè)標(biāo)記,因?yàn)樗?code>args中被提及三次。但是,由于解析的第二階段,.values
中只有一個(gè)bool
屬性。
在下一個(gè)示例中,我們將字符串選項(xiàng)解析為標(biāo)記。.inlineValue
現(xiàn)在具有布爾值(對(duì)于布爾選項(xiàng),它始終為undefined
):
assert.deepEqual(
parseArgs({
options, tokens: true,
args: [
'--str', 'yes', '--str=yes', '-s', 'yes',
]
}),
{
values: {__proto__:null,
str: 'yes',
},
positionals: [],
tokens: [
{
kind: 'option',
name: 'str',
rawName: '--str',
index: 0,
value: 'yes',
inlineValue: false
},
{
kind: 'option',
name: 'str',
rawName: '--str',
index: 2,
value: 'yes',
inlineValue: true
},
{
kind: 'option',
name: 'str',
rawName: '-s',
index: 3,
value: 'yes',
inlineValue: false
}
]
}
);
最后,這是解析位置參數(shù)和選項(xiàng)終止符的示例:
assert.deepEqual(
parseArgs({
options, allowPositionals: true, tokens: true,
args: [
'command', '--', '--str', 'yes', '--str=yes'
]
}),
{
values: {__proto__:null,
},
positionals: [ 'command', '--str', 'yes', '--str=yes' ],
tokens: [
{ kind: 'positional', index: 0, value: 'command' },
{ kind: 'option-terminator', index: 1 },
{ kind: 'positional', index: 2, value: '--str' },
{ kind: 'positional', index: 3, value: 'yes' },
{ kind: 'positional', index: 4, value: '--str=yes' }
]
}
);
16.4.2 使用標(biāo)記實(shí)現(xiàn)子命令
默認(rèn)情況下,parseArgs()
不支持git clone
或npm install
等子命令。但是,通過標(biāo)記,相對(duì)容易實(shí)現(xiàn)此功能。
這是實(shí)現(xiàn)方式:
function parseSubcommand(config) {
// The subcommand is a positional, allow them
const {tokens} = parseArgs({
...config, tokens: true, allowPositionals: true
});
let firstPosToken = tokens.find(({kind}) => kind==='positional');
if (!firstPosToken) {
throw new Error('Command name is missing: ' + config.args);
}
//----- Command options
const cmdArgs = config.args.slice(0, firstPosToken.index);
// Override `config.args`
const commandResult = parseArgs({
...config, args: cmdArgs, tokens: false, allowPositionals: false
});
//----- Subcommand
const subcommandName = firstPosToken.value;
const subcmdArgs = config.args.slice(firstPosToken.index+1);
// Override `config.args`
const subcommandResult = parseArgs({
...config, args: subcmdArgs, tokens: false
});
return {
commandResult,
subcommandName,
subcommandResult,
};
}
這是parseSubcommand()
的示例:
const options = {
'log': {
type: 'string',
},
color: {
type: 'boolean',
}
};
const args = ['--log', 'all', 'print', '--color', 'file.txt'];
const result = parseSubcommand({options, allowPositionals: true, args});
const pn = obj => Object.setPrototypeOf(obj, null);
assert.deepEqual(
result,
{
commandResult: {
values: pn({'log': 'all'}),
positionals: []
},
subcommandName: 'print',
subcommandResult: {
values: pn({color: true}),
positionals: ['file.txt']
}
}
);
評(píng)論
十七、Shell 腳本配方
原文:
exploringjs.com/nodejs-shell-scripting/ch_shell-scripting-recipes.html
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
-
17.1?通過 nodemon 交互式編輯代碼片段
-
17.1.1?nodemon
-
17.1.2?嘗試使用 nodemon 而不安裝它
-
-
17.2?檢測(cè)當(dāng)前模塊是否為“main”(應(yīng)用程序入口點(diǎn))
-
17.3?相對(duì)于當(dāng)前模塊訪問文件
17.1?通過 nodemon 交互式編輯代碼片段
本節(jié)描述了在處理 JavaScript 代碼片段時(shí)使用 Node.js 的技巧。
17.1.1?nodemon
例如,假設(shè)我們想要嘗試標(biāo)準(zhǔn) Node.js 函數(shù)util.format()
。我們創(chuàng)建文件mysnippet.mjs
,內(nèi)容如下:
import * as util from 'node:util';
console.log(util.format('Hello %s!', 'world'));
我們?nèi)绾卧谔幚硭鼤r(shí)運(yùn)行mysnippet.mjs
?
首先安裝npm 包nodemon:
npm install -g nodemon
然后我們可以使用它來持續(xù)運(yùn)行mysnippet.mjs
:
nodemon mysnippet.mjs
每當(dāng)我們保存mysnippet.mjs
,nodemon 會(huì)再次運(yùn)行它。這意味著我們可以在編輯器中編輯該文件,并在保存時(shí)查看更改的結(jié)果。
17.1.2?嘗試使用 nodemon 而不安裝它
甚至可以通過 npx(Node.js 工具)嘗試使用 nodemon 而不安裝它:
npx nodemon mysnippet.mjs
17.2?檢測(cè)當(dāng)前模塊是否為“main”(應(yīng)用程序入口點(diǎn))
參見§7.11.4“URL 用例:檢測(cè)當(dāng)前模塊是否為“main”(應(yīng)用程序入口點(diǎn))”。
17.3?相對(duì)于當(dāng)前模塊訪問文件
參見§7.11.3“URL 用例:訪問相對(duì)于當(dāng)前模塊的文件”。
評(píng)論
十八、跨平臺(tái)考慮
原文:
exploringjs.com/nodejs-shell-scripting/ch_cross-platform-considerations.html
譯者:飛龍
協(xié)議:CC BY-NC-SA 4.0
-
18.1 文件系統(tǒng)路徑
-
18.2 處理行終止符
-
18.3 檢測(cè)當(dāng)前平臺(tái)
-
18.4 在所有平臺(tái)上運(yùn)行與項(xiàng)目相關(guān)的任務(wù)
18.1 文件系統(tǒng)路徑
本書其他地方的材料:
-
§7.2.1 “路徑段、路徑分隔符、路徑分隔符”
-
§7.9 “在不同平臺(tái)上使用相同的路徑”
-
§7.3 “通過模塊
'node:os'
獲取標(biāo)準(zhǔn)目錄路徑”
18.2 處理行終止符
參見 §8.3 “跨平臺(tái)處理行終止符”。
18.3 檢測(cè)當(dāng)前平臺(tái)
-
process.platform
包含一個(gè)標(biāo)識(shí)當(dāng)前平臺(tái)的字符串。可能的值有:-
'aix'
-
'darwin'
-
'freebsd'
-
'linux'
-
'openbsd'
-
'sunos'
-
'win32'
-
-
模塊
'node:os'
包含更多與平臺(tái)相關(guān)的信息(處理器架構(gòu)、操作系統(tǒng)版本等)。
18.4 在所有平臺(tái)上運(yùn)行與項(xiàng)目相關(guān)的任務(wù)
參見 §15 “通過 npm 包腳本運(yùn)行跨平臺(tái)任務(wù)”。文章來源:http://www.zghlxwxcb.cn/news/detail-818879.html
評(píng)論
到了這里,關(guān)于Node.js Shell 腳本開發(fā)指南(下)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!