Introduction: Why Bash One‑liners Matter
If you spend any time in a Linux terminal, you’ll quickly learn that the real magic happens when you chain tiny commands together. A single line can search, transform, and act on files without opening an editor or writing a full script.
Who is this for?
- New developers who know the basics of
ls,cat, andgrep. - Sysadmins who want to replace repetitive loops with a single pipeline.
- Anyone who’d rather spend an afternoon debugging a script than typing the same 20‑line routine over and over.
In this tutorial we’ll explore 10 composable one‑liners built from the classic trio pipes (|), xargs, and friends. Each command is explained step‑by‑step, so you can see exactly why it works and how to adapt it.
Part 1 – The Basics of Pipes
A pipe (|) takes the standard output of the command on its left and feeds it as standard input to the command on its right. Think of it as a conveyor belt: each tool does a small job and passes the result along.
| # | One‑liner | What it does | Breakdown |
|---|---|---|---|
| 1 | ls -1 *.log \| wc -l |
Counts how many .log files are in the current directory. |
ls -1 *.log lists each log file on its own line. wc -l counts those lines. |
| 2 | ps aux \| grep '[s]sh' \| awk '{print $2}' |
Prints the PIDs of all active SSH sessions. | ps aux lists processes. grep '[s]sh' finds “ssh” without matching the grep itself. awk '{print $2}' extracts the PID column. |
| 3 | cat /var/log/syslog \| grep -i error \| tail -n 5 |
Shows the last 5 error messages from syslog. | cat streams the log, grep -i error filters case‑insensitively, tail -n 5 keeps the final five lines. |
Why use pipes?
- No temporary files are created.
- Each component stays tiny and testable.
Part 2 – Supercharging with xargs
xargs reads items from stdin and builds command lines. It’s perfect when a command doesn’t accept stdin but needs arguments.
| # | One‑liner | What it does | Breakdown |
|---|---|---|---|
| 4 | find . -name '*.tmp' -print0 \| xargs -0 rm -v |
Safely deletes every *.tmp file, handling spaces in names. |
find emits null‑terminated names (-print0). xargs -0 reads those names and runs rm -v on batches. |
| 5 | git diff --name-only \| xargs -d '\n' wc -l |
Shows the total line count change across all modified files. | git diff --name-only lists changed files. xargs -d '\n' wc -l runs wc -l on each, then aggregates. |
| 6 | cat urls.txt \| xargs -n 1 -P 5 curl -O |
Downloads up to 5 URLs in parallel, one per line. | xargs -n 1 passes one URL at a time. -P 5 allows 5 concurrent curl -O processes. |
Tips:
- Use
-0withfind -print0to avoid trouble with spaces, newlines, or quotes. -n Ncontrols how many arguments per invocation;-P Msets parallelism.
Part 3 – Combining find, grep, cut, sort, uniq
When you need to filter, reshape, and deduplicate data, the classic text‑processing toolbox shines.
| # | One‑liner | What it does | Breakdown |
|---|---|---|---|
| 7 | find /var/log -type f -mtime -2 \| xargs grep -H 'failed login' \| cut -d: -f1 \| sort \| uniq -c |
Counts how many recent log files contain “failed login”. | find selects files modified in the last 2 days. xargs grep -H prints matching lines with filenames. cut -d: -f1 keeps just the filename. sort orders them, uniq -c counts occurrences. |
| 8 | awk -F: '{print $3}' /etc/passwd \| sort -n \| uniq -c \| sort -nr |
Shows how many users share each UID, highest count first. | awk -F: extracts the UID field (3rd). sort -n numeric sort. uniq -c counts duplicates. Final sort -nr orders by count descending. |
| 9 | du -h . \| sort -h \| tail -n 5 |
Lists the five biggest items in the current directory (human‑readable). | du -h . reports disk usage. sort -h understands size suffixes (K, M, G). tail -n 5 keeps the largest five. |
| 10 | git log --pretty=format:'%h %ad %s' --date=short \| grep '^a' \| nl |
Numbers all commits whose hash starts with “a”, showing date and subject. | git log formats each commit on one line. grep '^a' keeps lines where the short hash begins with “a”. nl adds line numbers. |
These one‑liners are composable: you can pipe the output of #7 into #4, for example, to delete the offending files automatically.
Hands‑On Example: Clean Up Old, Empty Log Files
Let’s put a few of the tools together into a single, safe command that:
- Finds
.logfiles older than 30 days. - Checks they are empty (size 0).
- Deletes them, printing each deletion.
#!/usr/bin/env bash
# Find empty .log files older than 30 days and delete them verbosely.
find . -type f -name '*.log' -mtime +30 -size 0 -print0 |
xargs -0 rm -v
Run it step‑by‑step
# 1. List what would be deleted (dry run)
find . -type f -name '*.log' -mtime +30 -size 0 -print0 |
xargs -0 echo "Would delete:"
# 2. Actually delete
find . -type f -name '*.log' -mtime +30 -size 0 -print0 |
xargs -0 rm -v
Explanation
-type fensures we only look at regular files.-mtime +30selects files modified more than 30 days ago.-size 0restricts to empty files.-print0+xargs -0guarantees filenames with spaces or newlines are handled correctly.rm -vprints each removal, giving you immediate feedback.
You can copy the script into a file called clean_old_logs.sh, make it executable (chmod +x clean_old_logs.sh), and run it from any project directory.
Common Mistakes & How to Fix Them
| Mistake | Why it Happens | Fix |
|---|---|---|
Forgetting -print0 with find and using plain xargs. |
Filenames containing spaces cause xargs to split them incorrectly. |
Always pair find … -print0 with xargs -0. |
Using grep without quoting special characters. |
Shell expands characters like * before grep sees them. |
Quote the pattern: grep '[s]sh' or grep -F "pattern" for literal matches. |
Running xargs on a huge list and hitting the argument‑length limit. |
xargs builds command lines up to the system’s max size; exceeding it aborts. |
Let xargs split automatically (default) or set a smaller batch size with -n. |
Assuming wc -l will give you the total count after a pipeline. |
wc -l counts lines per file when given arguments, not the combined total. |
Use wc -l without arguments at the end of the pipeline, or sum with awk '{s+=$1} END{print s}'. |
Mixing sort -h with locales that treat “M” and “G” differently. |
Some locales sort strings alphabetically, breaking size order. | Set LC_ALL=C before the pipeline: LC_ALL=C sort -h. |
Try It Yourself
Exercise: Write a one‑liner that finds all Python (*.py) files in the current directory tree, counts how many times the word TODO appears in each, and prints the filenames sorted by the highest count first.
Hint: Use grep -c, sort -t: -k2 -nr, and remember to handle spaces in filenames.
What’s Next
- Advanced
xargs: Learn about-I{}placeholders for inserting arguments anywhere in the command line. - Process substitution (
<(…)) to replace temporary files in pipelines. - Parallel execution with GNU
parallelfor massive speed‑ups. - Custom Bash functions that wrap these one‑liners into reusable tools.
Bookmark this page for your daily Bash toolkit!