Open-Source Wikis

/

fd

/

Systems

/

Output

sharkdp/fd

Output

Active contributors: sharkdp, tmccombs, tavianator

Purpose

When fd is not running an external command, the receiver writes each match to stdout. The output subsystem is responsible for: choosing whether to colorise, splitting the path between styled "parent" and styled "basename", appending a trailing separator for directories, applying any --path-separator rewrite, wrapping in OSC 8 hyperlinks, applying a --format template, and ending each entry with either \n or \0.

Directory layout

src/
├── output.rs         # print_entry, print_entry_format, _colorized, _uncolorized,
│                     # print_trailing_slash, replace_path_separator
├── dir_entry.rs      # DirEntry::stripped_path / .style / Colorable impl
└── hyperlink.rs      # PathUrl percent-encoder, host() lookup

Key abstractions

Item File Purpose
print_entry src/output.rs Top-level entry point used by ReceiverBuffer::print. Decides between format/colorized/uncolorized printers and wraps in a hyperlink if needed.
print_entry_format src/output.rs Used when Config::format is set. Calls FormatTemplate::generate on the stripped path.
print_entry_colorized src/output.rs Splits the path at the last component, styles the parent with Indicator::Directory, styles the basename with the entry-specific style, and writes both.
print_entry_uncolorized src/output.rs The plain printer. On Unix without a path-separator rewrite, writes raw bytes to preserve invalid-UTF-8 filenames.
print_trailing_slash src/output.rs Writes actual_path_separator after directories, optionally styled.
PathUrl src/hyperlink.rs Display implementation that percent-encodes path bytes and produces a file://hostname/... URL.

How a single entry is printed

graph TD
    entry[DirEntry] --> stripped[entry.stripped_path]
    stripped --> hyperlink_q{config.hyperlink?}
    hyperlink_q -->|yes| osc_open[Write OSC 8 open<br/>'\x1B]8;;<url>\x1B\\']
    hyperlink_q -->|no| pick
    osc_open --> pick{which printer?}

    pick -->|config.format set| fmt[print_entry_format]
    pick -->|else, config.ls_colors| col[print_entry_colorized]
    pick -->|else| plain[print_entry_uncolorized]

    fmt --> trail[print_trailing_slash]
    col --> trail
    plain --> trail
    trail --> hyperlink_close{had hyperlink?}
    hyperlink_close -->|yes| osc_close[Write OSC 8 close<br/>'\x1B]8;;\x1B\\']
    hyperlink_close -->|no| sep
    osc_close --> sep[Separator: '\\n' or '\\0']

The separator decision is made in print_entry:

if config.null_separator {
    write!(stdout, "\0")
} else {
    writeln!(stdout)
}

-0/--print0 flips this. It is also passed through to --exec so per-result blocks are \0-terminated.

Path stripping

DirEntry::stripped_path(config) returns strip_current_dir(self.path()) when Config::strip_cwd_prefix is true. The CLI rule (in Opts::strip_cwd_prefix) is:

  • No positional search path was given, and
  • --strip-cwd-prefix was not explicitly set to Never, and either
    • It was explicitly set to Always, or
    • The default rule applies: !(null_separator || has_command) — i.e. plain interactive output, no --print0, no -x/-X/-l.

This mirrors what most users expect: piping or executing yields paths starting with ./, while a plain fd README shows README rather than ./README.

Color selection

The colorised printer splits the path so the parent segments are coloured by the directory indicator (Indicator::Directory from LsColors) while the last component carries its own style (looked up via lscolors::style_for_path_with_metadata against the cached Style). This is what gives fd its characteristic look where directory components are uniformly styled and only the file basename varies in color.

The actual style for the basename is computed once and cached on DirEntry::style (OnceCell<Option<Style>>). The walker calls entry.style(ls_colors) inside spawn_senders while it is still in worker threads so style computation is parallelised; the receiver only does the writes.

A subtle but important separator handling: print_entry_colorized walks the path string character by character starting at parent.to_string_lossy().len() and increments offset past every is_separator(c) it encounters. This guarantees that paths like dir/foo and dir//foo both split correctly between styled-parent and styled-basename.

Custom path separator

Config::path_separator (set by --path-separator) flows two ways:

  • In print_entry_uncolorized_base and the colorized printer, replace_path_separator(path, sep) rewrites every MAIN_SEPARATOR to the user's separator.
  • In print_trailing_slash, the rewrite is applied via Config::actual_path_separator (which is always populated, falling back to MAIN_SEPARATOR.to_string()).

On Windows there is also a default_path_separator (src/filesystem.rs) that returns Some("/") when the MSYSTEM env var is set, so MSYS2/Git Bash/Cygwin shells get forward slashes by default.

Trailing slashes for directories

print_trailing_slash writes Config::actual_path_separator after every directory entry, optionally with the directory style. This is why fd -td prints src/ rather than just src. The check is entry.file_type().is_some_and(|ft| ft.is_dir()).

--hyperlink[=auto|always|never] controls Config::hyperlink. When true, print_entry brackets each output with the OSC 8 escape sequences:

\x1B]8;;<url>\x1B\\<text>\x1B]8;;\x1B\\

The URL is constructed by PathUrl::new(path), which:

  • Calls absolute_path(path) from src/filesystem.rs (handles Windows verbatim prefix stripping).
  • On Unix, looks up the hostname once via nix::unistd::gethostname() and caches it in a OnceLock<String>.
  • Percent-encodes bytes that are not in the ASCII unreserved set + /, :, -, ., _, ~.
  • On Windows, converts \ to / rather than encoding it.

The fallback on Windows is host() returning "/", which yields URLs of the form file:///<encoded-path>.

The unit tests in src/hyperlink.rs cover the encoding (e.g., "$*\x1bßé/∫😃\x07""%24%2A%1B%C3%9F%C3%A9/%E2%88%AB%F0%9F%98%83%07").

The Auto mode of --hyperlink resolves to whatever colored_output was decided as. Many terminals implement OSC 8 only in colored output anyway, so this matches user expectations.

--format

When Config::format is set, the colorised path layout is bypassed and FormatTemplate::generate produces a single OsString from the stripped path, which is written via to_string_lossy(). The trailing slash, hyperlink wrapping, and separator choice still apply on top.

Performance notes

src/output.rs carries four // TODO: this function is performance critical and can probably be optimized comments above print_entry, print_entry_format, print_entry_colorized, and print_entry_uncolorized_base. The relevant micro-optimisations already in place:

  • The output is wrapped in io::BufWriter::new(io::stdout().lock()) (set up in WorkerState::receive).
  • Style is precomputed in worker threads, not in the printer.
  • On Unix without a separator rewrite, print_entry_uncolorized writes raw bytes via write_all(stripped.as_os_str().as_bytes()), sidestepping to_string_lossy() and preserving non-UTF-8 filenames.

Entry points for modification

  • Change directory-trailing-slash behaviour. Edit print_trailing_slash. The check is currently a strict is_dir() — symlinks to directories are not given a trailing slash today.
  • Change the hyperlink URL format. Edit PathUrl's Display impl in src/hyperlink.rs.
  • Add a new top-level decoration (e.g., a prefix per result). Edit print_entry. Be mindful that --format and --exec paths flow through the same function.
  • Walker — the producer side of the path stream that ends up here.
  • Format templates — the engine used by --format.
  • CLI parsing — where color/hyperlink/strip-cwd decisions are made.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Output – fd wiki | Factory