Repository navigation
Pipeline Splits
A split sends the same upstream items to more than one path. The source runs once. Each path can transform, encode, or collect its own results.
Place \- on the next line, with its backslash directly below the pipe you
want to tap. It receives the output of the stage immediately to that pipe's left.
The hyphen points toward the new branch's first stage.
let $main: [Integer]
let $extra: [Integer]
[2,4] | ($ * 10) | $main
\- ($ + 1) | $extra
echo $main...
echo $extra...
20 40
3 5
Here both paths receive 2 and 4. Multiplication belongs only to the upper
path; the lower path adds one to each original item. Branches use the same bare
$, automatic function argument, and destination rules as ordinary pipelines.
When a branch reaches past another diagram row, draw a vertical | on every
intervening row. That guide continues an existing connection; it executes nothing.
A branch's own real pipes can supply further branches. Each pipe supplies one
continuation and one branch; add another stage and junction for further paths.
fn command($n: Integer): Integer { $n + 1 }
let $main: [Integer]
let $other: [Integer]
let $secondary: [Integer]
let $early: [Integer]
[1, 2] | command | command | command | $main
| \- command | $other
| \- command | command | $secondary
\- $early
echo $main...
echo $other...
echo $secondary...
echo $early...
4 5
3 4
5 6
1 2
The leftmost guide carries the early connection past both deeper branches. The secondary branch attaches to the real pipe on the preceding branch row. Shelly follows these drawn paths rather than searching arbitrarily far upward for a matching column.
Misaligned connectors, missing guides, and unfinished guides are compile errors. A blank or comment-only row cannot replace a required guide. Diagnostics identify the broken connection and report the effective tab size. They suggest a tab-size directive when hard tabs occur in the relevant diagram prefixes.
Put #tab-size N in the file header, before executable code. Blank lines,
comments, and a shebang can precede it. Use one directive, with an integer from
1 through 32. This example contains a hard tab before the backslash:
#tab-size 8
let $main: [Integer]
let $copy: [Integer]
[1, 2] | $main
\- $copy
echo $main... $copy...
1 2 1 2
The precedence is:
- The current source's
#tab-size Ndirective. - An explicit
--tab-width N/-t Noption. -
$SHELLY_TAB_SIZE, inherited from the environment or set by startup scripts. - Four-column tab stops.
Set $SHELLY_TAB_SIZE = 8 in your RC to select eight-column stops for later
submissions and loaded sources. Export it if child Shelly processes should inherit
it. A file directive changes only that source; it does not rewrite the variable.
Each loaded module can choose its own directive.
Tabs advance to the next tab stop. Layout also accounts for Unicode display width. Source bytes stay unchanged: tabs inside strings, whitespace, and editor spans are preserved. Use a matching tab width in the editor when drawing diagrams.
The same notation can tap a byte stream. Here printf runs once: one path keeps
all its output, while the other finishes after its first line.
let $all: String
let $first: String
printf "north\nsouth\n" | cat | $all
\- head -n 1 | $first
echo $first.trim
echo ($all == "north\nsouth\n")
north
true
The early-ending branch detaches; it does not prevent the other path from receiving the remaining output. No intermediate array of lines is needed.
A fork broadcasts one item or byte chunk at a time, with bounded queues. A slow consumer applies backpressure; an early-ending consumer detaches while other consumers continue. Cooperative workers allow sibling I/O operations to progress. Execution order between sibling callbacks is not a synchronization guarantee.
Typed values retain their types and ordinary value-copy semantics. External
programs retain byte streams. A branch connector accepts the same boundary
annotations as a pipe: \-:json encodes, csv:\- decodes, and
json:\-:Reply decodes into Reply. See pipeline formats.
A fork at the source cannot mix whole-collection encoding with item iteration;
put an explicit stage before that fork when those interpretations would differ.
A destination must already exist. Use different destination bindings for different leaves; assigning two leaves to the same binding is rejected before stages run. Final array destinations collect their path's items. Intermediate paths stay streaming. Live resources must be adapted to transferable items or bytes before broadcasting; retained-data and VM-task limits produce errors rather than unbounded buffering. Native calls in callbacks still need a managed suspension contract. Put external programs directly in pipeline stages; ordinary external calls inside a scripted callback are not admitted by that existing managed-task policy. Split pipelines are unavailable during error reporting and cleanup.
An unhandled failure stops the whole graph and cleans up its children. A graph
containing external programs returns aggregate ExecResult status, which can be
captured using the usual rules. Pure Shelly graphs return the main path's final
value; side paths do not replace it. Ctrl+C interrupts the graph and returns an
interactive shell to its editor. An external source reading terminal input gets
foreground ownership for its lifetime; ownership returns to Shelly on EOF, error,
or interruption. Concurrent sources cannot both own the same terminal input.
Related: Pipelines · Pipeline formats · REPL · Startup
Begin here
Values and expressions
- Language basics
- Variables and scope
- Numbers and logic
- Strings and paths
- Arrays
- Hash maps
- Ranges and iteration
- Control flow
- Match and type guards
Functions and types
- Functions and calls
- Closures and function types
- Methods and properties
- Structs
- Enums
- Named types and unions
- Conversions
- Types and properties
Shell and modules
- Pipelines
- Pipeline splits
- Pipeline formats
- Commands and exit status
- Redirection
- Environment and exported variables
- Modules and visibility
- Nested modules and directory packages
- Conditional imports and declarations
- Errors and cleanup
- Supervised processes and terminals
Library guides
- Standard library
- The implicit prelude
- Bytes, files, readers, and writers
- Explicit item streams
- JSON documents and typed mapping
- Sockets, DNS, and TLS
- HTTP clients
- HTTP servers
- Framing, server-sent events, and JSON-RPC
- Terminal colors, styles, and geometry
- Build an application console
Interactive use