Pipes and Chains
POSIX Pipes
Section titled “POSIX Pipes”lash supports standard POSIX pipes. Nothing new here:
cat /var/log/syslog | grep ERROR | sort | uniq -c | sort -rn | head -20Pipes connect stdout of one process to stdin of the next as byte streams. Redirections work as expected:
`ls -la` > files.txt`ls -la` >> log.txt`cat < input.txt`.sortThe Lambda Bridge
Section titled “The Lambda Bridge”The real power of lash is the transition from byte-stream pipelines to functional method chains. Wrap a command in backticks and follow it with a dot to enter functional mode:
`ls -la`.sort()`grep -r "error" src/`.filter(x => x.length > 0).take(10)`cat access.log`.sort().unique().reverse()Backticks do NOT perform POSIX command substitution. Use $() for that. The backtick-dot operator converts the command’s stdout into a line-stream that methods operate on.
POSIX pipes inside backticks work normally:
`cat server.log | head -1000`.filter(x => x.contains("WARN"))A chain’s output can feed into a POSIX pipe:
`ls -la`.filter(x => x.contains(".d")).sort() | wc -lMethod Chaining
Section titled “Method Chaining”Parentheses are optional when a method takes no arguments:
`ls`.sort # equivalent to `ls`.sort()`ls`.sort.take(5) # mix freelyLambda Syntax
Section titled “Lambda Syntax”Methods like map and filter accept lambdas:
# explicit single-param`ls`.filter(x => x.endsWith(".log"))
# multi-param (for reduce)[1, 2, 3, 4].reduce(0, (acc, x) => acc + x) # 10
# implicit lambda -- the parameter is always `a`[1, 2, 3].map(a + 10) # [11, 12, 13][1, 2, 3, 4].filter(a > 2) # [3, 4]`ls`.map("-> ${a}") # string interpolation in implicit lambdaWhen a method receives a non-lambda expression, it wraps it as a => <expr>. The implicit a shadows any outer variable with that name.
Stream Selection
Section titled “Stream Selection”By default, backtick captures stdout. Use stream methods to change this:
`make`.stderr > errors.txt`make`.merge.filter(x => x.contains("error"))
let result = `make`.captureresult["exitCode"]result["stdout"]result["stderr"]Pipe vs Chain: Side by Side
Section titled “Pipe vs Chain: Side by Side”Find the 5 largest .log files:
# POSIX pipe approachls -la *.log | sort -k5 -rn | head -5
# functional chain approach`ls -la *.log`.sort().reverse().take(5)Extract unique error codes from a log:
# POSIX pipe approachgrep "ERROR" server.log | cut -d: -f2 | sort | uniq
# functional chain approach`grep "ERROR" server.log`.map(x => x.split(":")[1]).sort().unique()Count word frequencies across files:
# functional chain approach (no POSIX equivalent this clean)`cat *.md`.wordcount().take(20)List Methods
Section titled “List Methods”| Method | Description |
|---|---|
sort | String-based sort |
sortNumeric | Numeric sort |
map(fn) | Transform each element |
filter(fn) | Keep elements matching predicate |
grep(pattern) | Regex-powered filter (no process spawn) |
take(n) | First N elements |
drop(n) | Skip first N elements |
join(sep?) | Concatenate into single string |
collect | Capture lazy stream into static list |
length | Number of elements |
json | Parse each element as JSON (fails on invalid) |
jsonl | Parse each element as JSON (skips invalid) |
buffer(n) | Set bounded buffer size |
unique | Remove duplicates, preserve order |
reverse | Reverse element order |
flatten | Flatten one level of nesting |
sum | Sum numeric elements |
wordcount | Word frequencies, sorted by count descending |
each(fn) | Like map but returns original list (side effects) |
reduce(init, fn) | Aggregate into single value |
last(n) | Last N elements |
first | First element (unwrapped) |
empty | True if list has no elements |
column(n, sep?) | Extract Nth field (1-indexed) |
stdout | Select stdout stream (default) |
stderr | Select stderr stream |
merge | Combine stdout and stderr |
capture | Return object with stdout, stderr, exitCode |
extract(pattern) | Pattern matching with # and * wildcards |
String Methods
Section titled “String Methods”| Method | Description |
|---|---|
length | String length |
toUpper() | Convert to uppercase |
toLower() | Convert to lowercase |
trim() | Remove leading/trailing whitespace |
contains(sub) | True if string contains substring |
startsWith(prefix) | True if string starts with prefix |
endsWith(suffix) | True if string ends with suffix |
json | Parse string as JSON |
split(sep) | Split by separator, returns list |
replace(old, new) | Replace all occurrences |
toNumber() | Convert to int or float |
extract(pattern) | Pattern matching with wildcards |
Object Methods
Section titled “Object Methods”| Method | Description |
|---|---|
length | Number of keys |
keys() | Returns list of key names |
JSON Processing
Section titled “JSON Processing”Parse JSON from command output by joining lines and calling .json:
let data = `cat config.json`.join.jsondata["database"]["host"]For newline-delimited JSON (one object per line), use .jsonl:
`cat events.ndjson`.jsonl.filter(x => x["level"] == "error").take(10)Bracket access works with variables:
let field = "name"`cat users.ndjson`.json[field]Output Methods
Section titled “Output Methods”Write values to stdout or stderr:
"Fetching...".writeln(stderr)[1, 2, 3].writeln(stdout)Block-scoped file writing:
write to "output.txt" as f { "hello world".writeln(f) [1, 2, 3].writeln(f)}