I'm considering moving from bash to zsh as I often come across posts praising zsh. I'm an experienced command line user and I'm assuming the basics are pretty much the same, so I'm looking for advice to get the benefits of moving, and any gotchas to be aware of.

Please just give one bit of advice per answer. I'm looking for bite-size chunks where I can come back and integrate extra bits of info into my shell usage at a steady pace rather than trying to learn it all in one go.


10 Answers 10


As you say, zsh is similar in many respects to bash. It has some features you won't find in bash, and it can be extended in powerful ways. Don't think of moving as a kind of revolution, but rather as a series of evolutionary steps that help you in your daily work. Here are some hints from my .zshrc. Although you say you prefer single pieces of advice, this post is a longish list. Still it is a good idea to go through the points one by one. Just add the interesting bits to your ~/.zshrc and reload with source ~/.zshrc. A final tip: learn the keystrokes of zsh's default ("Emacs") keyboard shortcuts: ^A ^E ^W Alt-F Alt-B Alt-P ^L ^R. You can replace Alt by two separate keystrokes: Alt-P is equivalent to ESC P.

This gives you more extensive tab completion.

autoload -U compinit

Tab completion from both ends.

setopt completeinword

Tab completion should be case-insensitive.

zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}'

Better completion for killall.

zstyle ':completion:*:killall:*' command 'ps -u $USER -o cmd'

Changes the definition of "word", e.g. with ^W.

autoload select-word-style
select-word-style shell

Colors for ls.

if [[ -x "`whence -p dircolors`" ]]; then
  eval `dircolors`
  alias ls='ls -F --color=auto'
  alias ls='ls -F'

Shortcuts for ls.

alias ll='ls -l'
alias la='ls -a'

One history for all open shells; store 10,000 entries. This makes this into a useful memory aid to find the commands you used last time for ./configure etc. Use Alt-P (find command that starts like this) and ^R (search in history) liberally.

setopt sharehistory
setopt extendedhistory

