Skip to content

My favourite zsh/bash shortcuts (functions and aliases)

Posted by author

Introduction

My zsh profile is over 1000 lines at this point. A lot of that is functions I asked AI to generate for me, since it's fast, portable, and saves me a ton of typing.

Here's the thing though: the shortcuts that save me the most time aren't the clever ones. They're the dumb ones. Things like clone instead of git clone && cd, or dir instead of mkdir -p && cd. Each one only saves a second or two, but I run them so often that it adds up fast.

These are in no particular order, just the ones I reach for constantly.

Git aliases for common commands

A few one-liners I have set up as plain aliases:

1alias gcp="git cherry-pick"
2alias git-append="git commit --amend --no-edit -a"

gcp is self-explanatory. git-append amends the last commit with your currently staged (and unstaged, thanks to -a) changes without touching the commit message. Great for fixing up a commit you just made before you push.

Create a branch or switch to it if it already exists

One of my most-used functions. Normally you have to remember whether a branch exists before deciding between git checkout <branch> and git checkout -b <branch>. This just does the right thing either way:

1gb() {
2 if git rev-parse --verify --quiet "$1" >/dev/null; then
3 git checkout "$1"
4 else
5 git checkout -b "$1"
6 fi
7}

Nuke all local changes to reset the working tree

When an experiment goes sideways or I just want to throw everything away and start clean, I run nah:

1nah() {
2 git reset --hard
3 git clean -df
4 if [ -d ".git/rebase-apply" ] || [ -d ".git/rebase-merge" ]; then
5 git rebase --abort
6 fi
7}

This resets tracked changes, removes untracked files and directories. No confirmation prompt, so use it carefully.

Print recent commits as ready-to-paste cherry-pick commands

Useful when you need to cherry-pick a batch of commits from one branch onto another in order:

1logs() {
2 if [[ -z "$1" || "$1" =~ [^0-9] ]]; then
3 echo "Usage: logs <number_of_commits>"
4 return 1
5 fi
6 git log -n "$1" --reverse --pretty=format:"gcp %h"
7}

Run logs 5 and you get the last 5 commits printed oldest to newest, each one already formatted as gcp <hash> (using the alias from above). Copy, paste, done.

Create a directory and cd into it in one step

This is the one I mentioned that barely saves any time per use, but I run it constantlyn and the time save compounds:

1dir() { mkdir -p "$1" && cd "$1"; }

Make mkdir always create parent directories

I got tired of hitting "No such file or directory" errors from mkdir when the parent folder didn't exist yet, so I just overrode the default behavior globally:

1mkdir() {
2 command mkdir -p "$@"
3}

Now mkdir always behaves like mkdir -p.

Open nano and auto-create missing parent directories

Same idea as the mkdir override, but for opening a file in nano when the folder it lives in doesn't exist yet:

1nano() {
2 if [ $# -eq 0 ]; then
3 command nano
4 else
5 dir=$(dirname "$1")
6 if [ ! -d "$dir" ]; then
7 mkdir -p "$dir"
8 fi
9 command nano "$@"
10 fi
11}

Safely replace a file's contents by moving the original to trash first

Instead of overwriting a file and losing the original, replace moves it to ~/.Trash first and then opens a fresh file with the same name in nano:

1replace() {
2 if [[ $# -ne 1 ]]; then
3 echo "Usage: replace <file>" >&2
4 return 1
5 fi
6 local file="$1"
7 if [[ ! -f "$file" ]]; then
8 echo "Error: '$file' not found or not a file" >&2
9 return 1
10 fi
11 mkdir -p ~/.Trash
12 mv "$file" ~/.Trash/
13 nano "$file"
14}

But mainly this saves me from runing "rm && nano " which I often do when replacing a file from an AI chat or similar. If you need the original back, it's sitting in ~/.Trash.

Convert HEIC images to JPG or PNG

iPhone photos default to HEIC, which isn't great for sharing or uploading. These two functions batch-convert them using ImageMagick:

1heic2jpg() {
2 if [ $# -eq 0 ]; then
3 echo "Usage: heic2jpg file1.heic [file2.heic ...]"
4 return 1
5 fi
6 for f in "$@"; do
7 if [ ! -f "$f" ]; then echo "Not found: $f"; continue; fi
8 magick "$f" "${f%.*}.jpg"
9 done
10}
11 
12heic2png() {
13 if [ $# -eq 0 ]; then
14 echo "Usage: heic2png file1.heic [file2.heic ...]"
15 return 1
16 fi
17 for f in "$@"; do
18 if [ ! -f "$f" ]; then echo "Not found: $f"; continue; fi
19 magick "$f" "${f%.*}.png"
20 done
21}

Both take any number of files, so heic2jpg *.heic works fine.

Batch convert images to WebP

For getting images web-ready, this wraps img2webp with sensible defaults and skips anything that isn't actually a file:

1img2webp() {
2 if [[ $# -eq 0 ]]; then
3 echo "Usage: img2webp <file.png> [file2.png ...] or img2webp *.png"
4 return 1
5 fi
6 
7 for input in "$@"; do
8 if [[ ! -f "$input" ]]; then
9 echo "Skipping: '$input' is not a file"
10 continue
11 fi
12 local output="${input%.*}.webp"
13 echo "Converting: $input$output"
14 command img2webp -lossy -q 80 "$input" -o "$output"
15 done
16}

Runs at quality 80, lossy, which is a good default for most web use cases. Saves a TON of storage space and bandwidth

A few misc aliases

Small ones I use daily without thinking about them:

1alias pest="./vendor/bin/pest"
2alias python="python3" # For AI commands expecting it
3alias py="python3" # For me when I'm lazy

The pest alias saves typing out the full vendor binary path every time I want to run tests. The python/py aliases exist because plenty of AI-generated commands assume python points to Python 3, and half the time I'm too lazy to type the 3 myself anyway.

Conclusion

None of these are groundbreaking on their own. But that's kind of the point: the small, boring shortcuts you run 50 times a day save you more time overall than the clever ones you run once a week. If you're not already keeping a running file of these for yourself, start one. Every time you catch yourself typing the same thing twice, that's a candidate.


Syntax highlighting by Torchlight.dev

End of article