跳到内容

Process Promise

$ 返回一个 ProcessPromise 实例,它继承了原生的 Promise。当 resolve 时,它会变成一个 ProcessOutput

js
const p = $`command` // ProcessPromise
const o = await p    // ProcessOutput

默认情况下,$ 会立即生成一个新的进程,但是你可以延迟启动,手动触发。

ts
const p = $({halt: true})`command`
const o = await p.run()

stage

显示当前进程阶段:initial | halted | running | fulfilled | rejected

ts
const p = $`echo foo`
p.stage // 'running'
await p
p.stage // 'fulfilled'

stdin

返回 stdin 进程的可写流。访问这个 getter 会触发一个使用 stdio('pipe') 的子进程执行。

不要忘记结束流。

js
const p = $`while read; do echo $REPLY; done`
p.stdin.write('Hello, World!\n')
p.stdin.end()

默认情况下,每个进程都是以 inherit 模式创建的。

stdout/stderr

返回 stdout/stderr 进程的可读流。

js
const p = $`npm init`
for await (const chunk of p.stdout) {
  echo(chunk)
}

exitCode

返回一个 promise,它会 resolve 为进程的退出码。

js
if (await $`[[ -d path ]]`.exitCode == 0) {
...
}

json(), text(), lines(), buffer(), blob()

输出格式化器集合。

js
const p = $`echo 'foo\nbar'`

await p.text()        // foo\n\bar\n
await p.text('hex')   //  666f6f0a0861720a
await p.buffer()      //  Buffer.from('foo\n\bar\n')
await p.lines()       // ['foo', 'bar']
await $`echo '{"foo": "bar"}'`.json() // {foo: 'bar'}

[Symbol.asyncIterator]

返回 stdout 进程的异步迭代器。

js
const p = $`echo "Line1\nLine2\nLine3"`
for await (const line of p) {
  console.log()
}

pipe()

重定向进程的输出。

js
await $`echo "Hello, stdout!"`
  .pipe(fs.createWriteStream('/tmp/output.txt'))

await $`cat /tmp/output.txt`

pipe() 接受任何类型的 Writable, ProcessPromise 或文件路径。你可以传递一个字符串给 pipe() 来隐式地创建一个接收文件。前面的例子等价于

js
await $`echo "Hello, stdout!"`
  .pipe('/tmp/output.txt')

链式流会变成 thenables,所以你可以 await 它们

js
const p = $`echo "hello"`
  .pipe(getUpperCaseTransform())
  .pipe(fs.createWriteStream(tempfile()))  // <- stream
const o = await p

并且 ProcessPromise 本身与标准的 Stream.pipe API 兼容

js
const { stdout } = await fs
  .createReadStream(await fs.writeFile(file, 'test'))
  .pipe(getUpperCaseTransform())
  .pipe($`cat`)

管道可以用来显示进程的实时输出

js
await $`echo 1; sleep 1; echo 2; sleep 1; echo 3;`
  .pipe(process.stdout)

并且时光机已经准备好了!你可以在任何阶段管道进程:启动时,中间,甚至结束后。所有的 chunk 都会被缓冲,并以正确的顺序处理。

js
const result = $`echo 1; sleep 1; echo 2; sleep 1; echo 3`
const piped1 = result.pipe`cat`
let piped2

setTimeout(() => { piped2 = result.pipe`cat` }, 1500)
  
(await piped1).toString()  // '1\n2\n3\n'
(await piped2).toString()  // '1\n2\n3\n'

pipe() 方法可以组合 $ 进程。和 bash 中的 | 一样

js
const greeting = await $`printf "hello"`
  .pipe($`awk '{printf $1", world!"}'`)
  .pipe($`tr '[a-z]' '[A-Z]'`)

echo(greeting)

使用 pipe()nothrow() 的组合

js
await $`find ./examples -type f -print0`
  .pipe($`xargs -0 grep ${'missing' + 'part'}`.nothrow())
  .pipe($`wc -l`)

