This post covers how I set up my shell and development environment on Mac. I use Windows, Mac, and Linux regularly, so I try to keep things consistent across platforms where I can. You can see the software I use and how I customize it in the linked posts; this one focuses on shell and terminal setup.
Table of Contents
- Shell
- Development Environment
- Terminal
- Installing Homebrew
- Packages
- Keyboard Customizations
- Conda
- Alias Notes
- Debugging Your Environment
- Useful Git Commands
- Full .profile
Shell
On Macs, I use zsh as my shell. It’s been the default since macOS Catalina. My setup is built on top of Oh My Zsh, which provides a nice framework for managing zsh configuration, themes, and plugins. You can check if it’s installed with ls ~/.oh-my-zsh.
Development Environment
I store my configuration in a dotfiles repo so I can sync it across machines. The core idea is simple: put all your portable configuration (aliases, functions, environment variables) in a single ~/.profile file and symlink it from the repo. Then source it from whatever shell you’re using.
The Sourcing Chain
Here’s how my config loads:
~/.zshrc -> oh-my-zsh auto-sources ~/.oh-my-zsh/custom/profile.zsh -> ~/.profile -> ~/.credentials
The pieces:
~/.zshrcloads Oh My Zsh, which automatically sources every.zshfile in~/.oh-my-zsh/custom/.~/.oh-my-zsh/custom/profile.zshis a one-line file:source ~/.profile. This is the bridge between zsh and your portable config. You need to create this file manually on each new machine.~/.profileis where all the real configuration lives — aliases, functions, PATH modifications. This is the file I symlink from my dotfiles repo.~/.credentialscontains passwords and API keys. It’s sourced from.profilebut never committed to any repo.
Don’t Symlink .zshrc
I don’t recommend making your .zshrc portable. Many tools modify .zshrc as part of their installation:
conda initadds a conda initialization block- nvm adds its loader
- Various tools (LM Studio, Windsurf, Google Cloud SDK, etc.) append PATH entries
Each of these will either overwrite your symlink with a regular file or modify your repo’s copy with machine-specific paths. It’s a constant battle.
Instead, I leave .zshrc as a regular local file on each machine. The things you actually want to sync — your aliases, functions, and shortcuts — belong in .profile. Let .zshrc handle the machine-specific stuff (Oh My Zsh setup, conda init, tool-specific PATH additions).
Setting Up a New Machine
# Clone dotfiles
git clone https://github.com/YOUR_USER/dotfiles.git ~/git/dotfiles
# Symlink .profile
ln -sf ~/git/dotfiles/shell/profile ~/.profile
# Symlink .gitconfig
ln -sf ~/git/dotfiles/git/gitconfig ~/.gitconfig
# Create the oh-my-zsh bridge (one-time setup)
echo 'source ~/.profile' > ~/.oh-my-zsh/custom/profile.zsh
Then open a new terminal or run source ~/.zshrc.
Terminal
I use iTerm2 instead of the default Terminal app. I switch to the Dark Background profile.
Installing Homebrew
Homebrew is the standard Mac package manager. It installs to /opt/homebrew on Apple Silicon and /usr/local on Intel Macs.
After installing, Homebrew will tell you to add its shell setup to your profile. For Apple Silicon, your ~/.zprofile should contain:
eval "$(/opt/homebrew/bin/brew shellenv)"
Packages
Here are the packages I use to improve my terminal experience.
powerlevel10k
powerlevel10k is a fast, customizable zsh theme. Install it through Oh My Zsh (not through brew), then start a new terminal and it will walk you through configuration.
zoxide
zoxide is a smarter cd that learns your most-used directories. After visiting a directory once, you can jump to it by typing any part of its name. I alias it to j:
alias j='z'
alias ji='zi' # interactive mode with fzf
Add to your .zshrc:
eval "$(zoxide init zsh)"
fzf
fzf is a general-purpose fuzzy finder. It enhances ctrl+r history search, file finding, and more. Add to your .zshrc:
source <(fzf --zsh)
zsh-autosuggestions
zsh-autosuggestions suggests commands as you type based on your history. Install it, then add it to your Oh My Zsh plugins in .zshrc:
plugins=(git zsh-autosuggestions)
zsh-syntax-highlighting
zsh-syntax-highlighting highlights valid commands as you type — green for valid, red for invalid. After installing via brew, source it in your .profile:
source /opt/homebrew/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
The path is /usr/local/share/... on Intel Macs.
Pygments
Pygments is a Python syntax highlighter. It’s like cat with colors. I alias it to ccat:
alias ccat='pygmentize -O style=monokai -f console256 -g'
Keyboard Customizations
By default, Option+Arrow doesn’t jump between words in the terminal. Add this to your .profile:
bindkey "\e\e[D" backward-word
bindkey "\e\e[C" forward-word
I also remap some keys system-wide to make the control key more useful — ctrl+w to close tabs, ctrl+arrow to jump words in browsers, etc. This Stack Overflow answer explains how to set this up in iTerm2. Note that whether word-jumping works depends on the specific application.
Conda
I install Anaconda for all users, which places it at /opt/anaconda3/. When you run conda init zsh, it adds an initialization block to your .zshrc. Leave it there.
Conda and tmux
If you use tmux, be aware that it doesn’t always source .zshrc — sometimes it only sources .profile. This means conda won’t load in tmux sessions, and you might end up using the system Python instead. Check with which python. If this happens, move the conda initialization block from .zshrc into .profile.
Alias Notes
When using shell variables in aliases, quoting matters. If you define a variable:
export BASE="$HOME/git"
You need double quotes in the alias so the variable gets expanded:
alias cdh="cd $BASE" # works — $BASE is expanded
alias cdh='cd $BASE' # won't work — $BASE stays literal
However, $HOME works with single quotes in most shells because it’s expanded at a different stage.
Debugging Your Environment
If something unexpected shows up in your environment, you can search for it across your shell config files:
grep 'mysterious_string' ~/.zshrc ~/.zprofile ~/.zshenv ~/.profile
Useful Git Commands
A compact, colorized git log format:
git log --pretty=format:'%C(yellow)%h %Cred%ad %Cblue%an%Cgreen%d %Creset%s' --date=short
Full .profile
My full .profile is available in my dotfiles repo.