# Bash CLI One-Liners Cheatsheet

- URL: https://karandeepsingh.ca/cheatsheets/bash/bash-cli/
- Author: Karandeep Singh
- Published: 2025-05-19
- Tags: bash, cli, linux
- Summary: The command-line one-liners I use daily. find/xargs, grep, curl, jq, tar, disk and process triage, network checks, ssh/rsync, history tricks, and job control.

These are the one-liners I actually type on production boxes. Muscle-memory commands for finding files, chasing disk usage, poking APIs, and triaging misbehaving processes. When a one-liner turns into a script, the [Bash Basics Cheatsheet](https://karandeepsingh.ca/cheatsheets/bash/bash-basics/) covers making it robust.

## find + xargs

> **Docs:** [find(1) man page](https://man7.org/linux/man-pages/man1/find.1.html)

```bash
find /var/log -name '*.gz' -mtime +30 -delete        # old compressed logs
find . -type f -size +100M                           # large files
find . -name '*.sh' -exec chmod +x {} +              # batch chmod
find . -type f -name '*.yaml' | xargs grep -l 'image:'
find . -name '*.log' -print0 | xargs -0 rm --        # -print0/-0: safe with spaces
find . -type d -empty -delete                        # remove empty dirs
find /etc -newer /etc/hostname -type f               # changed since that file
ls *.png | xargs -P4 -I{} convert {} {}.webp         # -P4: 4 parallel jobs
```

## grep

> **Docs:** [grep(1) man page](https://man7.org/linux/man-pages/man1/grep.1.html)

```bash
grep -r 'TODO' src/                    # recursive
grep -rn --include='*.tf' 'aws_s3' .   # line numbers, filter by glob
grep -E 'error|fatal|panic' app.log    # extended regex, alternation
grep -o 'req_id=[a-f0-9]*' app.log     # print only the match
grep -c '500' access.log               # count matching lines
grep -v '^#' config | grep -v '^$'     # strip comments and blanks
grep -B2 -A5 'Traceback' app.log       # context: 2 before, 5 after
grep -i -l 'password' -r . 2>/dev/null # files containing, case-insensitive
```

## sort / uniq / wc Pipelines

> **Docs:** [sort(1) man page](https://man7.org/linux/man-pages/man1/sort.1.html)

```bash
sort file | uniq -c | sort -rn | head            # frequency count, descending
sort -t: -k3 -n /etc/passwd                      # numeric sort on field 3
sort -u hosts.txt                                # sort + dedupe in one
sort -h                                          # human sizes: 1K < 2M < 3G
uniq -d sorted.txt                               # only duplicated lines
wc -l < file                                     # line count, no filename
comm -23 <(sort a.txt) <(sort b.txt)             # lines in a but not b
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head   # top IPs
```

For heavier field surgery in these pipelines, see the [Bash sed & awk Cheatsheet](https://karandeepsingh.ca/cheatsheets/bash/bash-sed-awk/).

## tar / gzip / zstd

> **Docs:** [tar(1) man page](https://man7.org/linux/man-pages/man1/tar.1.html)

```bash
tar czf app.tar.gz app/                # create gzip archive
tar xzf app.tar.gz -C /opt/           # extract to a directory
tar tzf app.tar.gz                    # list contents without extracting
tar czf - /data | ssh host 'cat > /backup/data.tar.gz'   # archive over ssh
tar --exclude='.git' --exclude='node_modules' -czf src.tar.gz .
tar caf app.tar.zst app/              # -a: pick compressor by extension
zstd -T0 bigfile                      # zstd on all cores; much faster than gzip
gzip -dk file.gz                      # decompress, keep original
zcat access.log.gz | grep ' 500 '     # search inside without extracting
```

## curl

> **Docs:** [curl manpage on curl.se](https://curl.se/docs/manpage.html)

```bash
curl -I https://example.com                       # headers only
curl -sfSL https://example.com/health             # silent, fail on HTTP errors
curl -o out.bin -L https://example.com/dl         # follow redirects, save
curl -w '%{http_code} %{time_total}s\n' -o /dev/null -s https://api.example.com
curl -X POST https://api.example.com/v1/deploys \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"service": "web", "version": "1.4.2"}'
curl --retry 5 --retry-delay 2 --retry-all-errors -sf https://flaky.example.com
curl -u user:pass ftp://host/file                 # basic auth
curl -x http://proxy:3128 https://example.com     # via proxy
```

## jq Basics

```text
$ cat pods.json
{"items":[{"name":"web-1","status":"Running","restarts":0},
          {"name":"web-2","status":"CrashLoopBackOff","restarts":7},
          {"name":"db-1","status":"Running","restarts":2}]}
$ jq -r '.items[] | select(.restarts > 0) | .name' pods.json
web-2
db-1
$ jq -r '.items[] | [.name,.status] | @tsv' pods.json
web-1	Running
web-2	CrashLoopBackOff
db-1	Running
# -r drops the quotes, @tsv makes it safe to pipe into awk or column -t
```

> **Docs:** [jq Manual on jqlang.org](https://jqlang.org/manual/)

```bash
curl -s https://api.example.com/pods | jq .              # pretty-print
jq '.items[].metadata.name' pods.json                    # extract a field
jq -r '.token' resp.json                                 # -r: raw, no quotes
jq '.items | length' pods.json                           # count
jq '.[] | select(.status == "failed") | .id' jobs.json   # filter
jq '.[] | {name: .metadata.name, ip: .status.podIP}'     # reshape
jq -r '.[] | [.name, .cpu] | @tsv' nodes.json            # TSV for awk/column
jq -n --arg env "$ENV" '{environment: $env}'             # build JSON from shell
jq 'keys' obj.json                                       # what fields exist?
```

## Disk & Process Triage

> **Docs:** [ps(1) man page](https://man7.org/linux/man-pages/man1/ps.1.html)

```bash
df -h                                  # filesystem usage
du -sh /var/* | sort -rh | head        # what's eating this disk
du -xh --max-depth=2 / | sort -rh | head -20   # -x: don't cross filesystems
ncdu /var                              # interactive, if installed

ps aux --sort=-%mem | head             # top memory hogs
ps aux --sort=-%cpu | head             # top CPU hogs
ps -ef --forest                        # process tree
top -o %MEM                            # top sorted by memory (htop if you can)
lsof -p 1234                           # files open by PID
lsof +D /var/log                       # who has files open under a dir
lsof -i :8080                          # what's listening on 8080
kill -TERM 1234 && sleep 5 && kill -0 1234 && kill -KILL 1234
```

**tip:**
"Disk full" but `du` doesn't add up? A deleted file is still held open by a process. `lsof +L1` lists them. Restart the offender (usually a log-hoarding daemon) and the space comes back.

## Network

> **Docs:** [ss(8) man page](https://man7.org/linux/man-pages/man8/ss.8.html)

```bash
ss -tlnp                               # listening TCP sockets + owning process
ss -s                                  # socket summary (state counts)
dig +short example.com                 # just the A record
dig example.com MX                     # specific record type
dig @1.1.1.1 example.com               # query a specific resolver
dig -x 93.184.216.34                   # reverse lookup
nc -zv host 5432                       # is that port open?
nc -l 9000                             # quick listener for testing
timeout 3 bash -c '</dev/tcp/host/443' && echo open   # no nc? pure bash
curl -sv telnet://host:6379 </dev/null # handshake check via curl
ip -br addr; ip route                  # interfaces and routing at a glance
```

## ssh / scp / rsync

> **Docs:** [rsync(1) man page](https://man7.org/linux/man-pages/man1/rsync.1.html)

```bash
ssh -J bastion.example.com user@10.0.2.15        # jump host
ssh -L 5432:db.internal:5432 bastion             # local port forward
ssh host 'sudo journalctl -u nginx -n 50'        # run remote command
ssh-copy-id user@host                            # install your key

scp file.tar.gz user@host:/tmp/                  # simple copy
scp -r user@host:/var/log/app ./logs/            # recursive pull

rsync -avz --progress ./site/ user@host:/var/www/site/   # sync (mind the slashes)
rsync -avz --delete src/ dst/                    # exact mirror; deletes extras
rsync -avzn --delete src/ dst/                   # -n: ALWAYS dry-run first
rsync -avz -e 'ssh -J bastion' data/ user@internal:/data/
```

Trailing slash on the source means "contents of"; no slash copies the directory itself.

## History Tricks

> **Docs:** [History Expansion, GNU Bash manual](https://www.gnu.org/software/bash/manual/bash.html#History-Interaction)

| Trick | Effect |
|-------|--------|
| `!!` | Re-run the last command (`sudo !!` is the classic) |
| `!$` | Last argument of the previous command |
| `!*` | All arguments of the previous command |
| `!grep` | Re-run the most recent command starting with `grep` |
| `^old^new` | Re-run last command with first `old` replaced by `new` |
| `Ctrl-r` | Reverse-search history incrementally (press again to cycle) |
| `Alt-.` | Insert last argument, keep pressing to go further back |
| `$_` | Last argument, as a variable in scripts |

```bash
mkdir -p /opt/app/releases/v2 && cd !$    # !$ expands to the new path
history | awk '{print $2}' | sort | uniq -c | sort -rn | head   # your top commands
```

## Job Control

> **Docs:** [Job Control, GNU Bash manual](https://www.gnu.org/software/bash/manual/bash.html#Job-Control)

```bash
long_build.sh &                # run in background
jobs -l                        # list jobs with PIDs
fg %1                          # bring job 1 to foreground
bg %1                          # resume a Ctrl-Z'd job in background
kill %2                        # kill by job number
Ctrl-Z                         # suspend current foreground job

nohup ./migrate.sh > migrate.log 2>&1 &   # survive hangup on logout
./worker.sh & disown           # background + detach from the shell
wait                           # block until all background jobs finish
wait -n                        # until any one finishes (bash 4.3+)
```

For anything long-running over ssh, prefer `tmux` over `nohup`; you get your session back, output included.

**Question:** Next time you reach for `grep | awk | cut` on JSON, is there a single jq expression that would survive the field order changing?

## Related Cheatsheets

- [Bash Basics](https://karandeepsingh.ca/cheatsheets/bash/bash-basics/): `set -euo pipefail`, parameter expansion, traps, and arrays, everything needed to turn these one-liners into scripts that survive production
- [Bash sed & awk](https://karandeepsingh.ca/cheatsheets/bash/bash-sed-awk/): heavier text processing, meaning in-place edits, column sums, extracting text between markers, and the GNU vs BSD gotchas
- [tmux](https://karandeepsingh.ca/cheatsheets/terminal/tmux/): session, window, and pane keybindings plus scripted layouts, so long-running pipelines outlive a dropped SSH connection

There's more in the [Bash group](https://karandeepsingh.ca/cheatsheets/bash/), and the whole [cheatsheet library](https://karandeepsingh.ca/cheatsheets/) is one level up.
