let — Do shell arithmetic in bash, zsh, fish, PowerShell, and cmd across all 5 shells
Equivalents in every shell
let "x = y * 2"bash builtin. Evaluates each argument as an arithmetic expression; the LAST argument's value becomes the exit status (0 if non-zero, 1 if zero — surprising but standard). Modern bash prefers `(( x = y * 2 ))` (no `let`, double-parens) — same effect, no quoting hell. Operators: `+ - * / % ** ++ -- == != < > <= >= && || << >>`. Integer-only — no floating point.
let "x = y * 2"zsh builtin, same semantics. `(( x = y * 2 ))` is the recommended modern form. zsh additionally provides `float` and `typeset -F` for floating-point arithmetic — `let` itself remains integer-only.
set x (math "$y * 2")fish has NO `let` — its arithmetic primitive is `math`, an external command (since fish 3.0 implemented in C; earlier versions used `bc`). `math "1.5 + 2"` supports floating point natively (unlike bash `let`). `set x (math "$y * 2")` captures result into a variable.
$x = $y * 2pwsh has NO `let` because arithmetic is a first-class language feature — direct assignment with `$x = $y * 2` Just Works for `[int]`, `[double]`, `[decimal]`, `[BigInt]`. Type-cast for explicit precision: `[double]$x = $y / 3.0` forces floating-point even with integer operands. No quoting needed.
set /a x=y*2`set /a` is cmd's arithmetic. Integer-only, 32-bit signed wrap-around at `0x7FFFFFFF`. Operators similar to bash + cmd-specifics: `+ - * / % () ~ ! & ^ | && || << >> *= /= %= +=`. Quoting differs from bash — variables are NOT prefixed with `$`: `set /a x=y*2` reads `y` automatically. Use `set /a x=%y%*2` to embed a literal value.
Worked examples
Increment a counter
let i++ # or: (( i++ ))set i (math $i + 1)$i++set /a i+=1Compute a value with mixed multiplication and addition
(( total = base + items * unit_price ))set total (math "$base + $items * $unit_price")$total = $base + $items * $unit_priceset /a total=base+items*unit_priceFloating-point math (where shells differ)
result=$(echo "scale=2; 1.5 * 3" | bc)set result (math --scale=2 "1.5 * 3")$result = [math]::Round(1.5 * 3, 2)Gotchas
- bash `let` exit status is BACKWARDS from intuition — `let "x = 0"` returns exit status 1 (because the value is zero); `let "x = 5"` returns 0. Scripts that chain `let && command` are mostly checking for non-zero results, which works by accident. Use `(( ))` for cleaner intent.
- bash `let` is integer-only. `let "x = 3 / 2"` is `1`, not `1.5`. For floating-point, pipe to `bc` (POSIX), `awk` (universal), or use Python/Perl one-liners. Modern bash 5.2+ does NOT add float support — this is a permanent limitation.
- fish `math` defaults to `--scale=6` (6 decimal places). `math "1/3"` returns `0.333333`, not `0`. To force integer-like behavior, `math "floor(1/3)"` or `math --scale=0 "1/3"`. The difference from bash's integer-only `let` trips up bash-to-fish ports.
- cmd `set /a` truncates to 32-bit signed — `set /a x=2147483647+1` becomes `-2147483648` (silent overflow, no error). For 64-bit math in cmd, shell out to PowerShell: `for /f %i in ('powershell -NoProfile -c "[long]2147483647+1"') do @set x=%i`.
- pwsh integer division: `10 / 3` returns `3.333…` (`[double]`), NOT `3` (no truncation). To get integer division: `[math]::Floor(10 / 3)` or `[int](10 / 3)` (`[int]` cast also ROUNDS to nearest even, banker's rounding — surprising for those expecting truncation). For C-style truncation use `[int][math]::Truncate(10 / 3)`.