Enables all sorts of extended globbing, such as ls **/*.txt (find all text files), ls -d *(D) (show all files including those starting with "."). To find out more, go to man zshexpn, section "FILENAME GENERATION".

# superglobs
setopt extendedglob
unsetopt caseglob

This is useful to remember commands in your history without executing them.

setopt interactivecomments # pound sign in interactive prompt

Type ".." instead of "cd ..", "/usr/include" instead of "cd /usr/include".

setopt auto_cd

Nice prompt.

PS1='[%T] %n@%m:%~# '

Display CPU usage stats for commands taking more than 10 seconds


Some commands you use extensively in Ubuntu.

alias 'a=sudo aptitude'
alias 'ai=sudo aptitude install'
alias 'ar=sudo aptitude remove'
alias 'au=sudo aptitude update'
alias 'ag=sudo aptitude safe-upgrade'
alias 'as=apt-cache search'
alias 'aw=apt-cache show'

Lists packages sorted by their size - useful when deciding which packages are taking up you disk space.

function apt-list-packages {
  dpkg-query -W --showformat='${Installed-Size} ${Package} ${Status}\n' | grep -v deinstall | sort -n | awk '{print $1" "$2}'

I would recommend the book From bash to Z Shell. It has all the advice you need for switching your shell. It explains the differences of both shells and makes it easy for a new zsher.

  • The book is great for both bash and zsh users alike. You do have to love how each chapter starts with "both bash and zsh can do all this" and then leads to another 30 pages of "and here's the zsh only stuff"
    – Rick
    Sep 3, 2010 at 1:54

Here is my .zshrc and that is the most importaint thing! zsh have a lot of options you can use, so look at some of all the examples around the net or read the documentation at the Zsh homepage.

My .zshrc doesn't contain any really cool things other than a timestamp in the righthand side of the command line.

Btw, remember to try tab-compleation every where a few examples here:

mplayer -a[tab]

will show somthing like this:

mplayer -a
 -ac                 -- force usage of a specific audio codec
 -af                 -- activate audio filters
 -afm                -- force usage of a specific audio codec family
 -alang              -- select the DVD audio language
 -ao                 -- specify audio driver
 -aop                -- specify audio output filter

And if you use passwordless ssh-keys or ssh-agent you might find it usefull to tabcomplete remote files:

scp apollo:/home/user/[tab]
Desktop/ Documents/ Downloads/ Music/ Pictures/ Public/ Templates/ Videos/

After getting the list you can then press tab more times to cycle through the different possibilities.

But be warned, this shell will make you lazy and make you feel that a standard shell is stupid and annoying!


A couple of specifically useful extended globs:

  1. rmdir *(/^F) - delete all non-empty directories under the current directory

  2. grep traceback /srv/log/**/*(.m-2) - look for this regex in files modified in the last two days

  3. chmod g+w **/*(U^I) - make any files owned by me and not group-writable be group-writable

Yes, of course you can write this with find but this is easier to dash off. It does have two drawbacks, to be fair, both to do with them all being expanded onto the command line: if it matches many thousands of files the command line will get too long and this will fail, and secondly all of the files are found before the file starts to run.

(You'll need setopt extendedglob if that's not already on)


I dont know that much about bash, so I can't compate. Some snippets from my zsh config file.

Some config

REPORTTIME=10 # print elapsed time when more than 10 seconds
setopt NO_HUP
setopt LOCAL_OPTIONS # allow functions to have local options
setopt LOCAL_TRAPS # allow functions to have local traps
setopt SHARE_HISTORY # share history between sessions ???
setopt EXTENDED_HISTORY # add timestamps to history
setopt CORRECT

setopt APPEND_HISTORY # adds history
setopt INC_APPEND_HISTORY SHARE_HISTORY  # adds history incrementally and share it across sessions
setopt HIST_IGNORE_ALL_DUPS  # don't record dupes in history
# Leave some chars out of the out of WORDCHARS so ^W acts more nicely 

Git in the prompt

if [[ -n $SSH_CONNECTION ]]; then
  export PS1='%m:%3~$(git_info_for_prompt)%# '
  export PS1='%3~$(git_info_for_prompt)%# '

Some hotkeys, insert at the beginning of the line some text.

insert_sudo     () { zle beginning-of-line; zle -U "sudo "         }
insert_apt      () { zle beginning-of-line; zle -U "sudo apt-get " }
insert_gem      () { zle beginning-of-line; zle -U "sudo gem "     }
insert_install  () { zle -U "install "     }

zle -N insert-sudo      insert_sudo
zle -N insert-apt       insert_apt
zle -N insert-gem       insert_gem
zle -N insert-install   insert_install

bindkey "^B" insert-gem
bindkey "^N" insert-install
bindkey "^k" insert-sudo
bindkey "^a" insert-apt

The functions, I store then in ~/.zsh/functions

The git_info_for_prompt

local g="$(git rev-parse --git-dir 2>/dev/null)"
if [ -n "$g" ]; then
  local r
  local b
  if [ -d "$g/../.dotest" ]
    if test -f "$g/../.dotest/rebasing"
    elif test -f "$g/../.dotest/applying"
    b="$(git symbolic-ref HEAD 2>/dev/null)"
  elif [ -f "$g/.dotest-merge/interactive" ]
    b="$(cat "$g/.dotest-merge/head-name")"
  elif [ -d "$g/.dotest-merge" ]
    b="$(cat "$g/.dotest-merge/head-name")"
  elif [ -f "$g/MERGE_HEAD" ]
    b="$(git symbolic-ref HEAD 2>/dev/null)"
    if [ -f "$g/BISECT_LOG" ]
    if ! b="$(git symbolic-ref HEAD 2>/dev/null)"
      if ! b="tag: $(git describe --exact-match HEAD 2>/dev/null)"
        b="$(cut -c1-7 "$g/HEAD")..."

  if [ -n "$1" ]; then
    printf "$1" "${b##refs/heads/}$r"
    printf "[%s]" "${b##refs/heads/}$r"

Some github options

#compdef github

_github() {
  if (( CURRENT > 2 )); then
    # shift words so _arguments doesn't have to be concerned with second command
    (( CURRENT-- ))
    shift words
    # use _call_function here in case it doesn't exist
    _call_function 1 _github_${words[1]}
    _values "github command" \
     "fetch[Fetch from a remote to a local branch.]" \
     "ignore[Ignore a SHA (from 'github network commits')]" \
     "fetch_all[Fetch all refs from a user]" \
     "info[Info about this project.]" \
     "browse[Open this repo in a web browser.]" \
     "home[Open this repo's master branch in a web browser.]" \
     "clone[Clone a repo.]" \
     "pull-request[Generate the text for a pull request.]" \
     "network[Project network tools.]" \
     "pull[Pull from a remote.]" \
     "track[Track another user's repository.]"

_github_pull() {
  _arguments \
    "--merge[Automatically merge remote's changes into your master.]"
_github_clone() {
  _arguments \
    "--ssh[Clone using the [email protected] style url.]"

_github_track() {
  _arguments \
    "--private[Use [email protected]: instead of git://github.com/.]" \
    "--ssh[Equivalent to --private.]"

_github_network() {
  if (( CURRENT > 2 )); then
    # shift words so _arguments doesn't have to be concerned with second command
    (( CURRENT-- ))
    shift words
    # use _call_function here in case it doesn't exist
    _call_function 1 _github_network_${words[1]}
    _values "github network command" \
     "web[Open network in a web browser.]" \
     "list[List networked repositories.]" \
     "fetch[Fetched commits for a given networked repository.]" \
     "commits[List networked commits not pulled into this repo.]"

_github_network_commits() {
  _arguments \
    "--project[Filter commits on a certain project.]" \
    "--author[Filter commits on a email address of author.]" \
    "--common[Show common branch point.]" \
    "--nocache[Do not use the cached network data.]" \
    "--sort[How to sort : date(*), branch, author.]" \
    "--thisbranch[Look at branches that match the current one]" \
    "--applies[Filter commits to patches that apply cleanly.]" \
    "--limit[Only look through the first X heads - useful for really large projects]" \
    "--before[Only show commits before a certain date.]" \
    "--after[Only show commits after a certain date.]" \
    "--shas[Only show shas.]" \
    "--cache[Use the network data even if it's expired.]" \
    "--noapply[Filter commits to patches that do not apply cleanly.]"

I'm in the same trip :)

So far I've found that the thing is to have a good configuration file (.zshrc).

Take this one as example http://matt.blissett.me.uk/linux/zsh/zshrc, look at the comments and hack your way around. Stackoverflow and severphault and good places to search too.

I have yet to dive into http://dotfiles.org/.zshrc, but I don't have that much time to loose :)


Learn about the extended globbing and recursive globs in zsh.

Learn a little about zstyle and how various things (especially completion) let you tune their configuration using zstyle.

Look into the associative arrays. Also the standard arrays (beware the differences from bash, for the better!)

If you use regular expressions, look into =~ (which bash also has) and consider: setopt rematch_pcre

Avoid writing scripts which depend on more than a little of zsh's magic, because while it's fantastic to use, zsh can tend towards being write-only. If you're using too much more, think about when to switch to a language such as Python.

Zsh is seductive. It is the dark side. Welcome.


Big benefit – excellent tab completion with pre-packaged completion scripts for many commands. Here's an example showing the output of apt-get<TAB>:

autoclean        build-dep        clean            dselect-upgrade  install          remove           update           
autoremove       check            dist-upgrade     help             purge            source           upgrade          

I've give a coupe of talks and converted several people over to zsh. I keep a github repo of my (what are the advantages) notes along with both a starter and a copy of my own zsh config in github here.



Another great ressource is the zsh lovers page (comes from grml zsh site).

Not the answer you're looking for? Browse other questions tagged .