Skip to content

Commit 7ad4f5a

Browse files
committed
fix: update documentation and examples to improve clarity on @shell usage with zsh
1 parent 05d8d54 commit 7ad4f5a

6 files changed

Lines changed: 59 additions & 4 deletions

File tree

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ Shellflow also recognizes bounded block directives at the top of a block body:
9191
- `# @TIMEOUT <seconds>`
9292
- `# @RETRY <count>`
9393
- `# @EXPORT NAME=stdout|stderr|output|exit_code`
94+
- `# @SHELL <shell>` - Specify the shell to use (e.g., `zsh`, `bash`)
9495

9596
`<ssh-host>` must match a `Host` entry in your SSH config. Shellflow then connects using that SSH host definition, which means the actual machine can be resolved through the configured `HostName`, `User`, `Port`, and `IdentityFile` values.
9697

@@ -112,6 +113,22 @@ echo "remote output: $SHELLFLOW_LAST_OUTPUT"
112113
echo "version = $VERSION"
113114
```
114115

116+
Using `@SHELL` for remote servers with non-bash default shells:
117+
118+
```bash
119+
#!/bin/bash
120+
121+
# @REMOTE zsh-server
122+
# @SHELL zsh
123+
# zsh-specific commands work here
124+
reload
125+
compdef
126+
127+
# @REMOTE bash-server
128+
# Default bash shell is used
129+
ls -la
130+
```
131+
115132
## SSH Configuration
116133

117134
Example `~/.ssh/config` entry:

SKILL.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,22 @@ You can use multiple exports in a single block:
167167
curl -s -w "%{http_code}" -o response.txt https://api.example.com
168168
```
169169

170+
### Shell Directive
171+
172+
`# @SHELL <shell>` - Specify the shell to use for executing this block.
173+
174+
Use this when targeting remote hosts that use a non-bash default shell (e.g., zsh).
175+
176+
```bash
177+
# @REMOTE zsh-server
178+
# @SHELL zsh
179+
# zsh-specific commands now work
180+
reload
181+
compdef
182+
```
183+
184+
Without `@SHELL`, Shellflow defaults to `bash` for all remote blocks.
185+
170186
## 5. Assume every block runs in a fresh shell
171187

172188
Each block is isolated.
@@ -351,7 +367,7 @@ Shellflow returns distinct exit codes for different failure types:
351367
Before returning a Shellflow playbook, verify that:
352368

353369
- The script is valid bash without custom DSL syntax.
354-
- Only `# @LOCAL`, `# @REMOTE <host>`, and block directives (`# @TIMEOUT`, `# @RETRY`, `# @EXPORT`) are used.
370+
- Only `# @LOCAL`, `# @REMOTE <host>`, and block directives (`# @TIMEOUT`, `# @RETRY`, `# @EXPORT`, `# @SHELL`) are used.
355371
- Block directives appear immediately after the block marker, before any commands.
356372
- Anything before the first marker is safe to repeat for every block.
357373
- Every block can run independently in a fresh shell.
@@ -399,6 +415,7 @@ log "build $BUILD_ID complete"
399415
- Using an undefined remote host alias.
400416
- Placing block directives after commands instead of immediately after the marker.
401417
- Using invalid export sources (not stdout, stderr, output, or exit_code).
418+
- Forgetting that `@SHELL` must be specified before any commands in the block.
402419
- Forgetting that `@RETRY 0` means no retry attempts.
403420
- Using `@TIMEOUT` with values too small for normal operation.
404421
- Printing extra debug output from a block whose output is consumed by the next block via `@EXPORT`.

examples/zsh_remote.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/bash
2+
# Example: Using @SHELL directive for remote servers with zsh
3+
4+
# This script demonstrates how to use the @SHELL directive
5+
# to execute commands on remote servers that use zsh as their default shell.
6+
7+
# @REMOTE zsh-server
8+
# @SHELL zsh
9+
# Now zsh-specific commands will work
10+
echo "Running in zsh on remote server"
11+
12+
# @REMOTE another-server
13+
# Default bash shell will be used (no @SHELL directive)
14+
echo "Running in bash on another server"

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "shellflow"
3-
version = "0.2.0"
3+
version = "0.2.1"
44
description = "A minimal shell script orchestrator with SSH support"
55
readme = "README.md"
66
license = "Apache-2.0"

src/shellflow.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class Block:
4646
timeout_seconds: int | None = None
4747
retry_count: int = 0
4848
exports: dict[str, str] = field(default_factory=dict)
49+
shell: str | None = None # Shell to use for execution (e.g., "zsh", "bash")
4950

5051
@property
5152
def is_local(self) -> bool:
@@ -501,6 +502,11 @@ def _apply_block_directive(block: Block, marker_name: str, marker_argument: str
501502
export_name, export_source = _parse_export_directive(marker_argument, line_no=line_no)
502503
block.exports[export_name] = export_source
503504
return
505+
if marker_name == "SHELL":
506+
if not marker_argument:
507+
raise ParseError(f"Line {line_no}: @SHELL requires a shell name (e.g., zsh, bash)")
508+
block.shell = marker_argument
509+
return
504510
raise ParseError(f"Line {line_no}: Unknown marker @{marker_name}")
505511

506512

@@ -890,7 +896,8 @@ def execute_remote(
890896
if ssh_config_path.exists():
891897
ssh_args.extend(["-F", str(ssh_config_path)])
892898

893-
ssh_args.extend(["-o", "BatchMode=yes", host, "bash", "-se"])
899+
shell = block.shell or "bash"
900+
ssh_args.extend(["-o", "BatchMode=yes", host, shell, "-se"])
894901
remote_script = _build_executable_script(
895902
block.commands,
896903
context,

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)