Apple has announced that in macOS 10.15 Catalina the default shell will be zsh
.
In this series, I will document my experiences moving bash
settings, configurations, and scripts over to zsh
.
- Part 1: Moving to zsh
- Part 2: Configuration Files
- Part 3: Shell Options
- Part 4: Aliases and Functions (this article)
- Part 5: Completions
- Part 6: Customizing the
zsh
Prompt - Part 7: Miscellanea
- Part 8: Scripting
zsh
This series has grown into a book: reworked and expanded with more detail and topics. Like my other books, I plan to update and add to it after release as well, keeping it relevant and useful. You can order it on the Apple Books Store now.
As I have mentioned in the earlier posts, I am aware that there are many solutions out there that give you a pre-configured ‘shortcut’ into lots of zsh
goodness. But I am interested in learning this the ‘hard way’ without shortcuts. Call me old-fashioned. (“Uphill! In the snow! Both ways!”)
Aliases
Aliases in zsh
work just like aliases in bash
. You declare an alias with the alias
(built-in) command and it will work as a text replacement at the beginning of the command prompt:
alias ll='ls -al'
You can just copy your alias declarations from your .bash_profile
or .bashrc
to your .zshrc
. I had aliases for ..
and cd..
which are now handled by Auto CD and shell correction respectively, so I didn’t bother to move those. (part 3: ‘Shell Options’)
After the alias is declared, you can use it at the beginning of a command. When you try to use the alias anywhere else in the command, the alias will not work:
% sudo ll
sudo: ll: command not found
Global Aliases
This is where zsh
has an advantage. You can declare an alias as a ‘global’ alias, and then will be replaced anywhere in the command line:
% alias -g badge='tput bel'
% sudo badge #<beeps> with privilege
Identifying Aliases
There is one more feature of zsh
that is useful with aliases. The which
command will show if a command stems from an alias substitution:
% which ll
ll: aliased to ls -l
However, when you try this with global aliases, the substitution occurs before the which
command can evaluate the alias, which leads to an unexpected result:
% which badge
/usr/bin/tput
bel not found
You can suppress the alias substitution by escaping the first character or by quoting the entire alias name:
% which \badge
badge: globally aliased to tput bel
% which 'badge'
badge: globally aliased to tput bel
Functions
As with aliases, functions in your zsh
configuration will work just as they did in bash
.
function vnc() {
open vnc://"$USER"@"$1"
}
This code in your zsh
configuration file will define the vnc
function and make it available in the shell.
Autoload Functions
However, zsh
has some features which make using functions more flexible. There is (once again) a bit of configuration required to get this working.
Instead of declaring the function directly the configuration file, you can put the function in a separate file. zsh
has a built-in variable called fpath
which is an array of paths where zsh
will look for files defining a function. You can add your own directory to this search path:
fpath+=~/Projects/dotfiles/zshfunctions
Just having a file in the directory is not enough. You still have to tell zsh
that you want to use this particular function:
autoload vnc
This command tells zsh
: “’Declare a function named vnc
. To execute it, load a file named vnc
, it is somewhere in the fpath
.”
Note: you often see the -U
or -Uz
option added to the autoload
command. These options help avoid conflicts with your personal settings. They suppress alias substitution and ksh
-style loading of functions, respectively.
The vnc
file in my zshfunctions
directory can look like this:
# uses the arguments as hostnames for `open vnc://` (Screen Sharing)
# uses the $USER username as default account name
for x in $@; do
open vnc://"$USER"@"$x"
done
The vnc
function will open a Screen Sharing session with the current user name pre-filled in.
Initializing Autoload Functions
You could also put the code in the function file into a function
block:
function vnc {
for x in $@; do
open vnc://"$vnc_user"@"$x"
done
}
# initialization code
vnc_user="remote_admin"
alias screen_sharing='vnc'
The function name should match the function name declared with autoload
.
When you have additional code outside the function, the autoload
behavior changes. When the function is called for the first time, the function will be defined and the code outside the function will be run. The function itself will not be executed on the first run. On subsequent calls, the function will be executed and the code outside the function is ignored.
You can use this to provide setup and initialization code for the function. You can even have more functions defined in the function file. The above example declares and sets a variable to use for account name and an alias for the vnc
command.
Since you have to run the function once for the initialization, you often see this syntax in the zsh
configuration file:
autoload vnc && vnc
Which means ‘declare the function and if that succeeds run it.’
In some functions, the initialization code will already launch the function itself:
function vnc {
...
}
# initialization
vnc_user="remote_admin"
vnc()
Since the behavior will vary from each autoload
ed function to the next, be sure to study any documentation or the function’s code.
Identifying Functions
Finally, the which
command will show the function code:
% which vnc
vnc () {
for x in $@
do
open vnc://"$USER"@"$x"
done
}
The functions
command without any parameters, will print all functions (there will be a lot of them). Use functions +
to just list the function names.
Debugging Functions
When you are working on complex autoloaded functions, you will at some point have to do some debugging. You can enable tracing for functions with
% functions -t vnc
% vnc Client.local
+vnc:1> x=Client.local
+vnc:2> open vnc://armin@Client.local
You can disable tracing for this function with functions +t vnc
.
Next
In the next part we will enable, use and configure tab completions.