# Shell Scripting

You can also use the [`rex` CLI](/content/rex/docs/automate/cli/index.html) with typical shell scripts to automate everything in Rex:

```
#!/bin/sh
rex new work
rex split -s work -- htop
rex run -s work --label logs -- tail -f app.log
```

For anything beyond a few commands in a row, consider a [Lua script](/content/rex/docs/automate/lua-scripts/index.html) instead. Lua can do everything on this page with one connection to the server and without parsing JSON.

## Keeping Track of IDs

Commands that create something print its ID with `--json`. Save the ID and pass it to later commands, so the script keeps working when a label changes or focus moves.

```
$ rex split --json -- htop
{
  "anchor_block_id": "block:01a10e00-7268-78c6-8b85-86c56d6a53a9",
  "block_ids": [
    "block:01a10e00-729d-7c38-90f7-d6568611b1c4"
  ],
  "split_ids": [
    "split:01a10e00-72ab-7157-b9e0-0d4c2a8a1a68"
  ],
  "revision": 2
}
```

[`jq`](https://jqlang.org/) is the easiest way to pull the ID out:

```
block=$(rex split --json -- htop | jq -r '.block_ids[0]')
rex zoom -b "$block"
```

| Command             | Creates                                            | ID is at                  |
|---------------------|----------------------------------------------------|---------------------------|
| `rex new --json`    | A session with one window and one terminal         | `.session_id`            |
| `rex run --json`    | A terminal in a new window                         | `.block_ids[0]`, `.window_id` |
| `rex split --json`  | A terminal beside an existing one                  | `.block_ids[0]`         |

A label is a fine alternative when the script sets it. Give the block a label with `--label` and use that as the target:

```
rex split --label logs -- tail -f app.log
rex capture -b logs
```

## Building a Layout

`rex new` creates a session, `rex run` adds a window to it, and `rex split` splits an existing block.

```
#!/bin/sh
# Open a project: an editor with a shell below it, and a second
# window that follows the logs.
rex new api --cwd ~/src/api -- nvim
rex split -s api --split=below --ratio 30 --cwd ~/src/api
rex run -s api --label logs --focus=false -- tail -f ~/src/api/app.log
```

These options are shared by `rex run` and `rex split`:

| Option                | Does                                                      |
|-----------------------|-----------------------------------------------------------|
| `-- COMMAND`        | Runs a command instead of an interactive shell.          |
| `--cwd DIR`        | Sets the working directory.                               |
| `--label TEXT`     | Labels the new block.                                    |
| `--split DIRECTION` | Places the block `right`, `left`, `above`, or `below` its anchor. |
| `--ratio PCT`      | Gives the new block that percentage of the split.        |
| `--focus=false`     | Leaves focus where it was.                               |
| `--keep-open`       | Keeps the terminal open after its command exits.         |

A command runs with the environment of your login shell, so your `PATH` applies but aliases don’t. It isn’t parsed by a shell. To use pipes or `&&`, run a shell explicitly:

```
rex run -- sh -c 'make build && make test'
```

## Typing Into a Terminal

`rex send` types text into a terminal, and `rex send-key` presses keys:

```
rex send -b logs 'make test'
rex send-key -b logs enter
```

`rex send` doesn’t press Enter for you. Follow it with `rex send-key enter`, or include the newline in the text. It also reads from stdin:

```
printf 'make test\n' | rex send -b logs
```

`rex send-key` uses the same key names as [keyboard shortcuts](/content/rex/docs/customize/keyboard-shortcuts#writing-keys/index.html), so `rex send-key ctrl+c` interrupts whatever is running.

## Reading a Terminal

`rex capture` prints a terminal’s screen and its scrollback:

```
$ rex capture -b logs --trim
hello from logs
```

| Option                | Does                                               |
|-----------------------|----------------------------------------------------|
| `--trim`            | Removes trailing blank lines.                       |
| `--unwrap`          | Joins lines the terminal wrapped.                   |
| `--format html`     | Keeps colors and styling, as HTML.                 |
| `--format vt`       | Keeps the terminal’s control sequences.             |

A terminal keeps about 64 MiB of scrollback. That is roughly 100,000 lines of plain text at 80 columns, and fewer in a wider terminal or with styled output. Once the limit is reached, the oldest lines are dropped. To keep everything a long command prints, redirect its output to a file.

Full-screen programs, such as editors, pagers, and many TUI apps, draw on a separate screen that has no scrollback. While one is running, `rex capture` returns only what that program shows right now. Any history it has, like an editor’s file or a chat app’s earlier messages, stays inside the program.

## Running a Command and Waiting

Add `--wait` to `rex run` or `rex split` to wait for the command to finish. The `rex` command then exits with the command’s own status:

```
if rex run --wait -- make test; then
  echo "tests passed"
fi
```

`rex wait` does the same for a terminal that is already running, given its ID or label:

```
block=$(rex split --json -- make test | jq -r '.block_ids[0]')
# ... do other things ...
rex wait "$block"
echo "make exited with $?"
```

`rex wait` also accepts a window or a session, and returns when it closes.

Use `--for` to give up after a while. A wait that times out exits with status 1:

```
rex wait --for 10m "$block" || echo "failed or timed out"
```

## Performing Actions

`rex do` performs an action by name, with arguments as `key=value` pairs. It runs both the app’s actions and the [custom actions](/content/rex/docs/customize/actions/index.html) in your configuration file:

```
rex do dev_layout file=app.log
rex do pane.split direction=right
```

Actions such as `pane.split` are performed by the app, so the app must be running, with Remote Control turned on in its settings.

## Watching Events

`rex events` prints events as they happen and runs until interrupted. With `-s` it follows one session:

```
$ rex events -s work
14:38:07  block_created         …65951c  session …8f9d5d
14:38:07  session_view_changed  …8f9d5d  revision 11
14:38:07  block_event           …65951c  com.superlogical.terminal.bell {}
```

With `--json` it prints one JSON object per line, ready for `jq`. This prints the ID of each terminal that rings its bell:

```
rex events -s work --json |
  jq -r 'select(.payload.event.payload.name == "bell")
         | .payload.event.payload.block_id'
```

The [events reference](/content/rex/docs/reference/events#events-on-the-cli/index.html) describes the JSON and lists every event. To act on events rather than print them, [Lua event handlers](/content/rex/docs/automate/lua-events/index.html) are much less work than a pipeline.

## Example: Run Tests in a Split

This script runs a command in a split beside the current terminal. If the command fails, it zooms the split and exits with a failure.

```
#!/bin/sh
# tests.sh
cmd="${1:-make test}"

block=$(rex split --keep-open --focus=false --json -- sh -c "$cmd" |
  jq -r '.block_ids[0]')

if ! rex wait --for 10m "$block"; then
  rex zoom -b "$block" --on
  exit 1
fi
```

```
./tests.sh 'go test ./...'
```

The [same script in Lua](/content/rex/docs/automate/lua-scripts#example-run-tests-in-a-split/index.html) is a useful comparison.
