fish 3.1 makes pipeline results and captured text more explicit
fish 3.1 adds pipeline statuses, tracing, built-in timing and string collect. Together they expose useful distinctions between command execution, returned data and diagnostic output.
Shell programs pass several different kinds of information through a compact syntax. A pipeline returns data and exit statuses. A command substitution turns output into arguments. Functions can participate in a pipeline while executing inside the shell. Confusing those boundaries can make a short command difficult to explain.
The February 12 release of fish 3.1.0 improves those boundaries with per-command pipeline statuses, execution tracing, a built-in timing command and string collect. The tagged changelog carries the larger feature list under the preceding beta and identifies the final release's additional corrections.
I like fish's emphasis on an approachable interactive environment. These additions make that preference concrete for scripting as well. They expose information the shell already has and provide a direct way to control how captured text becomes arguments.
A pipeline has more than one result
The 3.1 manual describes status as the exit status of the last process in the last job to exit. The new pipestatus retains the exit statuses of all processes in the last executed pipeline.
That list can distinguish a failed producer from a successful consumer. A later command can process the input it received successfully even when the earlier command failed to produce everything intended. Looking only at one status loses the distinction.
false | true
set saved_pipeline_status $pipestatus
printf '%s\n' $saved_pipeline_status
This deliberately simple example gives the stages different outcomes. Saving the list immediately keeps it available while later commands inspect or report it. The shell does not need to encode the component statuses into a custom string before the caller can use them.
The interpretation still belongs to the script. Some commands use nonzero statuses for expected conditions. A wrapper needs to understand its component commands before deciding whether a particular result should fail the whole operation. The new list provides the necessary evidence without substituting for those command contracts.
Multiline text need not become several arguments
Ordinary fish command substitution splits output lines into separate arguments. The manual documents that behavior explicitly. It is convenient for commands returning a list of names, but it is unsuitable when the result is one multiline document.
The string collect reference provides a direct solution. As the final pipeline stage in a substitution, it collects input into one output argument and prevents the usual newline splitting.
set document (printf 'alpha\nbeta\n' | string collect)
printf '%s\n' "$document"
The variable represents one multiline value. The first newline separates lines inside that value instead of creating a second list element. That matters when the receiving command expects one argument containing a document rather than separate arguments naming independent items.
By default, string collect trims trailing newlines from its input. Its --no-trim-newlines option preserves them. A script collecting exact file contents therefore has an explicit choice about the final line terminators.
set contents (printf 'alpha\nbeta\n' | string collect -N)
The distinction is small in a terminal display and substantial in a data transformation. A final newline may be part of the content passed to another tool. Preserving internal line boundaries and preserving trailing newlines are related but separate requirements.
Collection also has its own exit status
The collect documentation says it returns success when any output argument is nonempty and failure otherwise. A command that successfully produces no text can consequently be followed by a collection command with a nonzero status.
That behavior reinforces the distinction between successful command execution and nonempty output. A script checking for content can use the collector's status deliberately. A script trying to diagnose the producer needs evidence about the producer itself.
When invoked with explicit arguments instead of pipeline input, string collect preserves those arguments separately. Its name should therefore not be read as a universal promise that any invocation collapses every supplied argument into one value. The documented input mode is part of the operation.
The manual also describes a default command-substitution output limit of 100 MiB. Exceeding it fails the entire containing command and sets status 122. Capturing output in one variable remains a bounded operation, even when newline splitting has been disabled.
This gives a useful division of responsibility. The producer determines the bytes, collection determines argument boundaries and trimming, and the shell applies the capture limit. Each can affect the result for a different reason.
Timing can include shell functions and blocks
The time reference describes fish's new timing facility as measuring a command or block. Because it operates inside fish, it can time shell functions and builtins as well as external commands.
time begin
printf '%s\n' first
printf '%s\n' second
end
This is a shell block, so an external executable cannot simply interpret it as one of its own command arguments. The built-in timing facility has access to the shell's execution structure and can measure the whole block.
The documented report separates elapsed time from user and system CPU time, and distinguishes fish work from external work. Those categories help describe where time was spent without assuming that a slow elapsed duration means the shell consumed that much CPU.
The reference also states that the timing results cannot currently be redirected. For scripts that need to retain timing reports as data, this is a practical constraint. A script should not assume that the normal output redirection rules provide a structured timing export in this version. That limitation is part of the feature shipped in February.
Execution traces explain the command path
The manual describes a nonempty fish_trace as printing commands before execution. Traces go to standard error by default, with a debugging-output option for another destination. This supplies a view of executed commands alongside the ordinary data output.
The release also revises internal debugging options. The previous debug-level option is removed, and the replacement debug option accepts categories. A separate command lists those categories. Internal debugging and shell execution tracing therefore have distinct controls.
For ordinary shell work, a trace can reveal that a function expanded into a different command sequence than expected. Timing can establish that the sequence took substantial time. Pipeline statuses can show which stages returned errors. Each observation answers a different question about the execution.
I prefer having those observations available directly in the shell. A friendly interface should help explain the commands it executes, including commands assembled from functions and substitutions. The additions are useful because they preserve details that a single final output line cannot contain.
Pipeline buffering changes when functions receive input
The changelog says fish no longer buffers pipes to the last function in a pipeline. It associates the change with commands that appeared to block or hang. That concerns input delivery before completion, rather than the eventual status after a pipeline exits.
A function at the end of a pipeline can need input while the producer continues running. Buffering the producer's output before the function consumes it changes the interaction between stages. Removing that boundary addresses an execution behavior that extra status reporting alone cannot repair.
This also distinguishes pipelines from command substitution. A substitution captures output to construct later arguments. A pipeline connects ongoing commands. Treating both as a generic form of output capture obscures why buffering can be necessary in one place and disruptive in another.
The release also accepts a command-specific environment assignment with VAR=val cmd syntax. That lets an invocation supply an environment value without making a persistent change to the shell's variables. It is another example of giving an operation a narrower scope. The familiar spelling helps when entering commands, while the rest of a fish script still follows fish's own language rules.
The stable release includes beta corrections
The final release notes list corrections beyond the beta. They include a brace-handling regression that removed spaces despite no brace expansion occurring, excessive errors for Unicode output in unsuitable locales, and build fixes for several platforms.
Those entries establish what changed between the January preview and the February stable release. The larger scripting facilities were already available in the beta. A precise release account should acknowledge that history instead of presenting every feature as newly written during the interval.
The combined result is a shell with clearer ways to preserve multiline text and inspect command execution. string collect controls how captured data becomes arguments, while pipeline statuses retain individual command outcomes. Built-in timing and tracing supply additional evidence without changing the data contract of the command being inspected.