使用 Node.js 编写 Shell 脚本
您可以购买本书的离线版本(HTML、PDF、EPUB、MOBI),并支持免费在线版本。
(广告,请不要屏蔽。)

13. 安装 npm 包和运行 bin 脚本



package.json 属性 "bin" 允许 npm 包指定它提供的 shell 脚本(有关更多信息,请参阅 §14 “创建跨平台 shell 脚本”)。如果我们安装了这样一个包,Node.js 会确保我们可以从命令行访问这些 shell 脚本(所谓的*bin 脚本*)。在本章中,我们将探讨两种安装带有 bin 脚本的包的方法。

我们将探讨所有这些含义,以及如何在安装 bin 脚本后运行它们。

13.1. 全局安装 npm 注册表包

cowsay 具有以下 package.json 属性

"bin": {
  "cowsay": "./cli.js",
  "cowthink": "./cli.js"
},

要全局安装此包,我们使用 npm install -g

npm install -g cowsay

注意:在 Unix 上,我们可能必须使用 sudo(我们很快就会学习如何避免这种情况)

sudo npm install -g cowsay

之后,我们可以在命令行中使用 cowsaycowthink 命令。

请注意,只有 bin 脚本是全局可用的。当 Node.js 在 node_modules 目录中查找裸模块说明符时,会忽略这些包。

13.1.1. 哪些包是全局安装的?npm ls -g

我们可以检查哪些包是全局安装的,以及安装在哪里

% npm ls -g
/usr/local/lib
├── [email protected]
├── [email protected]
└── [email protected]

在 Windows 上,安装路径为 %AppData%\npm,例如

>echo %AppData%\npm
C:\Users\jane\AppData\Roaming\npm

13.1.2. 全局安装的包在哪里?npm root -g

macOS 上的结果

% npm root -g
/usr/local/lib/node_modules

Windows 上的结果

>npm root -g
C:\Users\jane\AppData\Roaming\npm\node_modules

13.1.3. 全局安装的 shell 脚本在哪里?npm bin -g

npm bin -g 告诉我们 npm 在哪里全局安装 shell 脚本。它还确保该目录在 shell PATH 中可用。

macOS 上的结果

% npm bin -g
/usr/local/bin

% which cowsay
/usr/local/bin/cowsay

Windows 命令提示符上的结果

>npm bin -g
C:\Users\jane\AppData\Roaming\npm

>where cowsay
C:\Users\jane\AppData\Roaming\npm\cowsay
C:\Users\jane\AppData\Roaming\npm\cowsay.cmd

没有文件扩展名的可执行文件 cowsay 适用于基于 Unix 的 Windows 环境,例如 Cygwin、MinGW 和 MSYS。

Windows PowerShell 为 gcm cowsay 返回此路径

C:\Users\jane\AppData\Roaming\npm\cowsay.ps1

13.1.4. 全局安装的包在哪里?npm 安装前缀

npm 的*安装前缀*决定了全局安装包和 bin 脚本的位置。

这是 macOS 上的安装前缀

% npm config get prefix
/usr/local

因此

这是 Windows 上的安装前缀

>npm config get prefix
C:\Users\jane\AppData\Roaming\npm

因此

13.1.5. 更改全局安装包的位置

在本节中,我们将研究两种更改全局安装包位置的方法

13.1.5.1. 更改 npm 安装前缀

更改全局安装包位置的一种方法是更改 npm 安装前缀。

Unix

mkdir ~/npm-global
npm config set prefix '~/npm-global'

Windows 命令提示符

mkdir "%UserProfile%\npm-global"
npm config set prefix "%UserProfile%\npm-global"

Windows PowerShell

mkdir "$env:UserProfile\npm-global"
npm config set prefix "$env:UserProfile\npm-global"

配置数据将保存到主目录中的 .npmrc 文件中。

从现在开始,全局安装将添加到我们刚刚指定的目录中。

之后,我们仍然需要将 npm bin -g 目录添加到我们的 shell PATH 中,以便我们的 shell 能够找到我们全局安装的 bin 脚本。

**更改 npm 前缀的缺点:** 如果我们告诉 npm 升级自身,它现在也会安装在新位置。

13.1.5.2. 使用 Node.js 版本管理器

Node.js 版本管理器允许我们同时安装多个版本的 Node.js,并在它们之间切换。流行的版本管理器包括

13.2. 本地安装 npm 注册表包

要*本地*安装 npm 注册表包(例如 cowsay)(到一个包中),我们执行以下操作

cd my-package/
npm install cowsay

这会将以下数据添加到 package.json

"dependencies": {
  "cowsay": "^1.5.0",
  ···
}

此外,该包将下载到以下目录中

my-package/node_modules/cowsay/

在 Unix 上,npm 为 bin 脚本添加了以下符号链接

my-package/node_modules/.bin/cowsay -> ../cowsay/cli.js
my-package/node_modules/.bin/cowthink -> ../cowsay/cli.js

在 Windows 上,npm 将这些文件添加到 my-package\node_modules\.bin\

cowsay
cowsay.cmd
cowsay.ps1
cowthink
cowthink.cmd
cowthink.ps1

没有扩展名的文件是用于基于 Unix 的 Windows 环境(例如 Cygwin、MinGW 和 MSYS)的脚本。

npm bin 告诉我们本地安装的 bin 脚本的位置 - 例如

% npm bin
/Users/john/my-package/node_modules/.bin

注意:在本地,包始终安装在 package.json 文件旁边的 node_modules 目录中。如果当前目录中不存在后者,npm 会在祖先目录中搜索它,并将包安装在那里。要检查 npm 会在本地安装包的位置,我们可以使用命令 npm root - 例如(Unix)

% cd $HOME
% npm root
/Users/john/node_modules

