# Understanding the fzf `--become` Action: How It Differs from Shell Piping

> Learn how fzf's --become action replaces processes using syscall.Exec, differing from shell piping by preserving terminal state and avoiding quoting issues. Understand this powerful fzf feature.

- Repository: [Junegunn Choi/fzf](https://github.com/junegunn/fzf)
- Tags: deep-dive
- Published: 2026-03-01

---

**The `--become` action in fzf replaces the current process with a new command using `syscall.Exec`, preserving terminal state and avoiding the overhead and quoting issues of traditional shell piping.**

The `junegunn/fzf` repository provides a powerful interactive fuzzy finder for the command line. Among its advanced features, the `--become` action stands out as a specialized key-binding mechanism that fundamentally changes how fzf hands off control to other programs.

## What Is the fzf `--become` Action?

`--become` is a key-binding action that instructs fzf to **replace itself with another program** rather than merely launching a child process and returning to the selector. When triggered, fzf writes the target command to a temporary file, exits with a special status code, and allows a proxy process to execute the replacement.

In [`src/terminal.go`](https://github.com/junegunn/fzf/blob/main/src/terminal.go), the action is defined as `actBecome` and routed through a `reqBecome` request when a binding like `enter:become(vim {})` is triggered. The implementation posts the request to the main loop, which prepares the handoff.

The exit mechanism relies on the constant `ExitBecome = 126` defined in [`src/constants.go`](https://github.com/junegunn/fzf/blob/main/src/constants.go). When fzf exits with this code, the proxy process (handled in [`src/proxy.go`](https://github.com/junegunn/fzf/blob/main/src/proxy.go)) recognizes the signal and reads the temporary `*.become*` file containing the command and optional environment variables.

Finally, `Executor.Become` in [`src/util/util_unix.go`](https://github.com/junegunn/fzf/blob/main/src/util/util_unix.go) (and the Windows equivalent) calls `syscall.Exec`, which **overwrites the current process image** with the new command. The terminal file descriptors remain intact, and the new program inherits the same TTY.

## How `--become` Differs from Shell Piping

Understanding the distinction between `--become` and traditional shell piping is crucial for choosing the right integration pattern. The differences span process models, terminal handling, and data passing.

**Process Model**

- **`--become`**: fzf exits with code 126, the proxy reads the command file, and `syscall.Exec` replaces the process. No child process remains; fzf is completely gone.
- **Shell piping**: The shell forks fzf as a child process, creates a pipe, and forks another child for the receiving command. Both processes run concurrently, and fzf returns its own exit status independently.

**Terminal State**

- **`--become`**: The terminal is kept intact because the process image is replaced, not terminated. This avoids the "blank line" or cursor positioning issues that occur when fzf exits normally with `Ctrl-C` or empty selection.
- **Shell piping**: The piped command must explicitly open `/dev/tty` if it needs terminal interaction, as its stdin is connected to the pipe, not the terminal.

**Multi-Selection Handling**

- **`--become`**: Selections are safely expanded using the `QuoteEntry` helper in [`util_unix.go`](https://github.com/junegunn/fzf/blob/main/util_unix.go). Each selected item becomes a separate argument in the exec call, preserving spaces and special characters without additional quoting.
- **Shell piping**: Lines are concatenated with newlines. Handling filenames with spaces requires `xargs -0` or similar parsing, adding complexity and potential for errors.

**Performance**

- **`--become`**: No extra fork/exec cycle, no pipe buffer overhead, and no I/O through pipes.
- **Shell piping**: Requires an additional process spawn and data transfer through the pipe buffer.

## Practical Examples of Using `--become`

### Opening a Single File in Vim

The most common use case is opening a selected file directly in an editor without returning to the shell:

```bash
fzf --bind 'enter:become(vim {})'

```

When you press **Enter**, fzf writes `vim <selected_path>` to the temporary become file, exits with status 126, and the proxy executes `syscall.Exec` to replace the process with Vim. The terminal state is preserved exactly as it was under fzf.

### Editing Multiple Files with Proper Quoting

For multi-selection workflows, use the `{+}` placeholder to pass all selected items as separate arguments:

```bash
fzf --multi --bind 'enter:become(vim {+})'

```

The `QuoteEntry` function in [`src/util/util_unix.go`](https://github.com/junegunn/fzf/blob/main/src/util/util_unix.go) ensures that filenames containing spaces are properly quoted before being passed to the exec system call. This avoids the common pitfall of `fzf | xargs vim` where spaces break argument parsing.

### Passing Custom Environment Variables

You can prepend environment variables to the command, which are parsed and passed to the new process:

```bash
fzf --bind 'enter:become(FOO=bar BAZ=qux vim {})'

```

In [`src/proxy.go`](https://github.com/junegunn/fzf/blob/main/src/proxy.go), the proxy extracts these `KEY=VALUE` pairs into an environment slice (lines 44-46) and passes them to `Executor.Become`, which includes them in the `syscall.Exec` call. This allows you to configure the target program's environment without affecting the shell that launched fzf.

## Summary

- **`--become` is a process replacement mechanism**, not a subprocess spawn. It uses `ExitBecome` (126) and `syscall.Exec` to transform the fzf process into the target command.
- **Terminal state is preserved** because the TTY file descriptors remain attached to the same process group, avoiding the blank-line issues common with normal exit.
- **Argument handling is safer** than piping. The `QuoteEntry` helper ensures spaces and special characters in selections are handled correctly when expanded via `{}` or `{+}`.
- **Performance is optimal** because there is no pipe buffer, no extra fork/exec cycle, and no shell interpretation overhead.
- **Source implementation** spans [`src/terminal.go`](https://github.com/junegunn/fzf/blob/main/src/terminal.go) (action routing), [`src/constants.go`](https://github.com/junegunn/fzf/blob/main/src/constants.go) (exit code), [`src/proxy.go`](https://github.com/junegunn/fzf/blob/main/src/proxy.go) (proxy logic), and [`src/util/util_unix.go`](https://github.com/junegunn/fzf/blob/main/src/util/util_unix.go) (syscall execution).

## Frequently Asked Questions

### What exit code does fzf use for the become action?

Fzf exits with status code **126**, defined as `ExitBecome` in [`src/constants.go`](https://github.com/junegunn/fzf/blob/main/src/constants.go). This specific code signals the proxy process that it should read the temporary `*.become*` file and execute the replacement command rather than simply returning to the shell.

### Can I use become on Windows?

Yes, but the implementation differs. While Unix systems use `syscall.Exec` in [`src/util/util_unix.go`](https://github.com/junegunn/fzf/blob/main/src/util/util_unix.go) to replace the process image, Windows does not support a direct equivalent of `execve`. The Windows implementation in [`src/util/util_windows.go`](https://github.com/junegunn/fzf/blob/main/src/util/util_windows.go) simulates the behavior using process creation and exit handling, though the terminal state preservation characteristics may vary slightly from the Unix implementation.

### How does become handle filenames with spaces?

The `become` action handles filenames with spaces safely through the `QuoteEntry` helper function in [`src/util/util_unix.go`](https://github.com/junegunn/fzf/blob/main/src/util/util_unix.go). When using placeholders like `{}` or `{+}`, fzf properly escapes each selection before passing it to `syscall.Exec`. This ensures that files with spaces, quotes, or other special characters are treated as distinct arguments by the target program, eliminating the quoting issues common with traditional `xargs` or shell piping approaches.

### Is become better than execute for launching editors?

Generally yes, for launching interactive programs like editors. The `execute` action spawns a child process and waits for it to return, which can cause terminal state issues or require complex TTY handling. In contrast, `become` uses `syscall.Exec` to replace the fzf process entirely, preserving the terminal state and handing over control completely to the new program. This makes `become` ideal for workflows where you want to open a file in Vim, Emacs, or a debugger directly from the fzf interface without returning to the shell first.