Skip to main content
Pipes allow you to connect multiple commands, sending the output of one command as input to the next.

Pipe Operator (|)

Syntax

command1 | command2

Description

The pipe operator (|) takes the standard output of the command on the left and passes it as standard input to the command on the right.
1

Command 1 executes

The first command runs and produces output.
2

Output is captured

Instead of displaying on screen, output is captured in a pipe buffer.
3

Command 2 receives input

The second command reads from the pipe as if reading from a file.
4

Results display

The final command’s output is shown on screen.

Basic Pipe Examples

Simple Two-Command Pipes

# List files and search for .txt files
ls -la | grep .txt

# Show processes and search for specific process
ps aux | grep minishell

# Count files in directory
ls | wc -l

# Display file and page through content
cat large_file.txt | less

Filtering Output

# Find error messages in logs
cat logfile.txt | grep error

# Search environment for specific variable
env | grep PATH

# Filter and count
ls -la | grep ".c$" | wc -l

Chaining Multiple Commands

You can chain more than two commands together:
# Three commands
ls -la | grep .c | sort

# Four commands
cat file.txt | grep "error" | sort | uniq

# Five commands
ps aux | grep python | awk '{print $2}' | sort -n | head -5

Practical Multi-Pipe Examples

# Find, filter, sort, and count unique items
cat data.txt | grep "user" | sort | uniq | wc -l

# Process log files
cat access.log | grep "404" | awk '{print $1}' | sort | uniq -c | sort -nr

# System monitoring
ps aux | awk '{print $3}' | sort -rn | head -10
Minishell supports unlimited pipe chaining. Each command in the pipeline runs in its own process, allowing true parallel execution.

How Pipes Work

Data Flow

command1 | command2 | command3
Data flows like this:
[command1] --stdout--> [pipe1] --stdin--> [command2] --stdout--> [pipe2] --stdin--> [command3] --stdout--> [terminal]

Process Creation

When you execute a pipeline:
1

Pipeline parsing

Minishell counts the pipes to determine how many commands to execute.
2

Process creation

Each command gets its own child process (via fork()).
3

Pipe creation

Pipes are created to connect each pair of commands.
4

Execution

All commands run simultaneously, passing data through pipes.
5

Waiting

Minishell waits for all child processes to complete.
The number of child processes equals the number of commands (pipes + 1). For example, cmd1 | cmd2 | cmd3 creates 3 processes.

Combining Pipes with Redirections

Pipes and redirections can be used together for powerful combinations:

Input Redirection with Pipes

# Read from file, pipe to command
< input.txt grep "error" | sort

# Equivalent to:
cat input.txt | grep "error" | sort

# Multiple filters
< data.txt grep "user" | grep "active" | wc -l

Output Redirection with Pipes

# Pipe commands, save final output
ls -la | grep .txt > text_files.txt

# Multi-stage processing
cat data.txt | grep "error" | sort > sorted_errors.txt

# Append to file
ps aux | grep python >> process_log.txt

Both Input and Output Redirections

# Read from file, process, write to file
< input.txt sort | uniq > output.txt

# Complex processing
< logs.txt grep "ERROR" | sort | uniq -c > error_summary.txt

Heredoc with Pipes

# Pipe heredoc through commands
cat << EOF | grep "error" | wc -l
info: starting
error: connection failed
warning: retrying
error: timeout
info: stopping
EOF
# Output: 2

# Multiple pipes
cat << EOF | sort | uniq
apple
banana
apple
cherry
banana
EOF
# Output:
# apple
# banana
# cherry

Redirections in the Middle

# Save intermediate results
cat file.txt | grep "error" > errors.txt | wc -l

# Redirect specific commands
ls -la > all_files.txt | grep ".c" > c_files.txt
Each command in a pipeline can have its own redirections. The redirection applies only to that specific command, not the entire pipeline.

Built-in Commands with Pipes

