Apple’s heavy hand is forcing adoption of POSIX and even switch to Python
- TL;DR Summary
- Zsh is already there (before Catalina)
- Why the disruption?
- Why Z shell is better than Bash
- Script Bashisms break in Zsh
- Z Shell plug-ins
- Z shell Prompts
- Z shell themes
- Bashisms against portability
- Change default shell back to bash
- Why not Fish?
- Why not jump temporarily to Python?
- Video Tutorials
- Other Resources
- More on OSX
At its developer conference on June 4, 2019, Apple stated that, beginning with the Fall 2019 Catalina version of macOS, Z shell (zsh) would replace Bash as the default shell command language interpreter.
This article presents a deep but hopefully succinct guided tour through Z shell usage and issues.
BTW Shells are a command line interface (CLI) that allows users to interact with the computer’s operating system by typing rather than moving a mouse on a GUI (Graphical User Interface).
PROTIP: Ignore fake news clickbait articles such as “Upcoming MacOS Catalina to Ditch Bash in Favour of Zsh” (from InfoQ), which is technicall incorrect. The Apple announcement is about the default. After the change goes into effect, Bash will still be installed and you can still change the default back to Bash.
- Apple already ships Z shell with macOS (as well as the
shshell interpreter) along with
bash. But prompts are not compatible.
- The version of zsh is likely obsolete. So <a href-“#InstallZsh”>install latest (parallel) version of Zsh</a> and set
- Execute zsh temporarily or as default
- zsh is better than bash/sh in several aspects
- Bash scripts break in Zsh (due to “bashisms”)
- Bashisms in bash scripts break in zsh, so existing bash scripts need to be migrated and tested
- Apple’s disruption opens up a discussion about compatibility of script commands across platforms and POSIX.
- Change the default shell back to bash
- Switch to an alternative shell replacement FISH?
- Exit to Python and back is perhaps the ultimate solution for cross-platform compatibile automation
First, open a Terminal on your macOS machine.
Zsh is already there (before Catalina)
Identify the version of zsh installed while on Mojave or earlier:
The response if you are at Mojave version:
zsh 5.0.5 (x86_64-apple-darwin14.0)
Notice no “GNU”.
In fact, several shell programs are installed with macOS:
Get a list of shells allowed:*
The response (on macOS Mojave) is:
/bin/bash /bin/csh /bin/ksh /bin/sh /bin/tcsh /bin/zsh
Wikipedia says “Zsh is an extended Bourne shell with a large number of improvements, including some features of Bash, ksh, and tcsh.” (Korn shell). tcsh (“tee shell”) is the native root shell for BSD-based systems such as FreeBSD, which early versions of MacOS is based. OS X switched from tcsh to bash in version 10.3. tcsh is based on csh, the C-shell.
PROTIP: The predecessor Bourne Shell can still be invoked by the
The response on macOS Mojave:
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18) Copyright (C) 2007 Free Software Foundation, Inc.
Why the disruption?
Obtain the version metadata:
The response on a machine running macOS Mojave:
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18) Copyright (C) 2007 Free Software Foundation, Inc.
The Verge notes that “Apple is stuck using version 3.2 of bash that has been licensed under GPLv2, as newer versions are licensed under GPLv3. Apple has kept clear of using GPLv3 packages in macOS as the license is generally more restrictive to companies like Apple that sign their own code and it includes explicit patent grants, too.”
associative arrays was introduced in Bash 4.0 February 2009.
macOS does not have latest Zsh
PROTIP: The version of zsh from Apple is likely not the latest because Apple does not automatically update it (like modern browsers do).
The latest version of Zsh available is listed in the Zsh website’s News page at
At time of writing, it’s 5.7.1
PROTIP: Zshell has been around so long that its source code repository is still on Sourceforge</a> since its initial release in 1990, at https://sourceforge.net/p/zsh/code/ci/master/tree/ and https://sourceforge.net/projects/zsh/files
Install latest Z Shell (zsh) using Homebrew
PROTIP: Since one should not overwrite Apple’s stuff, upgrading requires installation of the latest version as if it did not already exist.
PROTIP: Z shell is also called zsh becuase of the Homebrew command to install it on macOS:https://github.com/robbyrussell/oh-my-zsh/wiki/themes
brew install zsh zsh-completions
NOTE: Ignore advice to add
-without-etcdirto disable the reading of Zsh rc files in /etc, which is obsolete since 2018.
BTW, On an Ubuntu system the install command is a similar:
sudo apt install zsh
==> Installing dependencies for zsh: pcre ==> Installing zsh dependency: pcre ==> Downloading https://homebrew.bintray.com/bottles/pcre-8.43.mojave.bottle.tar ==> Downloading from https://akamai.bintray.com/08/08e7414a7641d1e184c936537ff67 ######################################################################## 100.0% ==> Pouring pcre-8.43.mojave.bottle.tar.gz 🍺 /usr/local/Cellar/pcre/8.43: 204 files, 5.5MB ==> Installing zsh ==> Downloading https://homebrew.bintray.com/bottles/zsh-5.7.1.mojave.bottle.tar ==> Downloading from https://akamai.bintray.com/79/793d87f67e64a5e01dfdea890af21 ######################################################################## 100.0% ==> Pouring zsh-5.7.1.mojave.bottle.tar.gz 🍺 /usr/local/Cellar/zsh/5.7.1: 1,515 files, 13.3MB ==> Downloading https://github.com/zsh-users/zsh-completions/archive/0.30.0.tar. ==> Downloading from https://codeload.github.com/zsh-users/zsh-completions/tar.g ######################################################################## 100.0% ==> Caveats To activate these completions, add the following to your .zshrc: fpath=(/usr/local/share/zsh-completions $fpath) You may also need to force rebuild `zcompdump`: rm -f ~/.zcompdump; compinit Additionally, if you receive "zsh compinit: insecure directories" warnings when attempting to load these completions, you may need to run this: chmod go-w '/usr/local/share' ==> Summary 🍺 /usr/local/Cellar/zsh-completions/0.30.0: 133 files, 980.2KB, built in 8 seconds
PROTIP: Notice that the “mojave” in the message above means that the installation needs to be repeated with each new version of Apple macOS.
PROTIP: Notice the “To activate these completions, add the following to your .zshrc:”
Get information about what was installed:
brew info zsh
zsh: stable 5.7.1 (bottled), HEAD UNIX shell (command interpreter) https://www.zsh.org/ /usr/local/Cellar/zsh/5.7.1 (1,515 files, 13.3MB) * Poured from bottle on 2019-06-05 at 14:43:05 From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/zsh.rb ==> Dependencies Required: ncurses ✔, pcre ✔ ==> Options --HEAD Install HEAD version ==> Analytics install: 42,894 (30 days), 131,013 (90 days), 641,092 (365 days) install_on_request: 41,019 (30 days), 124,257 (90 days), 587,940 (365 days) build_error: 0 (30 days)
Confirm location of installation:
ls -la /usr/local/bin/zs*
lrwxr-xr-x 1 wilsonmar admin 27 Jun 5 14:43 /usr/local/bin/zsh -> ../Cellar/zsh/5.7.1/bin/zsh lrwxr-xr-x 1 wilsonmar admin 33 Jun 5 14:43 /usr/local/bin/zsh-5.7.1 -> ../Cellar/zsh/5.7.1/bin/zsh-5.7.1
Prompt settings are different
If you type in the command while in the Bash shell:
The response prompt can be a mess because zsh doesn’t understand prompt specifiers for Bash. For example, my
~/.bash_profilefile defines a PS1 system environment variable with this specification:
\n \w\[\033[33m\] $(parse_git_branch)\[\033[00m\]\n$
\nadd a blank line and indents. For easier readability.
\npositions the cursor always on the same spot at the left of the screen so I don’t have to search for it.
PROTIP: Zsh spits out the PS1 variable when it can’t find the
~/.zshrcthat it uses (instead of
~/.bash_profileto define prompts, paths, etc.
zsh has a builtin command to emulate different shells:
- Perform commands.
emulate -R zsh
-Rrestores all the options to their default values for that shell.
Execute zsh temporarily
To Switch to zsh (replace your current shell with a new shell):
byenow exits zsh just as
Similarly, to switch to Bash from inside Zsh:
exec bash -l
--login) avoids a source reset.
zsh always read ,
.zshrcwhen starting an interactive shell, whether it’s a login one or not.
Default to Zsh
To flip back to Bash shell:
PROTIP: Control the look of the prompt in Bash by setting the PROMPT_COMMAND variable to one or more of the special characters will customize it for you.
To Switch to bash (replace your current shell with a new shell):
Similarly, to switch to Bash from inside Zsh:
exec bash -l `-l` (for `--login`) avoids a source reset.
Identify where shells are installed:
You should see:
You should see:
In Terminal, select Preferences > General > Shells open to change to:
Close and start your Terminals for changes to take.
chsh to make zsh the default
PROTIP: Some tutorials recommend using a command such as “chsh -s /bin/zsh”. But my recommendation is instead of a hard-coded path, have the command look up where zsh is located. This is because there can be multiple versions of zsh around.
chsh -s $(which zsh)
You would be prompted “Password for …”, so give it up.
Alternately, there is an approach which directly updates the Mac’s user db, without need to edit /etc/shells, is the
dscl(Directory Services Command Utility) also used to create new users, reset password, etc. from the command line rather than the GUI. (see the dscl man page).
Verify the system variable that defines the shell your system defined:
If you have Z shell, it would be:
PROTIP: The concern is making Z shell the default, not whether to install it.
Verify the switch
Verify the process running:
If you have Bash, it would return:
If you have Z shell, it would return:
Why Z shell is better than Bash
Here is a small list of advantages to using zsh.
Automatic cd: Just type the name of the directory anywhere in the path (you don’t need to specify the entire path)
Recursive path expansion: For example “/u/lo/b” expands to “/usr/local/bin”
Spelling correction and approximate completion: If you make a minor mistake typing a directory name, ZSH fixes it for you
Z shell is case insensitive.
Floating point support (Bash does not have)
Hash data structures support (requires upgrade to Bash 4)
File globbing in Z uses character patterns that are similar, but not identical to those used in regexps. Example: “He said his name was [KC]arl” to (express ambiguity). See https://www.linuxnix.com/10-file-globbing-examples-linux-unix
To display all available options for a command, along with a short description, type the command followed by -, then hit tab. Tab through to select the option shown.
Zsh comes with commands that need to be installed in Bash:*:
Z shell comes with Git client and Git completions (see http://eseth.org/2010/git-in-zsh.html#show-stashed-changes)
zcalcis a command-line calculator for doing quick calculations without leaving the terminal. (Load it with autoload -Uz zcalc and run with zcalc)
zparseoptsparses complex options (arguments) provided to shell scripts
zmvdoes massive file/directory renames. To append ‘.txt’ to every file name run:
zmv –C '(*)(#q.)' '$1.txt'
- Z comes with an auto-update tool that makes it easier to keep installed plug-ins and themes updated.
See a list of Z Shell built-in commands in the Zsh Manual at:
More opinions about zsh:
- https://www.freecodecamp.org/news/how-to-configure-your-macos-terminal-with-zsh-like-a-pro-c0ab3f3c1156 from 15 November 2018
Script Bashisms break in Zsh
PROTIP: Unlike Microsoft Windows which selects the program to execute a file based on the file name extension, Linux looks into the first line within the file. That line which is called a “Shebang”. Thus, shell scripts do not require a file extension, but many add one anyway, such as
In a script, if you use Bashisms, have this she-bang:
By contrast, the recommended way to call python is:
PROTIP: The use of
envtakes a bit longer because it goes out and finds the program rather than specifying the program “bash” in a certain path:
The problem with the above location is that it is not available in other platforms and thus makes the script non-portable.
Alternately, to ensure your script runs on the widest variety of systems:
This is because the
shinterpreter is POSIX compliant. Bash is not.
BTW In Ubuntu, the default shell was changed from bash (the GNU Bourne-Again Shell), then to /bin/sh, then to dash (the Debian Almquist Shell) in Ubuntu 6.10. That’s back when actual Bourne shell and Korn shell weren’t open-source at that time. * Dash is faster to start than bash.
There are also other distributions, such as Cygwin (emulator in Windows), MinGW(used in git bash), CentOS, Amazon Linux, Red Hat, Debian, etc.
Each flavor of shell has a different algorithm for deciding which startup script to run.
The big issue of concern is compatibility with other Linux distributions. A further shift to MacOS’s FreeBSD roots would add “cognitive overload”. because the vast majority of other CLI ecosystems remain on Bash. Constantly thinking about whether one needs to use Z shell or Bash shell disrupts the “muscle memory” of developers, and thus reduce productivity.
Cloud shells on cloud vendors use Bash
- Azure Cloud Shell https://shell.azure.com/
One can configure Windows 10 to use Z shell but many use git bash which remains in Bash.
Azure’s list of its Cloud Shell features includes zsh.
Some find Apple’s move exciting because Apple is leading the rest of the tech world into the future, like Apple did about removing 3.5 mm headphone jacks on iPhone X.
So the transition will be painful, as the dream of one shell script that runs across operating systems is now dashed.
Z Shell plug-ins
~/.zshrc file (
~ meaning in your $HOME directory) is one file that enables and disables plugins.
plugins=( osx iterm2 brew git python pip colored-man colorize zsh-autosuggestions yarn web-search jsontools macports node osx sudo thor docker zsh-syntax-highlighting mouse )
WARNING: Adding plugins cause your shell startup time to increase.
The sequence is important. “prompt” must come last. history-substring-search must come before it, and syntax-highlighting must come before that.*
Z shell Prompts
Zsh has several built-in advanced prompts.*
Start Zsh advanced prompt support with
autoload -U promptinit, then
List available prompt names with
- Select a particular one with prompt prompt-name.
Display all available prompts with
- Except for the list and display commands above, insert the other ones in
~/.zshrcto be automatically executed at shell start, with the prompt you chose.
Z shell themes
The powerlevel9k theme adds a right-aligned info box, integration with git and command history, incredible customization, and wraps it all up in a slick interface based on the powerline plugin for vim.
git clone https://github.com/bhilburn/powerlevel9k.git \ ~/.oh-my-zsh/custom/themes/powerlevel9k
Enable it in .zshrc:
Customize the default prompt by defining POWERLEVEL9K_LEFT_PROMPT_ELEMENTS in your .zshrc. For example, to define a minimal prompt:
POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(vcs dir rbenv) POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(root_indicator background_jobs status load)
Read documentation at https://github.com/bhilburn/powerlevel9k#customizing-prompt-segments
Based on https://www.howtogeek.com/362409/what-is-zsh-and-why-should-you-use-it-instead-of-bash/
Other themes for Z shell:
Bashisms against portability
“Bashisms” are shell syntax which is only understood by the bash shell and not other shells.
To enable a shell script to run on more platforms, convert use of “bashisms” to POSIX: https://en.wikipedia.org/wiki/POSIX#POSIX.2
TODO: To verify, run linter shellcheck.
NOTE: The Ubuntu checkbashisms Perl program to identify bashisms in a script.
On Debian, it’s shipped as part of the package maintainer tools.
If you use the Atom text editor, install the plug-in for checkbashisms.
Configure a Git pre-commit hook to run https://tracker.debian.org/pkg/devscripts
Some of Bash’s scripting features are derived from the ksh and predecessor shells rather than those defined by POSIX standard defined by the IEEE at http://pubs.opengroup.org/onlinepubs/009695399/nfindex.html. The shell standard 1003.1-2004 is http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html
Set a script to disable non-POSIX usage to ensure portability, add:
set -o posix</strong>
Discussion about Bashisms are at:
Rather than using the magic variable $RANDOM, use this, which returns 20816:
random=`hexdump -n 2 -e '/2 "%u"' /dev/urandom`
- Indexes in an array within zsh begin with 1 vs. with 0 in bash and sh.
vzvol is a great example of modular shell code to ensure extensibility and cleanliness of code. It sources functions from files the same way you would import functions and libraries in C.
Z shell supports plugins and themes.
There are mutually exclusive alternatives:
Prezto (at https://github.com/sorin-ionescu/prezto) is a configuration framework for Zsh.
Antigen is a manager for Zsh plugins (bundles). Antigen lets you switch the prompt theme with a command (for the session) such as:
antigen theme candy
Oh My Zsh is the most commonly known.
Oh My Zsh
PROTIP: A user community website called “Oh My Zsh” at https://ohmyz.sh (@ohmyzsh) collects third-party plug-ins and themes for the Z shell. As of 2018, their GitHub repository has over 1,300 contributors, 200+ plug-ins, and over 140 themes (of varying quality).
Use this shell command to install:
sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
The initial response:
Cloning Oh My Zsh... Cloning into '/Users/wilsonmar/.oh-my-zsh'... remote: Enumerating objects: 1040, done. remote: Counting objects: 100% (1040/1040), done. remote: Compressing objects: 100% (957/957), done. remote: Total 1040 (delta 23), reused 852 (delta 20), pack-reused 0 Receiving objects: 100% (1040/1040), 691.56 KiB | 2.11 MiB/s, done. Resolving deltas: 100% (23/23), done. Looking for an existing zsh config... Found ~/.zshrc. Backing up to /Users/wilsonmar/.zshrc.pre-oh-my-zsh Using the Oh My Zsh template file and adding it to ~/.zshrc.
When prompted with this, type “n”:
Time to change your default shell to zsh: Do you want to change your default shell to zsh? [Y/n] n
The rest of the response:
Shell change skipped. __ __ ____ / /_ ____ ___ __ __ ____ _____/ /_ / __ \/ __ \ / __ `__ \/ / / / /_ / / ___/ __ \ / /_/ / / / / / / / / / / /_/ / / /_(__ ) / / / \____/_/ /_/ /_/ /_/ /_/\__, / /___/____/_/ /_/ /____/ ....is now installed! Please look over the ~/.zshrc file to select plugins, themes, and options. p.s. Follow us on https://twitter.com/ohmyzsh p.p.s. Get stickers, shirts, and coffee mugs at https://shop.planetargon.com/collections/oh-my-zsh
Set .zshrc start-up commands
Installing oh my zsh results in creation or update of file
~/.zshrc, with suggested settings commented out. Other examples are
PROTIP: Static values need to be defined to specify PATH, LANG, LC_ALL, PS1, PS2, PS4, etc. shell variables.
When the operating system starts, it looks for a file with a specific name to execute. Debian-family distros execute
.profile, and ignores the
.bash_profile that MacOS and RedHat derivatives (CentOS) execute.
The sh shell and Debian-family distros look for a file named
.profile to execute for user login. The bash shell, being generally backwards-compatible with /bin/sh, reads
.profile if one exists.
NOTE: There is a differentiation between interactive login. Each terminal window is a different process and require another password to login.
PROTIP: Some installers, such as the rvm (Ruby) installer, adds to the .profile file. So edit the .profile file add at the top of the a command to notify that it is being executed. Such as:
printf ">>> ~/.profile being processed ..."
Open the Terminal program.
This causes an interactive shell to be invoked.
You are required to provide your password because it logs you in as a user.
The zsh shell always reads file
.zshrcfor an interactive shell, whether it’s a login one or not.
Switch to a text editor to edit in your Home folder file
This is from here on GitHub. Robby Russell is the original developer of the project.
\# If you come from bash you might have to change your $PATH. export PATH=$HOME/bin:/usr/local/bin:$PATH \# Path to your oh-my-zsh installation. export ZSH="$HOME/.oh-my-zsh" # Set name of the theme to load --- if set to "random", it will # load a random theme each time oh-my-zsh is loaded, in which case, # to know which specific one was loaded, run: echo $RANDOM_THEME # See https://github.com/robbyrussell/oh-my-zsh/wiki/Themes ZSH_THEME="robbyrussell" # Set list of themes to pick from when loading at random # Setting this variable when ZSH_THEME=random will cause zsh to load # a theme from this variable instead of looking in ~/.oh-my-zsh/themes/ # If set to an empty array, this variable will have no effect. # ZSH_THEME_RANDOM_CANDIDATES=( "robbyrussell" "agnoster" ) # Uncomment the following line to use case-sensitive completion. # CASE_SENSITIVE="true" # Uncomment the following line to use hyphen-insensitive completion. # Case-sensitive completion must be off. _ and - will be interchangeable. # HYPHEN_INSENSITIVE="true" # Uncomment the following line to disable bi-weekly auto-update checks. # DISABLE_AUTO_UPDATE="true" # Uncomment the following line to automatically update without prompting. # DISABLE_UPDATE_PROMPT="true" # Uncomment the following line to change how often to auto-update (in days). # export UPDATE_ZSH_DAYS=13 # Uncomment the following line if pasting URLs and other text is messed up. # DISABLE_MAGIC_FUNCTIONS=true # Uncomment the following line to disable colors in ls. # DISABLE_LS_COLORS="true" # Uncomment the following line to disable auto-setting terminal title. # DISABLE_AUTO_TITLE="true" # Uncomment the following line to enable command auto-correction. # ENABLE_CORRECTION="true" # Uncomment the following line to display red dots whilst waiting for completion. # COMPLETION_WAITING_DOTS="true" # Uncomment the following line if you want to disable marking untracked files # under VCS as dirty. This makes repository status check for large repositories # much, much faster. # DISABLE_UNTRACKED_FILES_DIRTY="true" # Uncomment the following line if you want to change the command execution time # stamp shown in the history command output. # You can set one of the optional three formats: # "mm/dd/yyyy"|"dd.mm.yyyy"|"yyyy-mm-dd" # or set a custom format using the strftime function format specifications, # see 'man strftime' for details. # HIST_STAMPS="mm/dd/yyyy" # Would you like to use another custom folder than $ZSH/custom? # ZSH_CUSTOM=/path/to/new-custom-folder # Which plugins would you like to load? # Standard plugins can be found in ~/.oh-my-zsh/plugins/* # Custom plugins may be added to ~/.oh-my-zsh/custom/plugins/ # Example format: plugins=(rails git textmate ruby lighthouse) # Add wisely, as too many plugins slow down shell startup. plugins=(git) source $ZSH/oh-my-zsh.sh # User configuration # export MANPATH="/usr/local/man:$MANPATH" # You may need to manually set your language environment # export LANG=en_US.UTF-8 # Preferred editor for local and remote sessions # if [[ -n $SSH_CONNECTION ]]; then # export EDITOR='vim' # else # export EDITOR='mvim' # fi # Compilation flags export ARCHFLAGS="-arch x86_64" # Set personal aliases, overriding those provided by oh-my-zsh libs, # plugins, and themes. Aliases can be placed here, though oh-my-zsh # users are encouraged to define aliases within the ZSH_CUSTOM folder. # For a full list of active aliases, run `alias`. # # Example aliases alias zshconfig="mate ~/.zshrc" alias ohmyzsh="mate ~/.oh-my-zsh"
mateinvokes the TextMate editor.
#to activate lines.
# Per Homebrew zsh Caveat: fpath=(/usr/local/share/zsh-completions $fpath) # This can also be in a ~/.zprofile : export PS1="\n %10F%m%f:%11F%1~%f \$ \n"
Source aliases for compatibility
PROTIP: So that you can switch between zsh, bash, sh, put settings that are common among them into a separate file and have each script invoke the file.
Save the files in GitHub for version control.
Files in the $HOME folder are backed up.
Change default shell back to bash
Switch back the default to Bash:
chsh -s $(which bash)
You should see the status response above.
Why not Fish?
Why didn’t Apple go further with alternative FOSS shell Fish (Friendly Interactive SHell)? Information about it:
- http://fishshell.com is its home page.
- Wikipedia on “Friendly_interactive_shell”
- Fish Shell Overview
Why not jump temporarily to Python?
There is a rising call for coding Python rather than shell code. Python code is easier to read and maintain, runs a little faster, and can have a proper unit test suite. This article argues the Python is more cross-platform.
Examples of common OS-level actions:
- parsing arguments provided when called
- Getting the Time
- Reading configuration files
- Downloading Web Pages and Files
- Replace in line - https://stackoverflow.com/questions/10507230/insert-line-at-middle-of-file-with-python
sed ‘s/fields/fields\nNew Inserted Line/’ file.txt
- Killing (and creating) processes
- Creating (and deleting) directories
- Running applications
- Doing date arithmetic
- symlinking files
- Managing files
Accessing and Using the Azure Cloud Shell hands-on lab on Linux Academy
The System Administrator’s Guide to Bash Scripting [7:28:37] video course
http://www.bash2zsh.com from Peter Stephenson in 2005 - http://www.bash2zsh.com/zsh_refcard/refcard.pdf
More on OSX
This is one of a series on Mac OSX:
- MacOS Hardware and accessories
- MacOS Boot-up
- MacOS Terminal Tips and Tricks
- MacOS Keyboard tricks
- Text editors and IDEs on MacOS
- MacOS Setup automation
- MacOS Homebrew installers
- Manage Disk Space on MacOS
- Data Backups on MacOS
- Ports open
- Applications on MacOS
- Windows on Apple MacOS
- Packer create Vagrant Windows image
- Python on MacOS
- Maven on MacOS
- Ruby on MacOS
- Java on MacOS
- Node on MacOS installation
- PHP on MacOS
- Scala ecosystem