nushell-pro by hustcer/nushell-pro
npx skills add https://github.com/hustcer/nushell-pro --skill nushell-pro编写地道、高性能、安全且可维护的 Nushell 脚本。本技能强制执行 Nushell 约定,捕获安全问题,并帮助避免常见陷阱。
let;仅当函数式替代方案不适用时才使用 mutsource/use 需要解析时常量echo 或 returndef --env广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
par-each 并行化管道输入 ($in) 与函数参数不可互换!
# 错误 — 将管道数据视为第一个参数
def my-func [items: list, value: any] {
$items | append $value
}
# 正确 — 声明管道签名
def my-func [value: any]: list -> list {
$in | append $value
}
# 用法
[1 2 3] | my-func 4 # 正确工作
为何重要:
$list | func arg 对比 func $list argdef func [x: int] { ... } # 仅参数
def func []: string -> int { ... } # 仅管道
def func [x: int]: string -> int { ... } # 管道和参数
def func []: [list -> list, string -> list] { ... } # 多个 I/O 类型
| 实体 | 约定 | 示例 |
|---|---|---|
| 命令 | kebab-case | fetch-user, build-all |
| 子命令 | kebab-case | "str my-cmd", date list-timezone |
| 标志 | kebab-case | --all-caps, --output-dir |
| 变量/参数 | snake_case | $user_id, $file_path |
| 环境变量 | SCREAMING_SNAKE_CASE | $env.APP_VERSION |
| 常量 | snake_case | const max_retries = 3 |
url 可以,usr 不行)--all-caps -> $all_caps[1 2 3] | each {|x| $x * 2 }
{name: 'Alice', age: 30}
[1 2 3 4] | each {|x|
$x * 2
}
[
{name: 'Alice', age: 30}
{name: 'Bob', age: 25}
]
| 前后各一个空格|params| 前无空格:{|x| ...} 而非 { |x| ...}: 后一个空格:{x: 1} 而非 {x:1}[1 2 3] 而非 [1, 2, 3], 时其后一个空格(闭包参数等)# 完全类型化并带有 I/O 签名
def add-prefix [text: string, --prefix (-p): string = 'INFO']: nothing -> string {
$'($prefix): ($text)'
}
# 多个 I/O 签名
def to-list []: [
list -> list
string -> list
] {
# 实现
}
# 从 API 获取用户数据
#
# 通过 ID 检索用户信息并返回
# 包含所有可用字段的结构化记录。
@example 'Fetch user by ID' { fetch-user 42 }
@category 'network'
def fetch-user [
id: int # 用户的唯一标识符
--verbose (-v) # 显示详细的请求信息
]: nothing -> record {
# 实现
}
--output (-o): stringdef greet [name: string = 'World']? 表示可选位置参数:def greet [name?: string]def multi-greet [...names: string]def --wrapped 包装外部命令并转发未知标志def --env setup-project [] {
cd project-dir
$env.PROJECT_ROOT = (pwd)
}
{name: 'Alice', age: 30} # 创建记录
$rec1 | merge $rec2 # 合并(右偏)
[$r1 $r2 $r3] | into record # 合并多个记录
$rec | update name {|r| $'Dr. ($r.name)' } # 更新字段
$rec | insert active true # 插入字段
$rec | upsert count {|r| ($r.count? | default 0) + 1 } # 更新或插入
$rec | reject password secret_key # 移除字段
$rec | select name age email # 仅保留这些字段
$rec | items {|k, v| $'($k): ($v)' } # 迭代键值对
$rec | transpose key val # 转换为表格
$table | where age > 25 # 筛选行
$table | insert retired {|row| $row.age > 65 } # 添加列
$table | rename -c {age: years} # 重命名列
$table | group-by status --to-table # 按字段分组
$table | transpose name data # 转置行/列
$table | join $other_table user_id # 内连接
$table | join --left $other user_id # 左连接
$list | enumerate | where {|e| $e.index > 5 } # 带索引筛选
$list | reduce --fold 0 {|it, acc| $acc + $it } # 累加
$list | window 3 # 滑动窗口
$list | chunks 100 # 分批处理
$list | flatten # 展平嵌套列表
$record.field? # 如果缺失则返回 null(无错误)
$record.field? | default 'N/A' # 提供回退值
if ($record.field? != null) { } # 检查存在性
$list | default -e $fallback # 空集合的默认值
# 错误 — 使用可变变量的命令式
mut total = 0
for item in $items { $total += $item.price }
# 正确 — 函数式管道
$items | get price | math sum
# 错误 — 可变计数器
mut i = 0
for file in (ls) { print $'($i): ($file.name)'; $i += 1 }
# 正确 — 枚举
ls | enumerate | each {|it| $'($it.index): ($it.item.name)' }
# each: 转换每个元素
$list | each {|item| $item * 2 }
# each --flatten: 流式输出(将 list<list<T>> 转换为 list<T>)
ls *.txt | each --flatten {|f| open $f.name | lines } | find 'TODO'
# each --keep-empty: 保留 null 结果
[1 2 3] | each --keep-empty {|e| if $e == 2 { 'found' } }
# par-each: 并行处理(I/O 或 CPU 密集型)
$urls | par-each {|url| http get $url }
$urls | par-each --threads 4 {|url| http get $url }
# reduce: 累加(如果没有 --fold,第一个元素是初始累加器)
[1 2 3 4] | reduce {|it, acc| $acc + $it }
# generate: 从任意源创建值而无需 mut
generate {|state| { out: ($state * 2), next: ($state + 1) } } 1 | first 5
# 行条件 — 简写语法,自动展开 $it
ls | where type == file # 简单且可读
$table | where size > 100 # 展开为:$it.size > 100
# 闭包 — 完全灵活,可以存储和重用
let big_files = {|row| $row.size > 1mb }
ls | where $big_files
$list | where {$in > 10} # 使用 $in 或参数
简单字段比较使用行条件;复杂逻辑或可重用条件使用闭包。
def double-all []: list<int> -> list<int> {
$in | each {|x| $x * 2 }
}
# 需要稍后使用时提前捕获 $in(首次使用时即被消耗)
def process []: table -> table {
let input = $in
let count = $input | length
$input | first ($count // 2)
}
let config = (open config.toml)
let names = $config.users | get name
# 可接受 — 当没有函数式替代方案时使用 mut
mut retries = 0
loop {
if (try-connect) { break }
$retries += 1
if $retries >= 3 { error make {msg: 'Connection failed'} }
sleep 1sec
}
const lib_path = 'src/lib.nu'
source $lib_path # 有效:const 在解析时解析
let lib_path = 'src/lib.nu'
source $lib_path # 错误:let 仅在运行时有效
mut count = 0
ls | each {|f| $count += 1 } # 错误!闭包不能捕获 mut
# 解决方案:
ls | length # 使用内置命令
[1 2 3] | reduce {|x, acc| $acc + $x } # 使用 reduce
for f in (ls) { $count += 1 } # 如果确实需要突变,使用循环
完整优先级和规则请参阅字符串格式参考。
快速摘要(从高到低优先级):
[foo bar baz]r#'(?:pattern)'#'simple string'$'Hello, ($name)!'"line1\nline2"$"tab:\t($value)\n"(仅与转义一起使用)my-module/
├── mod.nu # 模块入口点
├── utils.nu # 子模块
└── tests/
└── mod.nu # 测试模块
export 定义是公开的;未导出的是私有的export def mainexport use submodule.nu * 重新导出子模块命令export-env 进行环境设置块#!/usr/bin/env nu
# 构建项目
def "main build" [--release (-r)] {
print 'Building...'
}
# 运行测试
def "main test" [--verbose (-v)] {
print 'Testing...'
}
def main [] {
print 'Usage: script.nu <build|test>'
}
对于 shebang 脚本中的 stdin 访问:#!/usr/bin/env -S nu --stdin
def validate-age [age: int] {
if $age < 0 or $age > 150 {
error make {
msg: 'Invalid age value'
label: {
text: $'Age must be between 0 and 150, got ($age)'
span: (metadata $age).span
}
}
}
$age
}
let result = try {
http get $url
} catch {|err|
print -e $'Request failed: ($err.msg)'
null
}
# 使用 complete 获取详细的外部命令错误信息
let result = (^some-external-cmd | complete)
if $result.exit_code != 0 {
print -e $'Error: ($result.stderr)'
}
do -i 抑制错误do -i(忽略错误)运行闭包并抑制任何错误,失败时返回 null。do -c(捕获错误)将错误捕获为值返回。
# 忽略错误 — 如果闭包失败则返回 null
do -i { rm non_existent_file }
# 用作简洁的回退
let val = (do -i { open config.toml | get setting } | default 'fallback')
# 将错误捕获为值(而不是中止管道)
let result = (do -c { ^some-cmd })
何时使用每种方法:
do -i — 发射后不管,或仅在失败时需要默认值时do -c — 将错误捕获为值,以便在失败时中止下游管道try/catch — 需要检查或记录错误时complete — 需要外部命令的退出码 + stdout + stderr 时use std/assert
for t in [[input expected]; [0 0] [1 1] [2 1] [5 5]] {
assert equal (fib $t.input) $t.expected
}
def "assert even" [number: int] {
assert ($number mod 2 == 0) --error-label {
text: $'($number) is not an even number'
span: (metadata $number).span
}
}
$value | describe # 检查类型
$data | each {|x| print $x; $x } # 打印中间值(传递)
timeit { expensive-command } # 测量执行时间
metadata $value # 检查跨度和其他元数据
完整指南请参阅安全参考。
Nushell 在设计上比 Bash 更安全(无 eval,参数作为数组传递而非通过 shell),但安全风险仍然存在。
# 危险 — 任意代码执行
^nu -c $user_input
source $user_provided_file
# 危险 — shell 解释字符串
^sh -c $'echo ($user_input)'
^bash -c $user_input
# 错误 — 构建命令字符串
let cmd = $'ls ($user_path)'
^sh -c $cmd
# 正确 — 直接传递参数(无 shell 解释)
^ls $user_path
run-external 'ls' $user_path
# 错误 — 可能存在路径遍历
def read-file [name: string] { open $name }
# 正确 — 验证防止遍历
def read-file [name: string, --base-dir: string = '.'] {
let full = ($base_dir | path join $name | path expand)
let base = ($base_dir | path expand)
if not ($full | str starts-with $base) {
error make {msg: $'Path traversal detected: ($name)'}
}
open $full
}
# 错误 — 凭据对所有子进程和 env 可见
$env.API_KEY = 'secret-key-123'
^curl -H $'Authorization: Bearer ($env.API_KEY)' $url
# 正确 — 限定凭据作用域,使用 with-env
with-env {API_KEY: (open ~/.secrets/api_key | str trim)} {
^curl -H $'Authorization: Bearer ($env.API_KEY)' $url
}
# 错误 — 可预测的临时文件,竞态条件
let tmp = '/tmp/my-script-tmp'
'data' | save $tmp
# 正确 — 使用 mktemp 创建唯一临时文件
let tmp = (^mktemp | str trim)
'data' | save $tmp
# ... 使用 $tmp ...
rm $tmp
let result = (^cargo build o+e>| complete)
if $result.exit_code != 0 {
error make {msg: $'Build failed: ($result.stderr)'}
}
# 错误 — 来自变量的 glob,可能匹配意外文件
^rm $'($user_dir)/*'
# 正确 — 验证后使用 trash 或显式路径
if ($user_dir | path type) == 'dir' {
rm -r $user_dir
}
完整清单请参阅脚本审查参考。
审查 Nushell 脚本时,按顺序检查以下类别:
nu -c / source / ^sh -cmktemp,而非可预测路径rm 操作受到保护且是故意的try/catch)complete 检查外部命令? 运算符访问可选字段for 作为最终表达式(改用 each)mut^ 前缀par-eacheach --flatten 进行流式处理let 绑定中详细解释请参阅反模式参考。
| 反模式 | 修复 |
|---|---|
echo $value | 直接使用 $value(隐式返回) |
$"simple text" | 'simple text'(无需插值) |
for 作为最终表达式 | 使用 each(for 不返回值) |
mut 用于累加 | 使用 reduce 或 math sum |
let path = ...; source $path | const path = ...; source $path |
"hello" > file.txt | `'hello' |
grep pattern | where $it =~ pattern 或内置 find |
| 解析字符串输出 | 使用结构化命令(ls, ps, http get) |
$env.FOO = bar 在 def 内部 | 使用 def --env |
| `{ | x |
$record.missing(错误) | $record.missing?(返回 null) |
对单个记录使用 each | 改用 items 或 transpose |
外部命令没有 ^ | 使用 ^grep 明确表示外部命令 |
def 上方使用 # 以便集成帮助default — 优雅处理 null/缺失值^ 前缀 — ^grep 而非 grep;Nushell 内置命令优先(例如,find 是 Nushell 的,而非 Unix find)^rg,巨型 JSON 用 ^jq编写或审查 Nushell 代码时:
nu -c 'source file.nu' 或 nu file.nunu -c 'help <command>' 检查命令签名和示例每周安装数
294
仓库
GitHub 星标数
2
首次出现
2026年3月1日
安全审计
安装于
claude-code283
opencode272
codex272
cursor272
gemini-cli24
github-copilot24
Write idiomatic, performant, secure, and maintainable Nushell scripts. This skill enforces Nushell conventions, catches security issues, and helps avoid common pitfalls.
let by default; only use mut when functional alternatives don't applysource/use require parse-time constantsecho or returndef --env when caller-side changes are neededpar-each parallelizationPipeline input ($in) is NOT interchangeable with function parameters!
# WRONG — treats pipeline data as first parameter
def my-func [items: list, value: any] {
$items | append $value
}
# CORRECT — declares pipeline signature
def my-func [value: any]: list -> list {
$in | append $value
}
# Usage
[1 2 3] | my-func 4 # Works correctly
Why this matters:
$list | func arg vs func $list argdef func [x: int] { ... } # params only
def func []: string -> int { ... } # pipeline only
def func [x: int]: string -> int { ... } # both pipeline and params
def func []: [list -> list, string -> list] { ... } # multiple I/O types
| Entity | Convention | Example |
|---|---|---|
| Commands | kebab-case | fetch-user, build-all |
| Subcommands | kebab-case | "str my-cmd", date list-timezone |
| Flags | kebab-case | , |
url ok, usr not ok)--all-caps -> $all_caps[1 2 3] | each {|x| $x * 2 }
{name: 'Alice', age: 30}
[1 2 3 4] | each {|x|
$x * 2
}
[
{name: 'Alice', age: 30}
{name: 'Bob', age: 25}
]
||params| in closures: {|x| ...} not { |x| ...}: in records: {x: 1} not {x:1}[1 2 3] not [1, 2, 3], when used (closure params, etc.)# Fully typed with I/O signature
def add-prefix [text: string, --prefix (-p): string = 'INFO']: nothing -> string {
$'($prefix): ($text)'
}
# Multiple I/O signatures
def to-list []: [
list -> list
string -> list
] {
# implementation
}
# Fetch user data from the API
#
# Retrieves user information by ID and returns
# a structured record with all available fields.
@example 'Fetch user by ID' { fetch-user 42 }
@category 'network'
def fetch-user [
id: int # The user's unique identifier
--verbose (-v) # Show detailed request info
]: nothing -> record {
# implementation
}
--output (-o): stringdef greet [name: string = 'World']? for optional positional params: def greet [name?: string]def multi-greet [...names: string]def --wrapped to wrap external commands and forward unknown flagsdef --env setup-project [] {
cd project-dir
$env.PROJECT_ROOT = (pwd)
}
{name: 'Alice', age: 30} # Create record
$rec1 | merge $rec2 # Merge (right-biased)
[$r1 $r2 $r3] | into record # Merge many records
$rec | update name {|r| $'Dr. ($r.name)' } # Update field
$rec | insert active true # Insert field
$rec | upsert count {|r| ($r.count? | default 0) + 1 } # Update or insert
$rec | reject password secret_key # Remove fields
$rec | select name age email # Keep only these fields
$rec | items {|k, v| $'($k): ($v)' } # Iterate key-value pairs
$rec | transpose key val # Convert to table
$table | where age > 25 # Filter rows
$table | insert retired {|row| $row.age > 65 } # Add column
$table | rename -c {age: years} # Rename column
$table | group-by status --to-table # Group by field
$table | transpose name data # Transpose rows/columns
$table | join $other_table user_id # Inner join
$table | join --left $other user_id # Left join
$list | enumerate | where {|e| $e.index > 5 } # Filter with index
$list | reduce --fold 0 {|it, acc| $acc + $it } # Accumulate
$list | window 3 # Sliding window
$list | chunks 100 # Process in batches
$list | flatten # Flatten nested lists
$record.field? # Returns null if missing (no error)
$record.field? | default 'N/A' # Provide fallback
if ($record.field? != null) { } # Check existence
$list | default -e $fallback # Default for empty collections
# Bad — imperative with mutable variable
mut total = 0
for item in $items { $total += $item.price }
# Good — functional pipeline
$items | get price | math sum
# Bad — mutable counter
mut i = 0
for file in (ls) { print $'($i): ($file.name)'; $i += 1 }
# Good — enumerate
ls | enumerate | each {|it| $'($it.index): ($it.item.name)' }
# each: transform each element
$list | each {|item| $item * 2 }
# each --flatten: stream outputs (turns list<list<T>> into list<T>)
ls *.txt | each --flatten {|f| open $f.name | lines } | find 'TODO'
# each --keep-empty: preserve null results
[1 2 3] | each --keep-empty {|e| if $e == 2 { 'found' } }
# par-each: parallel processing (I/O or CPU-bound)
$urls | par-each {|url| http get $url }
$urls | par-each --threads 4 {|url| http get $url }
# reduce: accumulate (first element is initial acc if no --fold)
[1 2 3 4] | reduce {|it, acc| $acc + $it }
# generate: create values from arbitrary sources without mut
generate {|state| { out: ($state * 2), next: ($state + 1) } } 1 | first 5
# Row conditions — short-hand syntax, auto-expands $it
ls | where type == file # Simple and readable
$table | where size > 100 # Expands to: $it.size > 100
# Closures — full flexibility, can be stored and reused
let big_files = {|row| $row.size > 1mb }
ls | where $big_files
$list | where {$in > 10} # Use $in or parameter
Use row conditions for simple field comparisons; use closures for complex logic or reusable conditions.
def double-all []: list<int> -> list<int> {
$in | each {|x| $x * 2 }
}
# Capture $in early when needed later (it's consumed on first use)
def process []: table -> table {
let input = $in
let count = $input | length
$input | first ($count // 2)
}
let config = (open config.toml)
let names = $config.users | get name
# Acceptable — mut when no functional alternative
mut retries = 0
loop {
if (try-connect) { break }
$retries += 1
if $retries >= 3 { error make {msg: 'Connection failed'} }
sleep 1sec
}
const lib_path = 'src/lib.nu'
source $lib_path # Works: const is resolved at parse time
let lib_path = 'src/lib.nu'
source $lib_path # Error: let is runtime only
mut count = 0
ls | each {|f| $count += 1 } # Error! Closures can't capture mut
# Solutions:
ls | length # Use built-in commands
[1 2 3] | reduce {|x, acc| $acc + $x } # Use reduce
for f in (ls) { $count += 1 } # Use a loop if mutation truly needed
Refer to String Formats Reference for the full priority and rules.
Quick summary (high to low priority):
[foo bar baz]r#'(?:pattern)'#'simple string'$'Hello, ($name)!'"line1\nline2"$"tab:\t($value)\n" (only with escapes)my-module/
├── mod.nu # Module entry point
├── utils.nu # Submodule
└── tests/
└── mod.nu # Test module
export definitions are public; non-exported are privateexport def main when command name matches module nameexport use submodule.nu * to re-export submodule commandsexport-env for environment setup blocks#!/usr/bin/env nu
# Build the project
def "main build" [--release (-r)] {
print 'Building...'
}
# Run tests
def "main test" [--verbose (-v)] {
print 'Testing...'
}
def main [] {
print 'Usage: script.nu <build|test>'
}
For stdin access in shebang scripts: #!/usr/bin/env -S nu --stdin
def validate-age [age: int] {
if $age < 0 or $age > 150 {
error make {
msg: 'Invalid age value'
label: {
text: $'Age must be between 0 and 150, got ($age)'
span: (metadata $age).span
}
}
}
$age
}
let result = try {
http get $url
} catch {|err|
print -e $'Request failed: ($err.msg)'
null
}
# Use complete for detailed external command error info
let result = (^some-external-cmd | complete)
if $result.exit_code != 0 {
print -e $'Error: ($result.stderr)'
}
do -ido -i (ignore errors) runs a closure and suppresses any errors, returning null on failure. do -c (capture errors) catches errors and returns them as values.
# Ignore errors — returns null if the closure fails
do -i { rm non_existent_file }
# Use as a concise fallback
let val = (do -i { open config.toml | get setting } | default 'fallback')
# Capture errors as values (instead of aborting the pipeline)
let result = (do -c { ^some-cmd })
When to use each approach:
do -i — Fire-and-forget, or when you only need a default on failuredo -c — Catch errors as values to abort downstream pipeline on failuretry/catch — When you need to inspect or log the errorcomplete — When you need exit code + stdout + stderr from external commandsuse std/assert
for t in [[input expected]; [0 0] [1 1] [2 1] [5 5]] {
assert equal (fib $t.input) $t.expected
}
def "assert even" [number: int] {
assert ($number mod 2 == 0) --error-label {
text: $'($number) is not an even number'
span: (metadata $number).span
}
}
$value | describe # Inspect type
$data | each {|x| print $x; $x } # Print intermediate values (pass-through)
timeit { expensive-command } # Measure execution time
metadata $value # Inspect span and other metadata
Refer to Security Reference for the full guide.
Nushell is safer than Bash by design (no eval, arguments passed as arrays not through shell), but security risks remain.
# DANGEROUS — arbitrary code execution
^nu -c $user_input
source $user_provided_file
# DANGEROUS — shell interprets the string
^sh -c $'echo ($user_input)'
^bash -c $user_input
# Bad — constructing command strings
let cmd = $'ls ($user_path)'
^sh -c $cmd
# Good — pass arguments directly (no shell interpretation)
^ls $user_path
run-external 'ls' $user_path
# Bad — path traversal possible
def read-file [name: string] { open $name }
# Good — validate against traversal
def read-file [name: string, --base-dir: string = '.'] {
let full = ($base_dir | path join $name | path expand)
let base = ($base_dir | path expand)
if not ($full | str starts-with $base) {
error make {msg: $'Path traversal detected: ($name)'}
}
open $full
}
# Bad — credential visible to all child processes and in env
$env.API_KEY = 'secret-key-123'
^curl -H $'Authorization: Bearer ($env.API_KEY)' $url
# Good — scope credentials, use with-env
with-env {API_KEY: (open ~/.secrets/api_key | str trim)} {
^curl -H $'Authorization: Bearer ($env.API_KEY)' $url
}
# Bad — predictable temp file, race condition
let tmp = '/tmp/my-script-tmp'
'data' | save $tmp
# Good — use mktemp for unique temp files
let tmp = (^mktemp | str trim)
'data' | save $tmp
# ... use $tmp ...
rm $tmp
let result = (^cargo build o+e>| complete)
if $result.exit_code != 0 {
error make {msg: $'Build failed: ($result.stderr)'}
}
# Bad — glob from variable, could match unintended files
^rm $'($user_dir)/*'
# Good — validate then use trash or explicit paths
if ($user_dir | path type) == 'dir' {
rm -r $user_dir
}
Refer to Script Review Reference for the full checklist.
When reviewing a Nushell script, check these categories in order:
nu -c / source / ^sh -c with untrusted inputmktemp, not predictable pathsrm operations are guarded and intentionaltry/catch for fallible operationscomplete when error handling matters? operatorfor as final expression (use each instead)mut not captured in closures^ prefix on external commandspar-each for I/O or CPU-bound parallel workeach --flatten for streaming when appropriatelet bindingsRefer to Anti-Patterns Reference for detailed explanations.
| Anti-Pattern | Fix |
|---|---|
echo $value | Just $value (implicit return) |
$"simple text" | 'simple text' (no interpolation needed) |
for as final expression | Use each (for doesn't return a value) |
mut for accumulation | Use reduce or |
# above def for help integrationdefault — Handle null/missing gracefully^ — ^grep not grep; Nushell builtins take precedence (e.g., find is Nushell's, not Unix )When writing or reviewing Nushell code:
nu -c 'source file.nu' or nu file.nunu -c 'help <command>' to check command signatures and examplesWeekly Installs
294
Repository
GitHub Stars
2
First Seen
Mar 1, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
claude-code283
opencode272
codex272
cursor272
gemini-cli24
github-copilot24
--all-caps--output-dir| Variables/Params | snake_case | $user_id, $file_path |
| Environment vars | SCREAMING_SNAKE_CASE | $env.APP_VERSION |
| Constants | snake_case | const max_retries = 3 |
math sumlet path = ...; source $path | const path = ...; source $path |
"hello" > file.txt | `'hello' |
grep pattern | where $it =~ pattern or built-in find |
| Parsing string output | Use structured commands (ls, ps, http get) |
$env.FOO = bar inside def | Use def --env |
| `{ | x |
$record.missing (error) | $record.missing? (returns null) |
each on single record | Use items or transpose instead |
External cmd without ^ | Use ^grep to be explicit about externals |
find^rg for large file search, ^jq for giant JSON