还有字面量!Pipe 也支持它们

js
await $`printf "hello"`
  .pipe`awk '{printf $1", world!"}'`
  .pipe`tr '[a-z]' '[A-Z]'`

默认情况下,pipe() API 操作的是 stdout 流,但是你也可以指定 stderr

js
const p = $`echo foo >&2; echo bar`
const o1 = (await p.pipe.stderr`cat`).toString()  // 'foo\n'
const o2 = (await p.pipe.stdout`cat`).toString()  // 'bar\n'

顺便说一句,如果指定了信号,它将会通过管道传输。

js
const ac = new AbortController()
const { signal } = ac
const p = $({ signal, nothrow: true })`echo test`.pipe`sleep 999`
setTimeout(() => ac.abort(), 50)

try {
  await p
} catch ({ message }) {
  message // The operation was aborted
}

简而言之,组合任何你想要的东西

js
const getUpperCaseTransform = () => new Transform({
  transform(chunk, encoding, callback) {
    callback(null, String(chunk).toUpperCase())
  },
})

// $ > stream (promisified) > $
const o1 = await $`echo "hello"`
  .pipe(getUpperCaseTransform())
  .pipe($`cat`)

o1.stdout //  'HELLO\n'

// stream > $
const file = tempfile()
await fs.writeFile(file, 'test')
const o2 = await fs
  .createReadStream(file)
  .pipe(getUpperCaseTransform())
  .pipe($`cat`)

o2.stdout //  'TEST'

kill()

杀死进程和所有子进程。

默认情况下,会发送 SIGTERM 信号。你可以通过参数指定一个信号。

js
const p = $`sleep 999`
setTimeout(() => p.kill('SIGINT'), 100)
await p

abort()

通过 AbortController 信号终止进程。

js
const ac = new AbortController()
const {signal} = ac
const p = $({signal})`sleep 999`

setTimeout(() => ac.abort('reason'), 100)
await p

如果没有提供 acsignal,它将会自动创建,并且可以用来控制外部进程。

js
const p = $`sleep 999`
const {signal} = p

const res = fetch('https://example.com', {signal})
p.abort('reason')

stdio()

为进程指定标准输入输出。

js
const h$ = $({halt: true})
const p1 = h$`read`.stdio('inherit', 'pipe', null).run()
const p2 = h$`read`.stdio('pipe').run() // sets ['pipe', 'pipe', 'pipe']

请记住,stdio 应该在进程启动之前设置,所以预设语法可能更可取

js
await $({stdio: ['pipe', 'pipe', 'pipe']})`read`

nothrow()

改变 $ 的行为,使其在非零退出码时不会抛出异常。等价于 $({nothrow: true}) 选项

js
await $`grep something from-file`.nothrow()

// Inside a pipe():

await $`find ./examples -type f -print0`
  .pipe($`xargs -0 grep something`.nothrow())
  .pipe($`wc -l`)

如果只需要 exitCode,你可以直接使用 exitCode

js
if (await $`[[ -d path ]]`.exitCode == 0) {
...
}

// Equivalent of:

if ((await $`[[ -d path ]]`.nothrow()).exitCode == 0) {
...
}

quiet()

改变 $ 的行为,启用抑制模式。

js
// Command output will not be displayed.
await $`grep something from-file`.quiet()

$.quiet = true
await $`echo foo`.quiet(false) // Disable for the specific command

verbose()

启用详细输出。传递 false 来禁用。

js
await $`grep something from-file`.verbose()

$.verbose = true
await $`echo foo`.verbose(false) // Turn off verbose mode once

timeout()

在指定的超时时间后杀死进程。

js
await $`sleep 999`.timeout('5s')

// Or with a specific signal.
await $`sleep 999`.timeout('5s', 'SIGKILL')

免责声明:这不是 Google 官方支持的产品。