已发布 上游基线 bf46254 原文 ↗ 在 GitHub 编辑

Command

了解如何在 Effect 中创建、运行和管理命令,包括自定义参数、环境变量以及输入/输出的处理。

@effect/platform/Command 模块提供了一种创建并运行命令的方式,你可以在其中指定进程名以及一个可选的参数列表。

创建命令

Command.make 函数会生成一个命令对象,其中包含进程名、参数以及环境等细节。

示例(为目录列表定义一个命令)

import { Command } from "@effect/platform"

const command = Command.make("ls", "-al")
console.log(command)
/*
{
  _id: '@effect/platform/Command',
  _tag: 'StandardCommand',
  command: 'ls',
  args: [ '-al' ],
  env: {},
  cwd: { _id: 'Option', _tag: 'None' },
  shell: false,
  gid: { _id: 'Option', _tag: 'None' },
  uid: { _id: 'Option', _tag: 'None' }
}
*/

该命令对象在被执行器(executor)运行之前不会真正执行。

运行命令

运行命令需要一个 CommandExecutor,它能够以字符串、行或流等多种格式捕获输出。

示例(运行命令并打印输出)

import { Command } from "@effect/platform"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Effect } from "effect"

const command = Command.make("ls", "-al")

// The program depends on a CommandExecutor
const program = Effect.gen(function* () {
  // Runs the command returning the output as a string
  const output = yield* Command.string(command)
  console.log(output)
})

// Provide the necessary CommandExecutor
NodeRuntime.runMain(program.pipe(Effect.provide(NodeContext.layer)))

输出格式

你可以选择不同的方法来处理命令的输出:

方法说明
string运行命令,并以字符串形式返回输出(使用指定的编码)
lines运行命令,并以行的数组形式返回输出(使用指定的编码)
stream运行命令,并以 Uint8Array 数据块组成的流形式返回输出
streamLines运行命令,并以行的流形式返回输出(使用指定的编码)

exitCode

如果你只需要命令的退出码,请使用 Command.exitCode

示例(获取退出码)

import { Command } from "@effect/platform"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Effect } from "effect"

const command = Command.make("ls", "-al")

const program = Effect.gen(function* () {
  const exitCode = yield* Command.exitCode(command)
  console.log(exitCode)
})

NodeRuntime.runMain(program.pipe(Effect.provide(NodeContext.layer)))
// Output: 0

自定义环境变量

你可以使用 Command.env 自定义命令的环境变量。当你需要为命令的执行指定特定的变量时,这很有用。

示例(设置环境变量)

在这个例子中,命令在一个 shell 中运行,以确保环境变量被正确处理。

import { Command } from "@effect/platform"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Effect } from "effect"

const command = Command.make("echo", "-n", "$MY_CUSTOM_VAR").pipe(
  Command.env({
    MY_CUSTOM_VAR: "Hello, this is a custom environment variable!",
  }),
  // Use shell to interpret variables correctly
  // on Windows and Unix-like systems
  Command.runInShell(true),
)

const program = Effect.gen(function* () {
  const output = yield* Command.string(command)
  console.log(output)
})

NodeRuntime.runMain(program.pipe(Effect.provide(NodeContext.layer)))
// Output: Hello, this is a custom environment variable!

向命令提供输入

你可以使用 Command.feed 函数把输入直接发送到命令的标准输入。

示例(将输入发送到命令的标准输入)

import { Command } from "@effect/platform"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Effect } from "effect"

const command = Command.make("cat").pipe(Command.feed("Hello"))

const program = Effect.gen(function* () {
  console.log(yield* Command.string(command))
})

NodeRuntime.runMain(program.pipe(Effect.provide(NodeContext.layer)))
// Output: Hello

获取进程详情

你可以访问正在运行的进程的详细信息,例如 exitCodestdoutstderr

示例(访问正在运行进程的退出码与流)

import { Command } from "@effect/platform"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Effect, Stream, String, pipe } from "effect"

// Helper function to collect stream output as a string
const runString = <E, R>(
  stream: Stream.Stream<Uint8Array, E, R>,
): Effect.Effect<string, E, R> =>
  stream.pipe(Stream.decodeText(), Stream.runFold(String.empty, String.concat))

const program = Effect.gen(function* () {
  const command = Command.make("ls")

  const [exitCode, stdout, stderr] = yield* pipe(
    // Start running the command and return a handle to the running process
    Command.start(command),
    Effect.flatMap((process) =>
      Effect.all(
        [
          // Waits for the process to exit and returns
          // the ExitCode of the command that was run
          process.exitCode,
          // The standard output stream of the process
          runString(process.stdout),
          // The standard error stream of the process
          runString(process.stderr),
        ],
        { concurrency: 3 },
      ),
    ),
  )
  console.log({ exitCode, stdout, stderr })
})

NodeRuntime.runMain(
  Effect.scoped(program).pipe(Effect.provide(NodeContext.layer)),
)

将 stdout 流式传输到 process.stdout

要把命令的 stdout 直接流式传输到 process.stdout,可以采用下面的做法:

示例(将命令输出直接流式传输到标准输出)

import { Command } from "@effect/platform"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Effect } from "effect"

// Create a command to run `cat` on a file and inherit stdout
const program = Command.make("cat", "./some-file.txt").pipe(
  Command.stdout("inherit"), // Stream stdout to process.stdout
  Command.exitCode, // Get the exit code
)

NodeRuntime.runMain(program.pipe(Effect.provide(NodeContext.layer)))