xargs Command Building

Building commands from input, parallel execution, and batch operations.

Core Concepts

# xargs reads stdin, passes as arguments to command
echo "file1 file2 file3" | xargs rm           # rm file1 file2 file3

# Default command is echo
echo "hello world" | xargs                     # hello world

# -n N: Max N arguments per execution
seq 1 6 | xargs -n 2 echo
# 1 2
# 3 4
# 5 6

# -L N: N lines per execution (not args)
printf "a b\nc d\n" | xargs -L 1 echo
# a b
# c d

Placeholder Substitution (-I)

# -I {}: Replace {} with input (one line at a time)
find . -name "*.txt" | xargs -I {} cp {} /backup/

# Custom placeholder
find . -name "*.log" | xargs -I FILE mv FILE FILE.old

# Multiple uses in same command
ls *.conf | xargs -I {} cp {} {}.$(date +%Y%m%d).bak

# With complex commands
find . -name "*.sh" | xargs -I {} sh -c 'echo "=== {} ==="; head -5 {}'

# IMPORTANT: -I implies -L 1 (one line per execution)

Delimiter Control

# Default: whitespace (space, tab, newline)

# -d: Single character delimiter
echo "a:b:c:d" | xargs -d ':' echo            # a b c d

# -d '\n': Only newline (preserves spaces in values)
printf "hello world\nfoo bar\n" | xargs -d '\n' -I {} echo "Line: {}"
# Line: hello world
# Line: foo bar

# -0: Null delimiter (CRITICAL for special filenames)
# BAD: Breaks on "my file.txt" or "file'name.txt"
find . -name "*.txt" | xargs rm

# GOOD: Handles ANY filename safely
find . -name "*.txt" -print0 | xargs -0 rm

# Other null-output commands
grep -lZ "pattern" *.txt | xargs -0 cat       # grep -Z
locate -0 "*.conf" | xargs -0 ls              # locate -0

Parallel Execution (-P)

# -P N: Run N processes in parallel (default: 1)

# Sequential (slow)
find . -name "*.gz" | xargs gunzip

# Parallel with 4 workers
find . -name "*.gz" | xargs -P 4 gunzip

# Use all CPU cores
find . -name "*.jpg" -print0 | xargs -0 -P $(nproc) convert_resize

# Parallel + placeholder
cat hosts.txt | xargs -P 10 -I {} ssh {} "uptime"

# Parallel + batching (4 workers, 50 files each)
find . -name "*.log" -print0 | xargs -0 -P 4 -n 50 gzip

# Parallel downloads
cat urls.txt | xargs -P 5 -n 1 wget -q

# CAUTION: -P output may interleave
# CAUTION: -P with databases can cause locks

Safety Flags

# -r: Don't run if stdin is empty
echo "" | xargs -r rm                          # Does nothing (safe)
find . -name "*.none" | xargs -r rm            # No error if no matches

# -p: Prompt before execution
find . -name "*.bak" | xargs -p rm
# rm file1.bak file2.bak?...y/n

# -t: Print command to stderr (trace)
echo "a b c" | xargs -t -n 1 echo
# echo a     <- printed to stderr
# a          <- actual output
# echo b
# b

# Combine for careful operations
find . -name "*.old" | xargs -t -p rm

# ALWAYS use -r in scripts
find . -name "*.tmp" -print0 | xargs -0 -r rm

find + xargs Patterns

# Delete old files
find /tmp -name "*.tmp" -mtime +7 -print0 | xargs -0 rm -f

# Move files
find . -name "*.log" -print0 | xargs -0 -I {} mv {} /archive/

# Compress in parallel
find /var/log -name "*.log" -mtime +1 -print0 | xargs -0 -P 4 gzip

# Change permissions
find /var/www -type f -print0 | xargs -0 chmod 644
find /var/www -type d -print0 | xargs -0 chmod 755

# Search in files (efficient - grep gets multiple files)
find . -name "*.py" -print0 | xargs -0 grep -l "import requests"

