# 10 · Automate Commands and System Tasks

English narration · HHY 1.7.0

Capture child stdout/stderr and interpret exit codes.

## 00:00:00.000 — Run a small command report

This lesson connects HHY to ordinary command line tools. Our script runs a few harmless local commands, captures their outputs, and writes a structured report. The supplied example targets macOS and Linux with printf, cat, and sh available. Run it from the examples directory with the environment variable shown. We intentionally include a command that exits with code seven, so success and failure handling are both visible.

```
HHY_COURSE_LABEL=local-course \
  hhy run command-report.hhy demo
```

## 00:00:33.340 — Check script arguments early

Args contains the arguments passed after the script filename. We require exactly one label before reading its first element. An invalid invocation prints usage to standard error and exits with code three. Checking inputs early gives callers a predictable contract and prevents an unrelated index failure later. The label here is only descriptive text; we do not splice it into a shell command or use it as an arbitrary output path.

```
if length(args) != 1 {
    print_error("usage: hhy run command-report.hhy <label>")
    exit(3)
}
print("report: " + args[0])
```

## 00:01:06.060 — Read an explicit environment setting

Require_env reads a required environment variable and raises an error when the setting is unavailable. Our teaching value is a harmless label, so printing it is appropriate. Real scripts should avoid printing secrets just to prove that configuration was loaded. The shell prefix shown earlier sets the variable for that one invocation. It does not require you to edit a global shell configuration file or permanently change your environment.

```
print(require_env("HHY_COURSE_LABEL"))
```

Expected output:
```text
local-course
```

## 00:01:38.160 — Prefer an argument vector

Run receives a List containing the executable and its separate arguments. This makes argument boundaries explicit and avoids asking a shell to reinterpret ordinary input text. We set a two second timeout because external tools should have a bounded opportunity to finish. The returned CommandResult contains an exit code and captured output. A successfully launched process can still report an unsuccessful exit status, so inspect that field deliberately.

```
let result = run(["printf", "alpha\nbeta\n"],
    { timeout: 2s })
print(result.exit_code)
```

Expected output:
```text
0
```

## 00:02:12.270 — Transform captured standard output

Stdout_lines exposes the captured standard output as a line stream. We uppercase each line and print the result using the same flow tools used for files. The distinction matters: this stream is derived from captured CommandResult output, rather than being a promise of live terminal streaming during execution. Choose appropriate output limits for commands that may produce large responses, and do not assume captured output can grow without bounds.

```
result
    |> stdout_lines
    |> map { line -> upper(line) }
    |> print
```

Expected output:
```text
ALPHA
BETA
```

## 00:02:46.400 — Supply standard input to a child

The stdin option supplies text to the child process. Cat echoes our input, making this a simple way to check that the input and output connection works. This is different from stdin_lines in an HHY script, which reads input supplied to HHY itself. The download includes a separate stdin script so you can try both directions. Keeping these two boundaries clear helps when connecting several command line tools together.

```
let echoed = run(["cat"], {
    stdin: "hello child\n", timeout: 2s
})
echoed.stdout |> trim |> print
```

Expected output:
```text
hello child
```

## 00:03:18.590 — A nonzero exit is data to inspect

For a controlled failure fixture, we explicitly invoke sh with a fixed command string. It writes a message to standard error and exits with seven. Run returns that result instead of automatically treating every nonzero status as a thrown HHY error. Interpret exit codes according to the tool's contract. This constant shell snippet is only a demonstration; avoid constructing shell source by concatenating untrusted labels or other external input.

```
let failed = run(["sh", "-c",
    "printf 'demo failure' >&2; exit 7"])
print(failed.exit_code)
print(failed.stderr)
```

Expected output:
```text
7
demo failure
```

## 00:03:53.790 — Save an ordinary data report

Project the fields you need into an ordinary Map before JSON encoding. Our report records the user's label, whether the first command succeeded, and the deliberate failure's exit code. It does not claim every command succeeded just because the HHY script reached its end. Open the generated report and check those three values. For a real automation job, decide whether any recorded failure should also make the overall script exit nonzero.

```
let report = {
    label: args[0],
    ok: result.exit_code == 0,
    failure_code: failed.exit_code
}
report |> encode_json({ pretty: true })
    |> save_text(path("out/report.json"),
        { create_parents: true })
```

## 00:04:28.010 — Practice both input channels

First pipe two lines into stdin dot HHY and verify uppercase output. Then run command-report without a label and confirm the usage message and exit code three. Finally, rerun the documented successful invocation and inspect the JSON report. These checks exercise argument validation, environment configuration, process capture, and failure interpretation. You now have the building blocks for repeatable system tasks, with explicit inputs and outcomes rather than assumptions about command success.

```
printf 'one\ntwo\n' | hhy run stdin.hhy
hhy run command-report.hhy
```

