zy v1.0.43
Official binary release for zy 1.0.43.
What's changed in 1.0.43
Changed
- Filesize units now match nushell (SI/metric):
1kbis 1000 bytes (was 1024),1mbis 10^6, and so on, while the IEC*ibforms stay binary (1kibis 1024). Display usesB/kB/MB/GB/TB/PBwith the value truncated to one decimal, like nu. If you relied onkb=1024, usekib.
Added
-
to msgpack/from msgpackround-trip values through the MessagePack binary format ([foo, 42, false]↔ the packed bytes). -
encode/decodesupport text charsets via iconv:shift-jis,euc-jp/euc-kr,utf-16/-le/-be,big5,gb18030,latin1,windows-1252, and more, with--ignore-errors.utf-16is decode-only. -
nu-checkvalidates nushell syntax with zy's own parser:'1 + 1' | nu-checkistrue,'if true {' | nu-checkisfalse;--debugprints the parse error. -
Typed
mathaggregations:math sum/avg/min/max/median/modepreserve the unit, so[1min 2min 3min] | math sumis6minand[5kb 10mb 200b] | math medianis5.0 kB;datetimesupportsmin/max. Mixed numeric input is unchanged. -
str length/str substring/str index-oftake--grapheme-clusters(-g) to count/index by extended grapheme cluster (flags, ZWJ sequences, combining marks) instead of bytes. -
More
ansicodes:cursor_on/cursor_off,cursor_blink_*, screen/line erase, cursor movement,attr_*/reset_*aliases, and nu's single-letter aliases (r g y b m p c w k i u d h s n). -
jobnamespace (nu-style closure-thread jobs):job spawn { … }runs a closure on a background thread and returns its id, withjob id,job list,job kill,job send/job recv(a thread-safe per-job mailbox), andjob flush. Closure execution is serialized against the main thread by a per-command executor lock (released while a worker blocks onsleep/job recv), and a worker's exit-status is thread-local, and the whole thing is TSan-clean. The bash-stylejobsprocess control is unaffected. -
describe -d/describe --detailedreturns nu's detailed record instead of the plain type string: a nested{type, detailed_type, value, …}withlengthfor lists and binary,columnsfor records, and a per-element table for lists, built recursively so each nested value is itself a detailed record. Thedetailed_typefield matches nu's full type spelling (table<a: int>for a list of records,list<oneof<int, string>>for mixed lists, recursiverecord<a: record<b: int>>). nu'srust_typefield is intentionally omitted (it exposes nushell's internal Rust types, meaningless in a C shell).--no-collect/-nis accepted but a no-op, since zy is eager and has no streams. The plaindescribe(no flag) is unchanged. -
into sqlite <path>writes the pipeline table into a sqlite database file (default tablemain,--table-nameto override), matching nu. -
Timezone-aware, sub-second datetimes (ZY-062): a datetime now carries its source UTC offset and nanoseconds, so
into datetimeround-trips+HH:MMand fractional seconds,into recordincludes millisecond/microsecond/nanosecond and the source timezone,into intgives the full nanosecond count, andinto stringmatches nu'sSat Oct 10 10:00:00 2020form.into datetimealso parses a{year..timezone}record, a list of strings,%Pam/pm,%zoffsets, and the Europeand.m.Yforms. -
transpose -d/--as-recordand the-l/--keep-last/-a/--keep-alldedup companions;rename --block {closure}renames each column by running the closure on its name. -
Digit separators in number literals:
1_000,1_234_567,3_141.59.
Fixed
-
A bare builtin statement before
;no longer prints its value (job id; 99,whoami; 99→ just99, matching nu):nu_outputnow honors force-capture likeinto_output. -
to jsonof a binary emits nu's byte-int array (0x[ff aa 01]→[255, 170, 1], indent-aware), not a quoted"0x[ff]"string. -
to nuonof a duration and binary match nu: a duration serializes as its raw nanosecond count (1day→86400000000000ns) and a binary as uppercase no-space hex (0x[ff]→0x[FF]), so they round-trip anddescribe -dof a duration/binary matches nu in every field. -
Duration and filesize arithmetic keep their unit:
(500day + 4hr + 5sec)is a duration (was a float), sointo recordbreaks it into week/day/hour;dur / duris a float ratio. -
flatten --allrecurses into nested list/table columns; a nested[[ ]]table literal inside a cell now parses. -
joinaccepts an inline[ … ]table as its right side and supports--suffix/--prefixfor colliding columns. -
insert/upsertwith a dotted cell-path key (a.0.b) descend instead of making a literal column;group-by --to-tableemits[{group, items}, …];findmatches list elements by value for numeric/filesize terms;seqhonours an explicit 3-arg step like nu (ZY-061);inspectas the last stage forwards its value to stdout (ZY-060). -
Empty quoted strings survive in records and tables (
['', 'a'],{a: '', b: 1},{a:'' b:1},[[a]; ['']]), and a$"…"value in a brace block (if c { $"label: (x)" }) no longer mis-parses as a record. -
to nuonquotes digit/dot/space-bearing strings like nu ({c1: 1}→{"c1": 1}). -
Cell-paths on list and record literals (ZY-053):
[10 20 30].1→ 20,{a:1}.a→ 1,{a:{b:2}}.a.b→ 2. -
$nu.os-infois a record (ZY-056) with name/arch/family/kernel_version, so| get nameand.archwork. -
String interpolation inside a record value (ZY-054):
{a: $"x ($n)"}evaluates the($n)block. -
bits shr --signedsign-extends and the width auto-sizes (ZY-044):-2 | bits shr 1 --signed→ -1
(was 127), and ints over 255 no longer wrap (256 | bits shr 1→ 128).-s/-nshort flags now parse (ZY-039). -
constaccepts a list or record value (ZY-038):const x = [1 2 3]binds the whole list, not just[1]. -
The fff MCP server advertises usage instructions so a fresh client knows the grep/find_files/multi_grep tools.
-
Whole-valued floats print with
.0(ZY-057), matching nu:10 / 2→5.0,9 | math sqrt→3.0,
string interpolation$"(10 / 2)"→5.0.into stringstill drops it (2.0 | into string→2).
math absof an int returns an int;math medianof an even-length list returns a float. -
constaccepts bothconst X=42(POSIX) andconst x = [1 2 3](nu) forms again. The fused form had
regressed to "not a valid identifier". -
A
deffunction now returns an integer value (ZY-042).def double [x] { return ($x * 2) }; double 5
gave nothing because an integerreturnwas always read as a POSIX exit code. Adefnow puts the value
on the pipeline like nu, while a POSIXname() { … }still uses the exit code. -
select / reject / update error on a missing column (ZY-045), like nu, instead of silently dropping it
or creating it. A trailing?marks a column optional (select a?); useupsertto add a column. -
wherehandles column arithmetic (ZY-055):where a mod 2 == 0,where a + b > 6and similar now
filter per row instead of returning nothing. -
A cell-path on a record literal works (ZY-053):
{a:1}.a→ 1,{a:{b:2}}.a.b→ 2. -
sortcompares by value type (ZY-043): a list of genuine strings sorts lexicographically like nu
("2 10 1" | split row " " | sort→ 1, 10, 2) instead of coercing to numbers. -
split charsandstr reverseare UTF-8 aware (ZY-040):'café' | split chars→ c a f é and
'café' | str reverse→éfac, no more byte mojibake. -
Records can mix spaced and compact colon entries (ZY-059):
{b: 4, a: 3, c:1}parses (and so
… | sort -v), in any order ofb: 4/c:1style. -
A huge
seqor range no longer freezes the terminal.seq 4 600000000and a
bare1..600000000used to allocate the whole list and exhaust memory; they now
error with a clear hint past 10M elements. This also removes the old silent
truncation of a range literal at 100k, so ranges up to 10M materialise fully
(1..200000 | lengthis200000, not100000). Lazy streaming (so big ranges
don't materialise at all, like nu) is planned separately. -
whereshorthand matches Nushell.where a > 1 and b > 1now honours the
whole and/or chain instead of just the first comparison;where p.age > 18
follows a dotted column path;where c in ["red" "green"]tests membership; and
where a >= 2/<= 25/== 2/!= 2work (the two-char operators were being
split).where $it mod 2 == 0over a list evaluates with full precedence. -
More commands take closures and map over lists.
sort-by {|x| …}and
group-by {|x| …}work on a plain list of values;into bool/into string/
into filesize/into recordmap element-wise over a list (andinto record
reads a list of[key value]pairs);parse --regexreturns every match per line
(not just the first) and names unnamed groupscapture0…. -
Records, ranges, and control flow.
let r = try { … } catch { … }assigns the
evaluated value; a bare(1 + 2)at the prompt prints3; a quoted string that
matches a command name (if false {"a"} else {"z"}) stays a string;
error make {msg: ("a" + "b")}evaluates the field; a runtime error binds
catch {|e| $e.msg}; the range1..3..9reads3as the second element (step 2)
like nu;matcharm bodies run a nestedmatchor a("x" | cmd)pipeline;
[[a b]]is a list-of-lists; anddef --wrapped foo […]definesfoo. -
String and value fixes.
mut s = "a"; $s = $s + "b"/+=/++=
concatenate;str kebab-case/snake-caseno longer double the separator on
CONST_NAME;$env.HOME?drops the optional marker; an empty string keeps its
type in a list/record/fold;str substring (-3)..(-1)works; and a filesize
serializes to nuon as a round-trippable<bytes>b. -
A
let x = {k: "v"}record no longer corrupts a string value (it had started
turning the value into a phantom key), andupdate/upsertwith a dotted-path
closure (upsert a.b {|v| $v + 10}) no longer reads uninitialised memory. -
valueson a table returns each column's values;rejecttakes a nested
cell-path.[[name meaning]; [ls list] [mv move]] | valuesis now
[[ls, mv], [list, move]](it used to pass the table through), and
{a: {b: 3, c: 5}} | reject a.bremoves the nested key to give{a: {c: 5}}. -
A
[[ … ]]literal with a;is always a table, even with commas in the
header.[[a, b]; [1, 2]]and[[a, b]; [1, 2] [3, 4]] | reject 1parse
correctly again (a regression from the comma-list change, where the comma was
read as "list literal" before the;was seen). -
String interpolation works inside closures, and escaped parens unescape.
echo $"v ($x)"/echo $'($k) ($v)'insideeach/items/dono longer
word-splits the interpolated value (it was producing[[v, 1]]instead of
["v 1"]); the interpolation is now self-delimiting so a re-lexed closure body
keeps it as one argument. Separately,$"escaped \(not interp\)"now renders
escaped (not interp)instead of leaking the backslashes. -
flattenturns a record into a table and expands a list column.
{a: 1, b: 2} | flattenis[[a, b]; [1, 2]]; a single list-valued field
expands into rows ({a: x, d: [1 2 3]} | flatten→ three rows), a record-valued
field merges into the parent, andflatten <col>picks the column. Previously a
record passed straight through unchanged. -
where COL =~ REGEXdoes a real regex match, not a substring.
where n =~ 'b.r',where n =~ '^a',where f =~ '.txt$'now match like nu
(they were doing a literalstrstr, so any metacharacter returned nothing). Also
fixes anchored^/$matching against a table cell (ZY-049). -
A record-returning closure body works in every higher-order filter.
each/items/reduce/where/filter/any/all/collect/chunk-by/
skip-while/skip-until/take-while/take-untilno longer mis-parse a
{|x| {a: $x}}body as a straya:command (the closure-brace stripper used to
eat the record literal's own closing brace). -
[[a, b], [c, d]]and[[N, u, s]]parse as list literals, not bash tests.
A comma-bearing[[ … ]]was dispatched to the POSIX[[ … ]]test builtin
("test: extra argument"); it is now a list-of-lists, while genuine
[[ -f x ]]/[[ 5 -gt 3 ]]tests still work. -
An unmatched glob passes through literally in the interactive shell, so
zy -cand interactivezyagree.[…] | get A?now works at the prompt (the
interactive globber used to rejectA?with "no matches found"); a real glob
still expands, and zsh-style erroring is available viasetopt nomatch. -
Record literals with multi-token values now parse correctly (ZY-047).
Computed values{a: (1 + 1)}, subexpressions{n: ([1 2 3] | length)},
space-separated lists{xs: [1 2 3]}, quoted scalars that stay strings
{a: "1"}, quoted keys{"a b": 1}, and nested records carrying any of these
{a: {b: (2 * 3)}}all work now; previously they were mangled (e.g.
{a: (1 + 1)}became{a: "(", "1": ")"}). The record-literal reassembler now
keeps each value as a balanced, quote-preserving span and the value evaluator
handles parenthesized/list/record spans. -
A whole-valued float keeps its
.0into nuonandto json, so
1.0 | to nuonis1.0and a float survives ato nuon | from nuon(or json)
round-trip as a float instead of turning into an int.into stringstill gives
1(matching Nushell), and scalar/table display is unchanged. -
to nuonquotes strings that look like a number, bool, or null, so a string
like"1"round-trips as a string instead of reparsing as an int. Real numbers,
bools, and ordinary words are unchanged. -
from tsvandfrom ssvinfer cell types (int, float, bool, null) like
from csvalready did and like Nushell, so a numeric TSV cell is an int rather
than a string. (from csv/tsv/ssvnow share one inference helper.) -
rename --column {old: new}renames columns by name instead of treating the
flag and map as positional names; columns not in the map keep their name.
Positionalrename <new1> [new2 …]is unchanged. -
zipreads the whole second list.[1 2 3] | zip [a b c]now gives
[[1, a], [2, b], [3, c]]instead of stopping at the first element; it uses the
same argument helper asappend/prepend. -
url parseexposes a decodedparamstable andurl joinaccepts a
paramsfield, sourl parse | get paramsand building a URL from
{scheme, host, params}both work (andurl parse | get params | url build-queryround-trips). -
drop nthhandles open-ended ranges.drop nth 2..drops from index 2 to
the end,..2drops the first three, and..<2the first two; closed ranges
and plain indices are unchanged. -
port <start> <end>returns the first free port in the range instead of
erroring. -
timeit { ... }prints only the measured duration. The block's return value
used to leak to the output (timeit { 1 + 1 }printed2then the time); it is
now discarded like Nushell, while an explicitprintinside the block still
shows. -
uniq-bysupports--count,-d, and-u.--countreturns a
[[value, count]; ...]table,-dkeeps only keys that repeat, and-ukeeps
only keys that appear once. They were parsed as positional arguments before. -
Fixed a heap buffer overflow in
parse. Every template parse (e.g.
"a=1" | parse "{k}={v}") read past the end of an internal buffer while
rewriting the pattern, because the buffer was terminated one step too late. It
is now terminated before that scan. (ZY-052) -
split rowhandles a--leading separator, keeps empty fields, and honors
--regex/--number.'a-b-c' | split row '-'works,'a,,b' | split row ','
keeps the empty middle field, and--number Nkeeps the remainder as the last
element. -
parseaccepts list input solines | parse "{k}={v}"produces one row per
line. -
sortsorts a record by key, or by value with-v/--values(it used to
pass records through unchanged), honoring-rand-i. -
transpose --ignore-titles/-iworks: it drops the leading
original-names column and shifts the auto column names down by one. -
datecommands.format date "%+"now prints an ISO-8601 timestamp instead
of the literal%+;date humanizerolls over to years past twelve months
(6 years ago, not78 months ago);date to-timezoneuses a datetime string
on the input instead of today's date; anddate list-timezonereturns a
[[timezone]; ...]table sowhere timezone =~ ...andget timezonework. -
update cellsworks on a table literal, e.g.[[a b]; [1 2]] | update cells { |v| $v + 1 }; the closure now runs over each record in alist<record>, not
just a table/record value. -
window --remainderkeeps the trailing partial windows ([1 2 3 4 5] | window 3 --remainderends with[4 5]and[5]). -
group-bywith no column groups a list by value into{ value: [items…] }
instead of erroring. -
url split-querykeeps duplicate keys and decodes values. It now returns a
[[key, value]; ...]table (soa=1&a=2yields two rows) and form-decodes both
fields (%20,%26, and+become space,&, space), instead of a record
that collapsed repeated keys.url build-queryaccepts that table as well
as a record, sourl split-query | url build-queryround-trips. -
url encodeandurl decodematch Nushell.url encodekeeps/,:,
and.by default (so a URL likehttps://x/foo baronly gets the space
encoded) and supports--allto encode everything but letters and digits.
url decodeno longer turns+into a space; it is strict, decoding only
%XX. -
pathcommand flags and shapes.path expandnow collapses./..
lexically (so/a/b/../cbecomes/a/c) even when the target doesn't exist or
with--no-symlink;path dirname --num-levels N --replace Xkeeps the
intermediate directories;path parse --extension tar.gzsplits the stem at the
given multi-part extension;path joinaccepts a{parent, stem, extension}
record sopath parse | path joinround-trips;path relative-toof an equal
path is empty; andwhichreports thebuilt-intype and omits unresolved
names (sowhich missing | lengthis 0). -
str replace --regexexpands$1/$2/${n}capture references. They were
copied through literally, so'abc' | str replace --regex '(.)(.)' '$2$1'gave
$2$1cinstead ofbac. References now splice in the matched group;
--no-expandkeeps them literal. -
str index-of --endreturns the last match (and--range N..Mlimits the
search);str substring 6..(open upper bound) returns to the end of the
string instead of"";char -u/-ishort flags work. -
into binarydefault is now the full int width, and--compacttrims (the
two were swapped).255 | into binaryis 8 bytes;--compactis 1. -
into intandinto floatvalidate the whole string.'123abc' | into int
now errors instead of returning 123, and'2.5 ' | into float(trailing
spaces) is accepted. -
into boolmatchestrue/falsecase-insensitively ('TRUE'works) and no
longer acceptsyes/no;'0'/'1'still convert. -
math abskeeps the type.-2hr | math absis2hr(was0), and the
absolute value of an int stays an int. -
first 0,take 0, andlast 0now return an empty list instead of one
element (the count was clamped up to 1).last 1now returns a single-element
list[x]to match Nushell, whilelastwith no count still returns the last
value directly. -
seq <first> <step> <last>reads the middle argument as the step.seq 1 2 10
now yields[1 3 5 7 9]instead of[1](the step and end were swapped). -
uniq --repeatedworks. zy only recognized--duplicates; Nushell's flag is
--repeated(both are accepted now). -
str containshonors--ignore-casewhen it trails the search string, e.g.
'hello' | str contains 'ELL' --ignore-case; previously a trailing flag was
mistaken for the text to search for. -
math sin/cos/tanandmath arcsin/arccos/arctannow honor--degrees.
The flag was silently ignored, so90 | math sin --degreescomputed the sine of
90 radians instead of 90 degrees. Forward trig now converts the input from
degrees, and the inverse functions return the result in degrees. Radians stay
the default. -
Pasting a table literal at the prompt no longer flashes "unmatched ']'" or
drops the prompt line. The interactive bracket-balance check treated[[and
]]as a paired unit, but a table literal[[a b]; [1 2] [3 4]]opens with
[[yet closes its header with a single]before the;. That corrupted the
depth count, so the line was misread as unbalanced (spurious error) and
incomplete (the prompt went into continuation mode). Brackets are now counted
one at a time, which balances table and nested-list literals correctly. (ZY-051) -
opennow parses more formats by file extension. It auto-parsed only
json/csv/xml;.toml,.yaml/.yml,.tsv,.nuon,.ssv, and.inicame
back as plain strings. They are now parsed into structured data (and--raw
still returns text), sosave data.tomlfollowed byopen data.tomlround-trips.
(ZY-050) -
savenow writes the format that matches the file extension. Saving a table
todata.csvused to write JSON regardless of the name.savenow serializes by
extension like Nushell, so.csv,.tsv,.yaml/.yml,.toml,.nuon,
.xml,.md, and.htmleach get their proper format (and round-trip back
throughopen/from <fmt>)..jsonand unknown extensions still write JSON;
--rawstill writes the value's string form unchanged. (ZY-037) -
bytes add,bytes remove,bytes replace, andbytes index-ofnow accept
the short flags. Only the long forms were recognized, so-a/--all,
-e/--end, and-i/--indexwere silently ignored and the command ran with
its default behavior. The short aliases now match their long forms. (ZY-039) -
findnow takes its flags and more than one search term. It used to use
only the first argument and treat flags as the text to search for, so
find --regex "^a"literally looked for "--regex".findnow supports
--regex(-r),--ignore-case(-i),--invert(-v), and several terms
(a row or item is kept if it matches any of them). (ZY-039) -
to tomlandto xmlnow serialize nested data instead of flattening or
dropping it.to tomlis a real serializer now: nested records become
[section]tables (dotted[a.b]for depth) and lists of records become
[[section]]arrays of tables, all valid TOML that round-trips through
from toml.to xmlfollows Nushell's{tag, attributes, content}shape (the
same onefrom xmlproduces) instead of dumping a generic<record>, so
from xml | to xmlround-trips exactly. (ZY-046) -
str ends-withandstr starts-withnow honor--ignore-case(-i). The
flag was being read as the text to match, so'FOO' | str ends-with -i 'oo'
always returned false. Case-insensitive matching works now, and the default
stays case-sensitive. (str contains -ialready worked.) (ZY-039) -
A closure that builds a record now works in
each. A body like
each {|r| {name: $r.first, city: $r.town}}failed with aname:: No such file
error becauseeachstripped its closure braces too greedily and ate the
record's own closing brace. Field projection and renaming with records now
work. (Record values that are parenthesized expressions, like
{n: ($x * 2)}, are still a separate open issue.) (ZY-048) -
to nuonno longer drops the data when you convert a parsed table. Piping a
table fromfrom csv,from tsv, orfrom ssvintoto nuonreturned an empty
[]and lost every row.to nuonnow serializes the table, renders a list of
records with matching columns as a NUON table the way Nushell does, and feeds its
output through the pipe so... | to nuon | from nuonround-trips. (ZY-041) -
eachandpar-eachno longer crash when mapping over a table. Running a
closure over a table of two or more rows and then serializing the result, for
example[{a:1} {a:2}] | each {|r| $r.a } | to nuon, aborted the shell with a
heap error. Table rows are stored inline, and the per-row value handed to the
closure was sharing that inline storage, so it got freed twice. Each row is now
copied into its own record before the closure runs. (ZY-035) -
stor updateno longer overwrites every row when you use Nushell's
--where-clause. zy only recognized--where, so passing--where-clause
(the Nushell spelling) silently dropped the filter and updated the whole table.
stor updatenow accepts--where-clauseand--table-namelike Nushell, and
rejects an unknown flag with an error instead of quietly running an unfiltered
update. A deliberate update with no where clause still updates all rows. (ZY-036)
Verify your download
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.43/zy_1.0.43_amd64.deb
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.43/SHA256SUMS
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.43/SHA256SUMS.minisig
curl -LO https://github.com/iskandarputra/zyshell/releases/download/v1.0.43/zy-release.pub
minisign -Vm SHA256SUMS -p zy-release.pub # confirms maintainer signature
sha256sum -c SHA256SUMS --ignore-missing # confirms the .deb hash matchesFull signing policy: docs/ops/RELEASE_SIGNING.md
VirusTotal scan
This release was uploaded to VirusTotal at publish time. Click through to see the live verdict from 65+ AV engines:
(Scans run within ~5 minutes of upload. If the page shows "Item not found", wait a moment and refresh.)
Try without installing
docker run --rm -it ghcr.io/iskandarputra/zyshell:1.0.43Install (Debian / Ubuntu)
Download zy_1.0.43_amd64.deb from the assets below, then:
sudo dpkg -i zy_1.0.43_amd64.deb
sudo apt-get install -f