# Count total lines
find . -name "*.sh" -print0 | xargs -0 wc -l | tail -1

# Archive today's files
find /data -type f -mtime 0 -print0 | xargs -0 tar -cvzf backup.tar.gz

Text Processing

# Search pattern in file list
cat filelist.txt | xargs grep -l "ERROR"

# Count matches per file
find . -name "*.log" -print0 | xargs -0 grep -c "WARN" | grep -v ":0$"

# Replace text in multiple files
find . -name "*.conf" -print0 | xargs -0 sed -i 's/old_server/new_server/g'

# Concatenate files in order
ls -v *.part | xargs cat > combined.bin

# First line of each file
find . -name "*.txt" -print0 | xargs -0 -I {} sh -c 'echo "=== {} ==="; head -1 {}'

# Extract CSV field
find . -name "*.csv" -print0 | xargs -0 -I {} sh -c 'cut -d, -f3 {} | tail -n +2'

Infrastructure Operations

# SSH to multiple hosts
cat servers.txt | xargs -P 10 -I {} ssh {} "uptime"

# Deploy file to hosts
cat hosts.txt | xargs -P 5 -I {} scp config.yml {}:/etc/app/

# Check service status
cat hosts.txt | xargs -P 10 -I {} sh -c \
    'ssh {} "systemctl is-active nginx" && echo "{}: OK" || echo "{}: FAIL"'

# Disk space check
cat servers.txt | xargs -P 10 -I {} sh -c 'echo "=== {} ==="; ssh {} "df -h /"'

# Parallel DNS lookups
cat domains.txt | xargs -P 20 -I {} dig +short {}

# Certificate expiry check
cat domains.txt | xargs -P 10 -I {} sh -c \
    'echo | openssl s_client -connect {}:443 2>/dev/null | openssl x509 -noout -enddate'

# HTTP status check
cat urls.txt | xargs -P 10 -I {} curl -o /dev/null -s -w "{}: %{http_code}\n" {}

Container Operations

# Remove stopped containers
docker ps -aq -f status=exited | xargs -r docker rm

# Remove dangling images
docker images -q -f dangling=true | xargs -r docker rmi

# Stop all running containers
docker ps -q | xargs -r docker stop

# Remove all volumes
docker volume ls -q | xargs -r docker volume rm

# Exec in multiple containers
docker ps -q | xargs -I {} docker exec {} uptime

# Pull multiple images
cat images.txt | xargs -P 3 -n 1 docker pull

Kubernetes Operations

# Delete pods by label
kubectl get pods -l app=test -o name | xargs kubectl delete

# Logs from multiple pods
kubectl get pods -o name | xargs -I {} kubectl logs {} --tail=10

# Exec in all pods
kubectl get pods -o name | xargs -I {} kubectl exec {} -- df -h

# Annotate resources
kubectl get svc -o name | xargs -I {} kubectl annotate {} updated="$(date)"

# Scale all deployments
kubectl get deploy -o name | xargs -I {} kubectl scale {} --replicas=0

# Delete completed jobs
kubectl get jobs -o name | xargs -I {} sh -c \
    'kubectl get {} -o jsonpath="{.status.succeeded}" | grep -q 1 && kubectl delete {}'

Git Operations

# Stage files matching pattern
git diff --name-only | grep "\.py$" | xargs git add

# Checkout specific files
cat restore-files.txt | xargs git checkout HEAD --

# Remove untracked files
git ls-files --others --exclude-standard | xargs -r rm

# Blame multiple files
git diff --name-only HEAD~5 | xargs -I {} git blame {}

# Cherry-pick commits
echo "abc123 def456 ghi789" | xargs -n 1 git cherry-pick

Process Management

# Kill by pattern
pgrep -f "zombie_process" | xargs kill -9

# Kill user's processes
pgrep -u baduser | xargs kill

# Check process resources
pgrep nginx | xargs -I {} ps -p {} -o pid,ppid,%cpu,%mem,cmd

# Restart services
echo "nginx apache2 mysql" | xargs -n 1 systemctl restart

