Pipes, Redirection, Errors, Logs
IX. Pipes and Redirection#
Pipes#
- Introduction to Pipes
Lumesh uses the same pipe symbol as bash, but it is more powerful:
Smart Pipe
|Automatically determines the appropriate behavior and can transmit structured data.Left Side: Can automatically read from the result of operations or standard output;
Reading Principle: For functions, built-in commands, and operations, read from the structured data channel. For third-party system commands, read from standard output.
Right Side:
Output Principle: If it is a third-party command, the data is passed as standard input. If it is a function, the data is passed as the last parameter.
| Data | Functions, Operations, Built-in Commands | Third-party Commands |
|---|---|---|
| Input (Left) | Reads from the structured data channel | Reads from standard output |
| Output (Right) | Outputs to the last parameter | Outputs to standard input |
Position Pipe
|_Forces the use of positional parameters in the pipe, directing the pipe to a specified position in the right-side function, using_as a placeholder. If not specified, it appends to the end of the parameters. In most cases, manual specification is unnecessary. Using|is sufficient. However, if the right-side command cannot read standard input or requires specified parameter positions, this pipe must be used.13 | print a _ b # Prints result: a 3 bLoop Dispatch Pipe
|>Used to loop dispatch tasks from the left-side list to the right-side command. Also supports_placeholders.1 0...8 |> print lineNo # Will print 8 lines 2 ls -1 *.txt |> cp _ /tmp/ # Will copy the listed filesPTY Pipe
|^Forces the right-side command to use PTY mode. Some programs require complete terminal control permissions to function correctly, thus requiring PTY mode. The smart pipe maintains a list of such programs, so generally, there is no need to force PTY mode, but if you find a program not functioning correctly, you can try forcing PTY mode.
- Basic Usage of Pipes
Traditional bash pipes, to maintain compatibility with more commands, can only handle byte streams. Byte streams are text data output by third-party commands, which the shell dispatches to the next program for processing. This mode greatly facilitates data transmission between different programs.
In fact, structured pipes are more efficient because they eliminate the need to convert plain text into structured data; some can even save time interacting with input/output devices.
Structured Pipes are More Efficient For example:
1# -- This is a text stream pipe --
2echo 3+5 | bat # This is a third-party command, and requires the pipe to read from standard output, resulting in double inefficiency
3
4# -- This is a structured pipe -- *Recommended usage*
53+5 | bat # The operation result is directly sent to the next program
6
7# -- This is incorrect usage --
8print 3+5 | bat # The print statement outputs to standard output, while also passing None as the operation result; bat captures the None result from the print statement
9
10# -- This is a structured pipe --
11tap 3+5 | bat # The tap statement prints to standard output while passing the result down, bat correctly captures this operation result
123+5 | tap | bat # Equivalent to the previous statementFrom the examples above, we can see:
- If the operation result does not need to be printed and only needs to be passed down, use the pipe directly.
- If the operation result needs to be printed and passed down, use tap before the pipe.
- If the operation result is printed and does not need to be passed down, use print.
- Avoid using echo unless advanced options like echo -e are needed.
- Advanced Usage of Pipes
Filtering
1# Filter data by size and display specified columns: 2Fs.ls -l | where(size > 5K) | select(name,size,modified) 3 4# Output 5+--------------------------------------+ 6| MODIFIED NAME SIZE | 7+======================================+ 8| 2025-06-02 06:26 Cargo.lock 46K | 9| 2025-06-02 04:40 CHANGELOG.md 9K | 10+--------------------------------------+Sorting
1# Filter data by time and sort by specified columns 2Fs.ls -l | where( Fs.diff('d',modified) > 3 ) | sort(size,name) 3 4# Output 5+-------------------------------------------------------+ 6| MODE MODIFIED NAME SIZE TYPE | 7+=======================================================+ 8| 511 2025-03-29 05:58 target 11 symlink | 9| 493 2025-04-06 12:21 benches 66 directory | 10| 493 2025-05-13 10:57 assets 102 directory | 11| 493 2025-03-23 11:58 target_ 128 directory | 12| 420 2025-03-23 05:32 LICENSE 1K file | 13| 420 2025-05-29 12:57 README-cn.md 4K file | 14| 420 2025-05-29 12:57 README.md 4K file | 15+-------------------------------------------------------+Grouping
1# Group by type 2Fs.ls -l | group 'type' # type is a function name, so the quotes cannot be omitted 3 4+-----------------------------------------------------------------+ 5| KEY VALUE | 6+=================================================================+ 7| directory +--------------------------------------------------+ | 8| | MODE MODIFIED NAME SIZE TYPE | | 9| +==================================================+ | 10| | 493 2025-05-13 10:57 assets 102 directory | | 11| | 493 2025-04-06 12:21 benches 66 directory | | 12| | 493 2025-06-02 05:15 src 346 directory | | 13| | 493 2025-06-03 04:32 wiki 528 directory | | 14| +--------------------------------------------------+ | 15| file +--------------------------------------------------+ | 16| | MODE MODIFIED NAME SIZE TYPE | | 17| +==================================================+ | 18| | 420 2025-03-23 05:32 LICENSE 1K file | | 19| | 420 2025-05-29 12:57 README.md 4K file | | 20| | 420 2025-06-02 06:26 Cargo.lock 46K file | | 21| | 420 2025-06-02 04:40 CHANGELOG.md 9K file | | 22| | 420 2025-06-02 06:26 Cargo.toml 2K file | | 23| +--------------------------------------------------+ | 24| symlink +-----------------------------------------------+ | 25| | MODE MODIFIED NAME SIZE TYPE | | 26| +===============================================+ | 27| | 511 2025-03-29 05:58 target 11 symlink | | 28| +-----------------------------------------------+ | 29+-----------------------------------------------------------------+Compatible with Third-party System Commands
You can use From.cmd or Into.table or chained calls .table() to convert text into structured data. It can accept a sequence of column names as parameters.
However, when comparing data, you need to manually convert types.
1ls -l --time-style=long-iso | .table() | where(int C4>1000)
2
3# Output
4+------------------------------------------------------------------+
5| C0 C1 C2 C3 C4 C5 C6 C7 |
6+==================================================================+
7| -rw-r--r-- 1 tix tix 10046 2025-06-02 12:40 CHANGELOG.md |
8| -rw-r--r-- 1 tix tix 47312 2025-06-02 14:26 Cargo.lock |
9| -rw-r--r-- 1 tix tix 2226 2025-06-02 14:26 Cargo.toml |
10| -rw-r--r-- 1 tix tix 1075 2025-03-23 13:32 LICENSE |
11| -rw-r--r-- 1 tix tix 4180 2025-05-29 20:57 README-cn.md |
12| -rw-r--r-- 1 tix tix 4333 2025-05-29 20:57 README.md |
13+------------------------------------------------------------------+Redirection#
<<Read>>Append Output>!Overwrite Output1 + 2 >> result.txt
Error Redirection: Combined with Error Handling Symbols For specific usage, please refer to the error handling section.
| Redirection Type | Lume | Bash |
|---|---|---|
| Standard Output, Append | cmd >> out.txt | cmd >> out.txt |
| Standard Output, Overwrite | cmd >! out.txt | cmd > out.txt |
| Error Redirect to Standard Output | cmd ?> >> out.log | command 2>&1 >> out.log |
| Separate Error Output | cmd ?? >> out.log | cmd 2>> out.log |
| Merge Standard and Error Output | cmd ?+ >> out.log | cmd >> out.log 2>&1 |
X. Error Handling#
Error Capture: ?:#
A statement followed by ?: expr,
Typically,
exprcan be a lambda function that accepts an Error object. This Error object is a Map object, and you can index specific properties using.or@or[]. This object contains three properties:codemsgexpr
If
expris of a regular type, it provides a default value in case of an exception.
Error Ignoring: ?.#
Equivalent to ?: {}
Error Printing: ?+, ??, ?>#
Print error to standard output:
?+Equivalent to?: e -> echo e.msgPrint error to error output:
??Equivalent to?: e -> eprint e.msgOutput error as operation result:
?>Equivalent to?: e -> e.msg
Error Termination: ?!#
- Silently terminate the pipeline when encountering an error
Fs.ls -l | ui.pick ?! |_ cp _ /tmp
When the user cancels the selection, subsequent pipeline operations terminate upon encountering an error.
The above methods can apply to any statement or function at the end. Error handling symbols have a higher priority than pipes.
1
26 / 0 ?. # ignore error
36 / 0 ?+ # print error to stdout
46 / 0 ?? # print error to stderr
56 / 0 ?! # use this error message as result
6
7let e = x -> echo x.code
86 / 0 ?: e # dealing with the error using a function/lambda
9
10# also functions could use error handling too.
11fn divide(x,y){
12 x / y
13} ?: eTips
Utilizing the characteristics of error handling symbols, you can also provide default values for failed calculations:
1let e = x -> 0 2let result = 6 / 0 ?: e 3echo result # Outputs default value: 0 4 5# Equivalent to 6let result = 6 / 0 ?: 0 7echo result # Outputs default value: 0Use error handling when defining functions
1let e = x -> {print x.code} 2 3fn div(a,b){ 4 a / b 5} ?: e 6 7div(3,0) # the defined error handling will be executed here.
Error Debugging:#
Error Messages Generally, observing the error message is sufficient to identify the problem. For example:
1[PARSE FAILED] # Syntax parsing error 2unexpected syntax error: expect "{" 3 ▏ 4 1 ▏ fn assert(actual, expected, test_name, test_count=0) { 5 2 ▏ if actual != expected 6 ▏ ^ 7 3 ▏ print "[FAIL]" test_count test_name "| 实际:" actual "| 预期" expected 8 4 ▏ } else { 9 5 ▏ print "[PASS]" test_count test_name 10 ▏ 11 ↳ at line 2, column 27 12 13 14> 0...8 x 15[ERROR] # Runtime error 16Message [1]: type error, expected Symbol as command, found 0...8: List 17Expression[1]: 0...8 x 18 19SyntaxTree[1]: 20Cmd 〈0...8〉 21〖 22 Symbol〈"x"〉 23〗debugCommand For further debugging, you can use the debug command.1# Simple data debugging: 2let c = 0..8 3debug c # Output: Range〈0..9,1〉 4 5# Complex statement debugging: 6let a := if a {3 + 5} else { print a is False} 7debug a 8 9# Output: 10if (Symbol〈"a"〉) { 11 12 { 13 14 Integer〈3〉 15 + 16 Integer〈5〉 17 } 18 19}else{ 20 21 { 22 23 Cmd 〈Symbol〈"print"〉〉 24 〖 25 Symbol〈"a"〉 26 Symbol〈"is"〉 27 Boolean〈false〉 28 〗 29 30 } 31 32}Log Module The use of the Log module is also beneficial for error debugging; please refer to the Log Module Documentation.