国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Node.js Shell 腳本開發(fā)指南(下)

這篇具有很好參考價(jià)值的文章主要介紹了Node.js Shell 腳本開發(fā)指南(下)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

十四、創(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.mjsversions.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è)名為homedirversions的 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 installdry 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.accesspackage.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ù)$0node-esm的路徑)并將剩余的參數(shù)傳遞給node。

  • execnode運(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.batscript運(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 變量使我們能夠通過hellohello.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 --evalnode --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的信息。
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)腳本都是為shcmd.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-scriptstrue,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í)更清晰。

以下表格總結(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 --evalnode --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)入osfs。

  • 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、chmodecho、findgrep、headln、ls、mv、pwd、sed、sort、tailtouch、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:stagingSCRIPT: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"
 }
 },
 ···
}

支持的屬性值有:darwinfreebsd、linuxsunos、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ù)涉及以下步驟:

  1. 用戶輸入一個(gè)文本字符串。

  2. shell 將字符串解析為一系列單詞和操作符。

  3. 如果調(diào)用一個(gè)命令,它會(huì)得到零個(gè)或多個(gè)單詞作為參數(shù)。

  4. 我們的 Node.js 代碼通過存儲(chǔ)在process.argv中的數(shù)組接收這些單詞。process是 Node.js 上的全局變量。

  5. 我們使用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.htmlmain.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)--timesparseArgs()對(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ì)介紹。

  • .allowPositionalsargs 是否包含位置參數(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)我們探索具有 .multipletrue 的選項(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).stricttrue(這是默認(rèn)值),那么parseArgs()如果發(fā)生以下情況之一,將拋出異常:

  • args中使用的選項(xiàng)名稱不在options中。

  • args中的選項(xiàng)類型錯(cuò)誤。目前,僅當(dāng)字符串選項(xiàng)缺少參數(shù)時(shí)才會(huì)發(fā)生這種情況。

  • args中有位置參數(shù),即使.allowPositionsfalse(這是默認(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 clonenpm 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ù)”。

評(píng)論

到了這里,關(guān)于Node.js Shell 腳本開發(fā)指南(下)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • PDF.js 前端開發(fā)使用指南

    PDF.js是一個(gè)用于在網(wǎng)頁中顯示PDF文檔的JavaScript庫。它是由Mozilla開發(fā)的,是一個(gè)完全免費(fèi)、開源的工具。在本篇文章中,我們將詳細(xì)介紹如何使用PDF.js進(jìn)行前端開發(fā),包括基本的使用方法、代碼示例以及一些實(shí)用的技巧。 1. 安裝 PDF.js 安裝PDF.js有兩種方法: 方法1:通過npm安裝

    2024年02月08日
    瀏覽(21)
  • Unity 3D游戲開發(fā)+腳本編程完整指南:制作第一個(gè)游戲:3D滾球跑酷

    Unity 3D游戲開發(fā)+腳本編程完整指南:制作第一個(gè)游戲:3D滾球跑酷

    教程相關(guān)資源 Unity 3D游戲開發(fā)+腳本編程完整指南(工程文件+PPT).zip 本節(jié)利用前面的知識(shí)來實(shí)現(xiàn)第一個(gè)較為完整的小游戲,如 圖 1-21 所示。 圖1-21 3D滾球跑酷游戲完成效果 1. 功能點(diǎn)分析 游戲中的小球會(huì)以恒定速度向前移動(dòng),而玩家控制著小球 左右移動(dòng)來躲避跑道中的黃色障

    2024年02月21日
    瀏覽(31)
  • 一份Node.js性能優(yōu)化技巧指南

    一份Node.js性能優(yōu)化技巧指南

    你是一個(gè)踏入后端開發(fā)領(lǐng)域的前端愛好者嗎??? 準(zhǔn)備好迎接在Node.js錯(cuò)綜復(fù)雜的景觀中驚險(xiǎn)刺激的旅程吧!?? 在這個(gè)探險(xiǎn)中,我們將探索后端的無數(shù)奇跡,從使用Node.js框架快速創(chuàng)建自己的后端,到應(yīng)對(duì)性能分析、測(cè)試,深入內(nèi)存管理。???? 加入我們,揭開C++插件的奧秘,征服子進(jìn)程

    2024年02月21日
    瀏覽(17)
  • 如何在shell腳本將node_modules里的文件復(fù)制一份到public文件里

    項(xiàng)目背景:由于公司網(wǎng)絡(luò)不連接公網(wǎng),所以在繪制地圖大屏項(xiàng)目時(shí),需要我們將邊界線數(shù)據(jù)包也部署起來,來獲取邊界線數(shù)據(jù) 解決方案: 1.讓后端寫個(gè)接口或者找個(gè)地方將數(shù)據(jù)包放到服務(wù)器即可 2.將數(shù)據(jù)包放到vue項(xiàng)目的public文件里,這樣同樣可以通過axios.get(\\\'/abc/def.json\\\'),這種

    2024年02月14日
    瀏覽(26)
  • 完整指南:如何使用 Node.js 復(fù)制文件

    完整指南:如何使用 Node.js 復(fù)制文件

    文件拷貝指的是將一個(gè)文件的數(shù)據(jù)復(fù)制到另一個(gè)文件中,使目標(biāo)文件與源文件內(nèi)容一致。 Node.js ?提供了文件系統(tǒng)模塊 fs,通過該模塊可以訪問文件系統(tǒng),實(shí)現(xiàn)文件操作,包括拷貝文件。 在 Node.js 中,有幾種常用的方法可以用來實(shí)現(xiàn)文件拷貝: 1.使用 fs.copyFile() ?Node.js v8.5.

    2024年02月07日
    瀏覽(15)
  • Node.js 中如何寫入文件:全面指南

    Node.js 中如何寫入文件:全面指南

    文件寫入是? Node.js ?中的一項(xiàng)重要任務(wù),它允許你將數(shù)據(jù)保存到本地文件系統(tǒng)中,供后續(xù)使用。這個(gè)功能在許多應(yīng)用中都有廣泛的應(yīng)用,包括數(shù)據(jù)備份、日志記錄、配置文件更新等。在本文,我們將介紹如何在 Node.js 中執(zhí)行文件寫入操作,提供基本概念、常用方法、使用場(chǎng)景

    2024年02月04日
    瀏覽(22)
  • Node.js 版本管理工具 n 使用指南

    Node.js 版本管理工具 n 使用指南

    Node.js 版本更新很快,目前 node v20.x 已經(jīng)發(fā)布,我們?cè)谑褂脮r(shí)避免不了會(huì)需要切換不同的 Node.js 的版本來使用不同版本的特性。 所以就出現(xiàn)了像 windows 上的 nvm ,MacOS 上的 n 工具,本文就介紹一下如何使用 n 管理 Node.js 的版本。 使用 Brew 安裝時(shí),未安裝可以參考 Brew 官網(wǎng)安裝

    2024年02月16日
    瀏覽(18)
  • 簡(jiǎn)單易懂的 nvm 和 Node.js 版本控制指南

    簡(jiǎn)單易懂的 nvm 和 Node.js 版本控制指南

    NVM是Node.js的版本管理工具,可以方便地在不同版本的Node.js之間切換。它可以通過命令行或者腳本來管理Node.js的版本,支持在同一臺(tái)機(jī)器上安裝多個(gè)版本的Node.js,并能夠方便地切換它們。 NVM的主要功能包括: 安裝和卸載Node.js的不同版本。 切換不同版本的Node.js。 管理全局和

    2024年02月08日
    瀏覽(27)
  • Node.js 的 Buffer 是什么?一站式了解指南

    Node.js 的 Buffer 是什么?一站式了解指南

    在 Node.js 中,Buffer 是一種用于處理二進(jìn)制數(shù)據(jù)的機(jī)制。它允許你在不經(jīng)過 JavaScript 垃圾回收機(jī)制的情況下直接操作原始內(nèi)存,從而更高效地處理數(shù)據(jù),特別是在處理網(wǎng)絡(luò)流、文件系統(tǒng)操作和其他與 I/O 相關(guān)的任務(wù)時(shí)。Buffer 是一個(gè)全局對(duì)象,不需要額外的模塊導(dǎo)入就可以使用。

    2024年02月11日
    瀏覽(25)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包