awk + xargs Combos

# Kill high CPU processes
ps aux | awk '$3 > 50 {print $2}' | xargs kill

# Process IPs from netstat
netstat -tn | awk '{print $5}' | cut -d: -f1 | sort -u | xargs -I {} geoiplookup {}

# Users from CSV
awk -F, '{print $1}' users.csv | xargs -I {} useradd {}

# Block failed logins
grep "FAILED LOGIN" /var/log/auth.log | awk '{print $11}' | sort -u | \
    xargs -I {} iptables -A INPUT -s {} -j DROP

Safe Operations

# DRY RUN: See what would happen
find . -name "*.log" -mtime +30 | xargs echo rm
# Output: rm old1.log old2.log (nothing deleted)

# COUNT FIRST: Know the scope
find . -name "*.tmp" | wc -l                   # How many files?
find . -name "*.tmp" -print0 | xargs -0 du -ch | tail -1  # Total size?

# PREVIEW: See affected files
find . -name "*.tmp" | head -20

# STAGED: Test on subset first
find . -name "*.txt" | head -1 | xargs -t sed -i 's/old/new/g'

# BACKUP BEFORE MODIFY
find . -name "*.conf" -print0 | xargs -0 -I {} sh -c 'cp {} {}.bak && sed -i "s/old/new/g" {}'

# REVERSIBLE: Move to trash instead of delete
find . -name "*.tmp" -print0 | xargs -0 -I {} mv {} ~/.trash/

# PERMISSION CHECK
find . -name "*.log" -print0 | xargs -0 -I {} sh -c 'test -w {} || echo "NO WRITE: {}"'

# LIMIT SCOPE
find . -maxdepth 1 -name "*.tmp" | xargs rm   # Current dir only
find . -type f -name "*.log" | xargs rm       # Files only
find . -name "*.log" -mtime +7 | xargs rm     # Older than 7 days
find . -name "*.dat" -size -1M | xargs rm     # Smaller than 1MB

Common Gotchas

# GOTCHA: Variable not expanded in sh -c
NAME="test"
echo "file" | xargs -I {} sh -c 'echo $NAME: {}'  # $NAME is empty!

# FIX: Pass as argument
echo "file" | xargs -I {} sh -c 'echo "$1": {}' _ "$NAME"

# GOTCHA: {} in literal quotes
find . -name "*.txt" | xargs -I {} echo '{}'      # Prints literal {}

# FIX: Use double quotes or no quotes
find . -name "*.txt" | xargs -I {} echo {}

# GOTCHA: Empty lines processed
printf "a\n\nb\n" | xargs -I {} echo "Line: {}"   # Includes empty line

# FIX: Filter empty lines
printf "a\n\nb\n" | grep -v '^$' | xargs -I {} echo "Line: {}"

# GOTCHA: Filenames with spaces break
find . -name "*.txt" | xargs rm                   # Breaks on "my file.txt"

# FIX: Always use -print0 / -0
find . -name "*.txt" -print0 | xargs -0 rm

Quick Reference

# Essential flags
-n N          # Max N args per execution
-L N          # N lines per execution
-I {}         # Placeholder (implies -L 1)
-P N          # N parallel processes
-0            # Null delimiter (use with find -print0)
-d CHAR       # Custom delimiter
-r            # Don't run if empty (ALWAYS USE IN SCRIPTS)
-t            # Trace (print commands)
-p            # Prompt before execution
-s N          # Max command line length

# Common patterns
find . -print0 | xargs -0 cmd                     # Safe filenames
find . | xargs -I {} cmd {}                       # Placeholder
find . | xargs -P 4 cmd                           # Parallel
find . | xargs -n 100 cmd                         # Batch of 100
cat list | xargs -P $(nproc) -I {} cmd {}         # All cores

# Safety checklist
# 1. Use -print0 / -0 for filenames
# 2. Use -r to handle empty input
# 3. Preview with | head or | wc -l first
# 4. Test with -t or echo before real command
# 5. Use -p for destructive operations