为什么我不能将 npm install 通过管道传输到 sed,同时保留颜色和进度更新?

Why can't I pipe npm install to sed, while preserving color and progress updates?

如果我键入 npm i | sed "s/^/ /"npm i 的输出在打印到标准输出时没有间隔。例如。我得到以下信息:

$ npm i | sed "s/^/    /"
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@^1.0.0 
(node_modules/chokidar/node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for 
fsevents@1.1.2: wanted {"os":"darwin","arch":"any"} (current: 
{"os":"linux","arch":"x64"})
npm WARN [name_removed]@0.0.2 No repository field.
npm WARN [name_removed]@0.0.2 No license field.

而不是:

$ npm i | sed "s/^/    /"
    npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@^1.0.0 
    (node_modules/chokidar/node_modules/fsevents):
    npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for 
    fsevents@1.1.2: wanted {"os":"darwin","arch":"any"} (current: 
    {"os":"linux","arch":"x64"})
    npm WARN [name_removed]@0.0.2 No repository field.
    npm WARN [name_removed]@0.0.2 No license field.

编辑: 警告将转到 stderr(duh...),所以我需要使用 npm i 2>&1 | sed 's/^/ /,但这会从输出中删除颜色并且我看不到进度条,您可以在下面的 gif 中看到。

Edit2:颜色已通过 npm i --color=always | sed 's/^/ / 修复,但我仍然看不到进度条。此外,它似乎在所有输出之后的行中添加了行......我假设这是由输出颜色代码引起的?您可以在下面的 gif 中看到这种现象:

目前状态:

您在这里遇到了几个问题,其中大部分与 npm 处理其输出的方式有关。

捕获警告

首先请注意,npm 输出 warnings 并在 stderr 上更新 progress,而只有最终结果至 stdout。因此,为了处理警告,您必须将 stderr 重定向到 stdout,例如:

npm install 2>&1 | sed 's/^/    /'

保留颜色

但是现在 stderr 通过管道传输到 sed 进程,您会注意到 npm 省略了着色!然而,这是大多数命令行工具(如 lsgrep 等)的标准行为。只有当输出到 TTY 设备(即到用户,而不是文件或管道)时,它们才会输出 ANSI 转义(颜色)序列。确定文件描述符是否连接到 TTY 的常用方法是通过 isatty(int fd) function. It turns out (after some digging) npm 使用相同的机制。

要解决着色问题,我们有两个选择:

(1) 我们可以 强制 颜色输出 --color=always 选项(类似于 ls, grep, 其他):

npm install --color=always 2>&1 | sed 's/^/    /'

或者,(2) 我们可以使用一个名为 script 的工具,它将 伪造 一个 TTY任何 program/script 运行:

的输出设备
script -feqc 'npm install' /dev/null | sed 's/^/    /'

请注意,我们不必再将 stderr 重定向到 stdoutscript 会为我们做这件事。此外,script 会将完整的输出保存到我​​们选择的文件中(在这种情况下我们不需要它,所以我们说 /dev/null)。 (顺便说一句,-f 将在每次写入后刷新输出,-e 将确保命令的退出代码返回到 parent/shell,-q 强制 quiet 模式,没有信息消息,使用 -c cmd 我们将命令提供给 运行。)

保留进度更新

好的,现在我们缩进了警告并保留了颜色,但是我们丢失了进度(栏)更新!

为什么会这样?好吧,因为 npm 在一行中输出了完整的进度。在每次进度更新时,它将移动到字符位置零(同一行!)并打印新进度。对于sed完整的进度只是一行,由于sed是面向行的,所以会等到行尾(\n) 在任何处理(和输出)之前。

显然,我们需要降低一个级别 - 逐个字符地处理。为了达到缩进的效果,我们将每次出现的 \n 替换为 \n<4 spaces>.

通常,对于字符翻译我们可以使用tr,但在这里我们需要更多,因为在某些情况下(\n)我们需要将一个字符扩展为多个。一种方法是使用这个简单的 bash script/function:

#!/bin/bash
# read each character of stdin, indenting each line
interactive_indent() {
    local space='    '
    echo -n "$space"
    while IFS= read -r -d '' -n1 chr; do
        [[ $chr == $'\n' ]] && chr="\n\r$space"
        [[ $chr == $'\r' ]] && chr="\r$space"
        echo -ne "$chr"
    done
    echo -ne '\r'
}

例如:

$ echo -e 'one\ntwo\rthree' | interactive_indent
    one
    three

最后,我们来解决我们的交互npm过程:

script -feqc 'npm install' /dev/null | interactive_indent

这将传递每个字符(显示进度),同时缩进每行(在 \n\r 之后)。

请注意,我们的 interactive_indent 函数比简单的 \n-to-\n<spaces> 替换器稍微复杂一些。我们还必须处理 returns (\r) ,它被 npm 大量用于进度更新和依赖树绘制,并确保每个新行都从位置零开始(因此 \r 旁边每个 \n).