Built-in commands work in pipes:
# Export and filter
export | grep PATH

# Environment variables
env | grep USER | sort

# Echo through pipe
echo "Hello World" | grep World

# PWD in pipeline
pwd | cat
When a built-in command is part of a pipeline, it executes in a child process (not the main shell process), so changes like cd or export don’t affect the shell environment.

Limitations and Edge Cases

Empty Commands

# Pipe with no command before or after
| grep error
# Output: syntax error: pipes

grep error |
# Output: syntax error: pipes

Multiple Consecutive Pipes

# Double pipe is an error
ls || grep
# Output: syntax error: pipes

# Triple pipe is an error
ls ||| grep
# Output: syntax error: pipes
Minishell does not support logical operators like || (OR) or && (AND). Only the pipe operator | for data flow is supported.

Pipe Before Redirection Operator

# Invalid syntax
ls | < input.txt grep error
# Output: syntax error: pipes

grep error | > output.txt
# Output: syntax error: redir

Performance Considerations

Buffering

Pipes use kernel buffers (typically 64KB):
# Small data - no blocking
echo "hello" | cat

# Large data - buffered automatically
cat large_file.txt | grep pattern

Process Overhead

Each command in a pipeline runs in a separate process:
# Creates 5 processes
cmd1 | cmd2 | cmd3 | cmd4 | cmd5
For long pipelines with many commands, there’s a small overhead from process creation and context switching, but this is usually negligible for interactive use.

Advanced Pipeline Patterns

Sorting and Uniqueness

# Find unique items
cat list.txt | sort | uniq

# Count occurrences
cat data.txt | sort | uniq -c | sort -rn

# Top 10 most frequent
cat access.log | cut -d' ' -f1 | sort | uniq -c | sort -rn | head -10

Text Processing

# Extract specific fields
cat data.csv | cut -d',' -f2 | sort | uniq

# Count lines, words, characters
cat document.txt | wc

# Filter and transform
cat data.txt | grep "pattern" | tr '[:lower:]' '[:upper:]' | sort

System Monitoring

# Process monitoring
ps aux | grep -v grep | grep python | wc -l

# Disk usage
df -h | grep "/dev/" | awk '{print $5}' | sort -rn

# Network connections
netstat -tuln | grep LISTEN | wc -l

Exit Status with Pipes

The exit status of a pipeline is the exit status of the last command:
# Last command succeeds
ls /nonexistent | grep txt
echo $?
# Output: 0 (grep succeeded, even though ls failed)

# Last command fails
echo "hello" | grep "goodbye"
echo $?
# Output: 1 (grep found no match)
Minishell stores the exit status of the last command in the pipeline in the $? variable, following standard shell behavior.

Practical Use Cases

Log Analysis

# Find errors in logs
cat /var/log/app.log | grep ERROR | wc -l

# Get unique error types
cat app.log | grep ERROR | cut -d':' -f2 | sort | uniq

# Time-based filtering
cat access.log | grep "2024-03" | grep "404" | wc -l

File Management

# Find large files
ls -lh | sort -k5 -h | tail -10

# Count file types
ls | grep "\.." | rev | cut -d'.' -f1 | rev | sort | uniq -c

# Find old files
ls -lt | tail -20

Data Processing

# CSV processing
cat data.csv | grep "active" | cut -d',' -f1,3 | sort

# JSON processing (with jq)
cat data.json | jq '.users[]' | grep "email"

# Number crunching
cat numbers.txt | sort -n | tail -1

Implementation Details

For developers interested in the internals:
  • Pipe counting: ft_count_pipes.c:92-106
  • Pipeline execution: ft_execute_commands.c:68-95
  • Process creation: Fork creates child for each command
  • Pipe creation: pipe() system call creates pipe pairs
  • File descriptor management: dup2() redirects stdin/stdout
  • Wait for completion: ft_execute_commands.c:92-94

Build docs developers (and LLMs) love