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