Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions Sources/ContainerCommands/Machine/MachineRun.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ extension Application {

public static let configuration = CommandConfiguration(
commandName: "run",
abstract: "Run a command or interactive shell in a container machine, booting the container machine if necessary"
abstract: "Run a command or interactive shell in a container machine, booting the container machine if necessary",
discussion: """
When <executable> is provided, the command and arguments are joined into a
single string and evaluated by the guest user's shell (shell -c "$*"),
including expansion, substitution, and redirection. This is not argv-preserving.
"""
)

@OptionGroup
Expand All @@ -47,10 +52,10 @@ extension Application {
@Flag(name: .long, help: "Run as root instead of matching host user")
var root: Bool = false

@Argument(help: "Command to run (default: login shell)")
@Argument(help: "Command evaluated by the guest shell (default: login shell)")
var executable: String?

@Argument(parsing: .captureForPassthrough, help: "Command arguments")
@Argument(parsing: .captureForPassthrough, help: "Words joined with the command and evaluated via shell -c")
var arguments: [String] = []

public func run() async throws {
Expand Down
9 changes: 7 additions & 2 deletions docs/command-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1125,6 +1125,8 @@ container machine create --virtualization --kernel ./vmlinux-kvm alpine:3.22

Runs a command in a container machine, booting it first if needed. With no command, it opens an interactive login shell. By default the command runs as a user matching the host user.

When a command is provided, `<executable>` and `<arguments>` are joined into a single string and evaluated by the execution user's shell inside the guest (`shell -c "$*"`). Expansion, substitution, and redirection apply. This differs from argv-preserving interfaces such as `docker exec`.

**Usage**

```bash
Expand All @@ -1133,8 +1135,8 @@ container machine run [<options>] [<executable>] [<arguments> ...]

**Arguments**

* `<executable>`: Command to run (default: login shell)
* `<arguments>`: Command arguments
* `<executable>`: Command evaluated by the guest shell (default: login shell)
* `<arguments>`: Additional words joined into the shell command string

**Options**

Expand Down Expand Up @@ -1164,6 +1166,9 @@ container machine run -n my-machine uname -a

# pass arguments to the command after --
container machine run -n my-machine -- cat /proc/cpuinfo

# shell evaluation: quote a script string when you need spaces or substitution
container machine run -n my-machine -- 'printf ":%s:" "one two three"'
```

### `container machine list (ls)`
Expand Down
13 changes: 13 additions & 0 deletions docs/container-machine.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,19 @@ container machine run -n dev uname -a
container machine run -n dev -- cat /proc/cpuinfo
```

Commands are **not** argv-preserving. The guest joins `<executable>` and `<arguments>` into one string and evaluates it with the execution user's shell:

```sh
exec "${USER_SHELL:-${SHELL}}" -c "$*"
```

That shell performs expansion, substitution, and redirection. For example, `printf ':%s:' 'one two three'` becomes three words after joining, and `$(...)` inside an argument is evaluated in the guest. Quote a single shell script string when you need spaces or metacharacters preserved as one argument to the guest shell:

```bash
container machine run -n dev -- 'printf ":%s:" "one two three"'
container machine run -n dev -- 'echo "uid=$(id -u)"'
```

### Set a default

Pick a default container machine so you can drop the `-n` flag:
Expand Down