John 的主目录中没有 package.json,但 npm 无法在祖先目录中安装任何内容,这就是 npm root 显示此目录的原因。在当前位置本地安装包将导致创建 package.json 并照常进行安装。

13.2.1. 运行本地安装的 bin 脚本

(本小节中的所有命令都在目录 my-package 中执行。)

13.2.1.1. 直接运行 bin 脚本

我们可以从 shell 中按如下方式运行 cowsay

./node_modules/.bin/cowsay Hello

在 Unix 上,我们可以设置一个助手

alias npm-exec='PATH=$(npm bin):$PATH'

然后以下命令将起作用

npm-exec cowsay Hello
13.2.1.2. 通过包脚本运行 bin 脚本

我们还可以向 package.json 添加一个包脚本

{
  ···
  "scripts": {
    "cowsay": "cowsay"
  },
  ···
}

现在我们可以在 shell 中执行此命令

npm run cowsay Hello

之所以可行,是因为 npm 在 Unix 上临时将以下条目添加到 $PATH

/Users/john/my-package/node_modules/.bin
/Users/john/node_modules/.bin
/Users/node_modules/.bin
/node_modules/.bin

在 Windows 上,类似的条目将添加到 %Path%$env:Path

C:\Users\jane\my-package\node_modules\.bin
C:\Users\jane\node_modules\.bin
C:\Users\node_modules\.bin
C:\node_modules\.bin

以下命令列出了包脚本运行时存在的环境变量及其值

npm run env
13.2.1.3. 通过 npx 运行 bin 脚本

在包内部,可以使用 npx 访问 bin 脚本

npx cowsay Hello
npx cowthink Hello

稍后将详细介绍 npx。

13.3. 安装未发布的包

有时,我们有一个尚未发布或永远不会发布的包,并且希望安装它。

假设我们有一个名为 @my-scope/unpublished-package 的未发布包,它存储在目录 /tmp/unpublished-package/ 中。我们可以通过以下方式使其全局可用

cd /tmp/unpublished-package/
npm link

如果我们这样做

由于链接包的引用方式,对其进行的任何更改都将立即生效。更改时无需重新链接。

要检查全局安装是否成功,我们可以使用 npm ls -g 列出所有全局安装的包。

在全局安装了未发布的包之后(请参阅上一小节),我们可以选择将其本地安装在我们的一台机器上

cd /tmp/other-package/
npm link @my-scope/unpublished-package

这将创建以下链接

/tmp/other-package/node_modules/@my-scope/unpublished-package
-> ../../../unpublished-package

默认情况下,未发布的包不会作为依赖项添加到 package.json 中。其背后的理由是,npm link 通常用于临时使用注册表包的未发布版本 - 这不应该出现在依赖项中。

取消本地链接

cd /tmp/other-package/
npm uninstall @my-scope/unpublished-package

取消全局链接

cd /tmp/unpublished-package/
npm uninstall -g

13.3.4. 通过本地路径安装未发布的包

本地安装未发布包的另一种方法是使用 npm install 并通过本地路径(而不是包名称)引用它

cd /tmp/other-package/
npm install ../unpublished-package

这有两个效果。

首先,将创建以下符号链接

/tmp/other-package/node_modules/@my-scope/unpublished-package
-> ../../../unpublished-package

其次,将依赖项添加到 package.json

"dependencies": {
  "@my-scope/unpublished-package": "file:../unpublished-package",
  ···
}

这种安装未发布包的方法也适用于全局安装

cd /tmp/unpublished-package/
npm install -g .

13.3.5. 安装未发布包的其他方法

13.4. npx:在 npm 包中运行 bin 脚本,而无需安装它们

npx 是一个用于运行 bin 脚本的 shell 命令,它与 npm 捆绑在一起。

它最常见的用法是

npx <package-name> arg1 arg2 ...

此命令在 npx 缓存中安装名为 package-name 的包,并运行与包名称相同的 bin 脚本 - 例如

npx cowsay Hello

这意味着我们可以在不先安装 bin 脚本的情况下运行它们。npx 最适合一次性调用 bin 脚本 - 例如,许多框架都提供用于设置新项目的 bin 脚本,而这些脚本通常是通过 npx 运行的。

npx 首次使用某个包后,该包将在其缓存中可用,后续调用将快得多。但是,我们无法确定包在缓存中保留的时间。因此,npx 不能替代全局或本地安装 bin 脚本。

如果包附带的 bin 脚本的名称与其包名称不同,我们可以像这样访问它们

npx --package=<package-name> <bin-script> arg1 arg2 ...

例如

npx --package=cowsay cowthink Hello

13.4.1. npx 缓存

npx 的缓存位于哪里?

在 Unix 上,我们可以通过以下命令找到它

npx --package=cowsay node -p \
  "process.env.PATH.split(':').find(p => p.includes('_npx'))"

这将返回一个类似于此的路径

/Users/john/.npm/_npx/8f497369b2d6166e/node_modules/.bin

在 Windows 上,我们可以使用(一行分成两行)

npx --package=cowsay node -p
  "process.env.Path.split(';').find(p => p.includes('_npx'))"

这将返回一个类似于此的路径(单个路径分成两行)

C:\Users\jane\AppData\Local\npm-cache\_npx\
  8f497369b2d6166e\node_modules\.bin

请注意,npx 的缓存与 npm 用于安装模块的缓存不同

可以通过以下方式确定两个缓存的父目录

npm config get cache

有关 npm 缓存的更多信息,请参阅npm 文档

与 npx 缓存不同,数据永远不会从 npm 缓存中删除,只会添加。我们可以在 Unix 上按如下方式检查其大小

du -sh $(npm config get cache)/_cacache/

在 Windows PowerShell 上

DiskUsage /d:0 "$(npm config get cache)\_cacache"