1. Introduction

Shells can handle output in different ways. For example, there are many methods to add newlines to Bash commands. However, some shells behave differently when it comes to newlines at the end of output.

In this tutorial, we talk about unterminated output in the Zsh shell. First, we broadly explore the topic of output without explicit terminators. After that, we see how perhaps the most widely used shell handles unterminated output. Next, we delve into Zsh and its automated and semi-automated output termination. Finally, we go through ways to control the output termination feature of Zsh.

For clarity, we use $ as the Bash prompt and % as the Zsh prompt in code snippets.

We tested the code in this tutorial on Debian 12 (Bookworm) with GNU Bash 5.2.15 and Zsh 5.9. It should work in most POSIX-compliant environments unless otherwise specified.

2. Unterminated Output

In this case, we call output that doesn’t end with a specific terminator unterminated output. While we can have terminators like null, our main focus here in terms of both function and convenience is the newline character terminator.

To begin with, although all Linux text files should end with newlines in theory, this isn’t always the case:

$ cat file1.txt
Text file one contents.
$ cat file2.txt
Text file two contents.$

As we can see, the first file ends with a newline, but the second one doesn’t:

$ xxd -s -1 file1.txt
00000013: 0a                                       .
$ xxd -s -1 file2.txt
00000012: 2e                                       .

Here, xxd [-s]eeks and returns the -1 last character of each file. Immediately, we can notice one file has 13, while the other has only 12 bytes. Further, the last character of file1.txt is 0a, i.e., a line feed, and file2.txt ends with 2e (period).

That’s despite the fact that many editors place a newline character at the end of files automatically, even if we only appear to save one line:

$ vi file1.txt
[...]
Text File one contents.
[:x]

Hence, it’s not at all uncommon to see command output that doesn’t end with a newline. Even the ubiquitous printf, unlike echo, expects a manual newline character to end on one.

So, let’s explore such situations in two of the most widely used shells.

3. Bash Unterminated Output

Let’s take the basic printf example:

$ printf 'Output.'
Output.$

Without an explicit newline character, commands just hand stdout back to Bash without any corrections. As a result, the next prompt ($) appears at the end of the last output line with no spacing.

Of course, we can correct that ourselves:

Now, let’s see how Zsh handles this command.

4. Zsh Unterminated Output

We again use printf, but this time with Zsh:

% printf 'Output.'
Output.%
%

Notably, the shell automatically adds a % percent sign and newline to the end of our output.

More specifically, Zsh appends a specific set of characters:

  1. negative bolded % percent sign for a normal user or # octothorp for root to indicate the operation
  2. line of spaces minus one (79 for 80-character terminals) to ensure a new line begins after an unterminated one
  3. carriage return to bring the cursor back to the beginning of said line

This way, there are two alternatives depending on the last output character:

  • newline: insert indicator character at the beginning of the new X-character line, fill the rest of the line with X-1 spaces, and to the beginning of the same line, clearing up everything, effectively undoing the initial insertions
  • non-newline: insert indicator character after the last output character, go to a newline with the spaces, place cursor at the beginning of that new line, protecting the previous one from the 

Thus, the prompt ends up at the beginning of a fresh line, while the unterminated output is indicated by a highlighted and bolded % or # symbol. This feature is meant to aid the user in seeing such occurrences clearly.

5. Zsh Output Control

There are some ways we can modify the behavior of Zsh when it comes to unterminated lines.

5.1. PROMPT_CR and PROMPT_SP

Since line wrapping and a relatively fast terminal are required to ensure the correct cosmetics of unterminated line indication in Zsh, we might want to disable the feature in some environments.

In particular, the prompting options of set provide control over different aspects of the prompt. To enable options, we can either use set -o or setopt. On the other hand, set +o or unsetopt disables an option.

More specifically, we can leverage two options for unterminated output:

  • PROMPT_CR (default on): toggle inserting a before prompt output
  • PROMPT_SP (default on): insert spaces before the to preserve the current line, if populated

Notably, if PROMPT_CR isn’t set, PROMPT_SP has no effect.

For example, let’s see what happens when toggling PROMPT_CR:

% setopt PROMPT_CR
% printf 'Output.'
Output.%
% unsetopt PROMPT_CR
% printf 'Output.'
Output.% printf 'Next command.'
Next command.%

As expected, without PROMPT_CR, we get the same behavior as Bash.

Alternatively, we can leave PROMPT_CR on, but only disable PROMPT_SP:

% setopt PROMPT_CR
% unsetopt PROMPT_SP
% printf 'Output.'
%

In this case, we don’t see the output line, because wipes the line without moving to the next one with spaces. Of course, we also don’t see the highlighter character.

5.2. PROMPT_EOL_MARK

The $PROMPT_EOL_MARK special variable sets the highlighter character that Zsh uses to mark unterminated output lines:

% setopt PROMPT_CR
% setopt PROMPT_SP
% printf 'Output.'
Output.%
% PROMPT_EOL_MARK=!
% printf 'Output.'
Output.!
%

In other words, we can change the default % and # characters.

Notably, the default PROMPT_EOL_MARK value includes formatting and undergoes expansion:

%B%S%#%s%b

Let’s break this down:

  1. enable [%B]oldface mode
  2. enable [%S]tandout mode
  3. %# expands to # for a privileged shell, and % otherwise
  4. disable [%s]tandout mode
  5. disable [%b]oldface mode

We can employ any of these and other prompt expansion features for our value of PROMPT_EOL_MARK as well.

6. Summary

In this article, we talked about the handling of unterminated lines in the Zsh shell.

In conclusion, unlike the ubiquitous Bash, Zsh has several features to ensure the proper formatting of output and following prompts, even when the output doesn’t end with an explicit newline.