Detecting Output Destination in CLI Programs

Programs can detect where their output is going (terminal vs pipe vs file) and modify their behavior accordingly. This is why tools like rg, ls, and git show colors when output goes to your terminal but plain text when piped to other commands.

How It Works in C

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>

int main() {
    if (isatty(STDOUT_FILENO)) {
        printf("\033[32mOutput going to terminal - colors enabled!\033[0m\n");
    } else {
        printf("Output piped/redirected - plain text\n");
    }
    
    // More detailed detection
    struct stat st;
    fstat(STDOUT_FILENO, &st);
    
    if (S_ISREG(st.st_mode)) {
        printf("Redirected to regular file\n");
    } else if (S_ISFIFO(st.st_mode)) {
        printf("Piped to another command\n");
    } else if (S_ISCHR(st.st_mode)) {
        printf("Connected to terminal/character device\n");
    }
    
    return 0;
}

Ripgrep Example

Ripgrep automatically adjusts its output:

# Colors and formatting when going to terminal
rg "function" main.c

# Plain text when piped (no ANSI color codes)
rg "function" main.c | head -5

# Override the auto-detection
rg "function" main.c --color=always | head -5  # Force colors
rg "function" main.c --color=never             # Force plain text

The difference: terminal output includes ANSI color codes and formatting, while piped output is clean text suitable for further processing.

Bash Detection

#!/usr/bin/env bash


# Basic TTY test
if [ -t 1 ]; then
    echo -e "\033[32m✓ Terminal output - using colors\033[0m"
else
    echo "✓ Piped/redirected output - plain text"
fi

# Practical usage pattern
if [ -t 1 ]; then
    RED='\033[0;31m'
    GREEN='\033[0;32m'
    BLUE='\033[0;34m'
    NC='\033[0m'
else
    RED=''
    GREEN=''
    BLUE=''
    NC=''
fi

echo -e "${GREEN}Success:${NC} Operation completed"
echo -e "${RED}Error:${NC} Something went wrong"

[-t 1] test what is it?

The [-t 1] breaks down as:

  • -t = terminal test (or tty test)
  • 1 = file descriptor 1 (which is stdout)

So -t 1 means "test if file descriptor 1 (stdout) is connected to a terminal/TTY."

[ -t 0 ]  # Is stdin a terminal?
[ -t 1 ]  # Is stdout a terminal?
[ -t 2 ]  # Is stderr a terminal?

More on file descriptors in File Descriptor.

Test the Bash Script

# Direct to terminal (shows colors if terminal supports them)
./script.sh

# Piped (plain text output)
./script.sh | cat

# Redirected to file (plain text)
./script.sh > output.txt

Key Points

  • isatty() in C and [ -t 1 ] in bash test if stdout is a terminal
  • File descriptor 1 = stdout, 0 = stdin, 2 = stderr
  • Well-designed CLI tools automatically adapt their output format
  • You can usually override auto-detection with flags like --color=always/never
  • This pattern makes tools both human-friendly and script-friendly

Backlinks