Today I would like to share with you my simple yet useful zsh(1) shell config that I use daily.
You may want to check other articles in the Ghost in the Shell series on the Ghost in the Shell β Global Page where you will find links to all episodes of the series along with table of contents for each episodeβs contents.
I have been various UNIX and Linux systems since almost two decades. Through that time I was always looking for the best interactive shell out there. Obviously I have started with the bash(1) on Linux and it generally worked but bash(1) also did not impressed me at all. Just a shell. I have similar experiences with the ksh(1) shell. Today even the plain POSIX /bin/sh shell on FreeBSD has basic completion similar in many ways to what bash(1) or ksh(1) allows. The bash(1) shell gets little better when you install the bash-completion companion but its very limited still.
When I moved to FreeBSD I got to know its default tcsh(1)/csh(1) shell … which is PITA to use and scripting. Its pointless to learn limited CSH shell syntax in 2021. Omit it at any costs. After I settled a little in the FreeBSD wonderland I started to try other shells such as zsh(1) or fish(1) shells. I really liked fish(1) shell preconfigured setup and its defaults because it required literally zero effort to use it at its peak possibilities … but when I tried one of my typical use cases which is some commands | while read I; other command "${I}"; done it came to me that fish(1) is very limited shell and even does not support critical POSIX /bin/sh syntax! What a disappointment to say the last.
I abandoned the fish(1) shell and went to the zsh(1) which by default does not do more then a bash(1) shell and needs well thought configuration to be useful and powerful. After checking some guides and howtos about zsh(1) shell I started to create my own config and this was the interactive environment I was looking for. Of course I had several newbie problems or things that did not worked well for me like for example automatic completion of user home directories or UPPERCASE to lowercase automatic translation but after digging more into the zsh(1) config and man pages I finally settled with sensible and reasonable zsh(1) shell config.
I also tried various ready to use zsh(1) preconfigurations such as PowreLevel10k or Oh-My-Zsh but none of them really satisfied my while being kinda ‘blackbox’ with features that I do not really need. I really like to use things that I understand under the hood so I stayed with my quite simple yet fast loading config.
Why ZSH Shell Anyway?
Besides The Usual Suspects (really great movie by the way) like recursive search with [CTRL]+[R] for forward search and [CTRL]+[SHIFT]+[R] for reverse search – argument completion for most commands like shown below.
% tar -[TAB]
A -- append to an archive
c -- create a new archive
f -- specify archive file or device
t -- list archive contents
u -- update archive
v -- verbose output
x -- extract files from an archive
The graphical example of that can be shown here. Of course I am not able to show [TAB] key there as I am able to add in the ‘text’ examples.
Interactive argument completion like showing the list of processes you can kill(1) by pressing the [TAB] key while being at kill(1) or killall(1) commands.
% kill -9 [TAB]
9289 4 Ss+ 0:00.62 -zsh (zsh)
16994 2 Is 0:00.28 -zsh (zsh)
17860 1 Is+ 0:00.17 -zsh (zsh)
23797 3 Is+ 0:00.23 -zsh (zsh)
30335 4 S+ 0:00.01 -zsh (zsh)
32381 4 R+ 0:00.00 ps
44994 0 Is+ 0:00.50 -zsh (zsh)
59828 2 I+ 0:00.02 /bin/sh /usr/bin/man zsh
65435 2 I+ 0:00.05 less
Similar with the pkill(1) command when trying to autocomplete with ‘h‘ letter. The filter adds all running processes that have ‘h‘ letter in them – not only processes that start with the ‘h‘ letter.
There are also other more sophisticated completions like completioning the file name but not from the front but by the part of it … or by extension. Take a look at these two examples below. This is out directory listing that we will be using as an example here.
% exa -l
drwxr-xr-x - vermaden 2021-09-18 21:47 and a really PITA dir with spaces
.rw-r--r-- 0 vermaden 2021-09-18 21:54 huge.iso
.rw-r--r-- 0 vermaden 2021-09-18 21:46 really.async.example.txt
.rw-r--r-- 0 vermaden 2021-09-18 21:47 some-plain-file.txt
Three files and one directory with spaces in its name.
To autocomplete any of them with bash(1)you would have to start typing the file or dir name from the beginning. The fish(1) shell is on par with zsh(1) here as it would also support the thing that I want to show you.
First things first – the cd(1) command to change current working directory. Because there is only ONE directory there both zsh(1) and fish(1) shells would properly autocomplete the only once and a really PITA dir with spaces dir for the cd(1) command like shown below.
% exa -l drwxr-xr-x - vermaden 2021-09-18 21:47 and a really PITA dir with spaces .rw-r--r-- 0 vermaden 2021-09-18 21:54 huge.iso .rw-r--r-- 0 vermaden 2021-09-18 21:46 really.async.example.txt .rw-r--r-- 0 vermaden 2021-09-18 21:47 some-plain-file.txt % cd [TAB] // after pressing [TAB] once becomes this % cd and\ a\ really\ PITA\ dir\ with\ spaces
The bash(1) (and csh(1)/tcsh(1) for the record) would obviously need to start from the first letter of any of those dir or files trying the really stupid completion method.
Now the second part about completion of files extensions or names in the middle of dirs or files. Both zsh(1) and fish(1) shells support that. Examples below.
% exa -l drwxr-xr-x - vermaden 2021-09-18 21:47 and a really PITA dir with spaces .rw-r--r-- 0 vermaden 2021-09-18 21:54 huge.iso .rw-r--r-- 0 vermaden 2021-09-18 21:46 really.async.example.txt .rw-r--r-- 0 vermaden 2021-09-18 21:47 some-plain-file.txt % cat txt[TAB] // NOW zsh(1) will show all files that have 'txt' string in it % cat le.txt[TAB] // AFTER SECOND [TAB] HIT IT WILL LIST THEM WITH MENU FEATURE (MORE ON THAT IN A MOMENT) % cat really.async.example.txt[TAB] really.async.example.txt some-plain-file.txt // NOW FIRST FILE WITH 'txt' IS USED - HIT [TAB] AGAIN TO SWITCH TO NEXT ONE % cat some-plain-file.txt[TAB] really.async.example.txt some-plain-file.txt // YOU CAN ALSO USE ARROW KEYS TO SELECT BETWEEN THEM - CHECK SCREENSHOT BELOW
Example of menu completion feature below.
Time to stop showing off and start to provide some useful content.
System Config
There are lots of guides and ideologies about how you spread your zsh(1) configuration between system wide config file and user customized ones like these:
- /etc/zshenv
- /etc/zprofil
- /etc/zshrc
- /etc/zlogin
- /etc/zlogout
- ~/.zshenv
- ~/.zprofile
- ~/.zshrc
- ~/.zlogin
- ~/.zlogout
My take? Lets not make some big issue about that. I really like simple sensible setups and I use zsh(1) as interactive shell so ‘system wide’ configuration is not crucial here. To make things as simple as possible I only use two of all of the above. The /usr/local/etc/zshrc for the ‘system wide’ part and ~/.zshrc for my ‘user’ part. Thats it. I have been doing that since more then a decade and it worked for me like a charm but as in every case your millage may vary here.
As there are too many Linuxisms out there assuming that you are using Ubuntu Linux or that bash(1) shell is always available as /bin/sh binary after 16 years of me using FreeBSD UNIX there for sure will several BSDisms but at least they are harmless and documented π
The /usr/local/etc/zshrc (or should I say /etc/zshrc on Linux and other then FreeBSD UNIX systems) is available on my GitHub scripts repository – https://raw.githubusercontent.com/vermaden/scripts/master/zshrc – because WordPress has some problems on displaying it correctly here.
While its comments generally say a lot about that is happening there I will also add several notes here.
I have left disabled the UPPERCASE from/to lowercase transparent translation because while it helped at about 10% of times it really pissed me off with pointless autocomplete suggestions the 90% of the time. If your work/complete schema is different the enable and test it. Maybe it will suit you better then me. Below is the part I am talking about – in enabled form.
# ZSH COMPLETION CASE (IN)SENSITIVE
zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}'
At the end of the config you will find ‘casting’ for the best ls(1) solution existing in a system. After trying various listing commands such as:
- FreeBSD ls(1) command
- Linux ls(1) (known as gls(1) under FreeBSD)
- New exa(1) command
- New lsd(1) command
I have abandoned lsd(1) as besides colors its close to useless to use exa(1) as primary listing command. The second one that I recommend (that may be a surprise to FreeBSD users) would be the Linux ls(1) command from sysutils/coreutils package on FreeBSD. The last ‘resort’ command would be the FreeBSD ls(1) command as documented in the config. Why you should ask? The answer is quite simple – the directory listing. Both exa(1) and gls(1) have options to list directories (and what is more important SYMLINKS to directories) first. The FreeBSD ls(1) not only does not list symlinks to directories first – it also treat any directory as any other object and just list directories and symlinks put somewhere there withing all other files. Its unacceptable for me. Its just a messy pointless output. As much as I like and respect FreeBSD UNIX this is just plain fucking stupid. No matter how much history is in it.
Here is the comparison between them. I also wanted to show you the long listing (with -l option obviously) but its the same ‘not dirs first’ behavior for the FreeBSD ls(1) so not need for that.
User Config
I will not add the ‘user’ part of my zsh(1) config and add some comments below.
If for any reason WordPress would mess the above config up here is the plain text version – https://raw.githubusercontent.com/vermaden/scripts/master/DOT.zshrc – available from my GitHub scripts repository.
I will not try to describe more useful parts of it. There are tons of aliases there from which these are more interesting ones.
Quickly grep(1) for IP or MAC address with grepIP or grepMAC functions respectively.
While most folks out there recommend the [CTRL]+[L] shortcut I really rarely use it. I know it and I generally advocate for those old UNIX shortcuts but for some reason the right [CTRL] key on my keyboard can not exist. I just do not use it at all. Its like in new condition on any keyboard I use. I should swap [CTRL] keys every quarter to make them look similarly used π
Because of that I often use ‘c‘ shortcut to clear the screen. As I was forced to use Windows in my earlier employer I also had The Microsoft equivalent for clearing the terminal – the cls command – thus you will also find an alias for that in my config – so called muscle memory is still strong π
Other aliases just have some arguments that are useful to add in 95% of cases.
Now some comment on the functions. There are for sure the Short Query Functions that I described in my Ghost in the Shell series. There is also additional dc alias to take me into directory where a file is. For example I have full path file under my X11 PRIMARY BUFFER. For example its /home/vermaden/gfx/wallpapers/amiga-500-grey.png value. I can now type cd and paste that buffer and then remove the amiga-500-grey.png characters with [BACKSPACE] key or type dc and then paste /home/vermaden/gfx/wallpapers/amiga-500-grey.png value and hit [ENTER] key. That alias(1) will now take me to the /home/vermaden/gfx/wallpapers/ dir.
There are several FreeBSD related commands also. Both pkg(8) or FreeBSD Ports related.
There are several that are health related such as BMI/BFP/BMR calculations that I sometimes use.
I really like the (and often use) the math function as it has the best of both worlds – the expr(1) and bc(1) commands.
There are also several functions related to conversions like converting the battery capacities between the Wh and mAh values or inches to centimeters conversions.
Similarly to the FreeBSD ls(1) command I also prefer to use the Linux (or should I say GNU) version of cal(1) command (known as gcal(1) in FreeBSD).
I also sometimes use the day function to highlight the exact day in the context of full year. Sometimes (quite rarely but still) its useful to know each occurrence of the 19 day of each month in current year. Below you will find screenshot with example.
You probably noticed the ~/.zshrc.DOAS.SUDO file at the beginning. Its about the detection of both sudo(8) and doas(1) supervisor commands. I prefer the more secure and simpler doas(1) command so when both are detected in the system then the doas(1) will be chosen as the right one.
The ~/.zshrc.DOAS.SUDO contents are here – https://raw.githubusercontent.com/vermaden/scripts/master/DOT.zshrc.DOAS.SUDO – available from my GitHub scripts repository.
Summary
As the zsh(1) shell is very configurable there are probably at least dozen guides that make it better then me and in more depth but I just wanted to share all these with you as many of you asked what I actually use as my daily shell ‘driver’ setup.
Maybe you will be able to show me some other interesting zsh(1) tips s that would make it even more productive setup π
UPDATE 1 – fish(1) Coloring and Completion in zsh(1)
One of the things I likes about the fish(1) shell was its ‘predictive’ showing what command can be executed completing from command history … I now also have that in the zsh(1) shell with two additional packages from FreeBSD.
First you need to install shells/zsh-autosuggestions and shells/zsh-syntax-highlighting packages.
# pkg install -y \
shells/zsh-autosuggestions \
shells/zsh-syntax-highlighting
Updating FreeBSD repository catalogue... FreeBSD repository is up to date. All repositories are up to date. The following 2 package(s) will be affected (of 0 checked): New packages to be INSTALLED: zsh-autosuggestions: 0.7.0 zsh-syntax-highlighting: 0.7.1,1 Number of packages to be installed: 2 40 KiB to be downloaded. [1/2] Fetching zsh-autosuggestions-0.7.0.pkg: 100% 9 KiB 8.7kB/s 00:01 [2/2] Fetching zsh-syntax-highlighting-0.7.1,1.pkg: 100% 31 KiB 31.9kB/s 00:01 Checking integrity... done (0 conflicting) [1/2] Installing zsh-autosuggestions-0.7.0... [1/2] Extracting zsh-autosuggestions-0.7.0: 100% [2/2] Installing zsh-syntax-highlighting-0.7.1,1... [2/2] Extracting zsh-syntax-highlighting-0.7.1,1: 100%
===== Message from zsh-autosuggestions-0.7.0: -- Add the line below to your .zshrc to enable auto suggestions. source /usr/local/share/zsh-autosuggestions/zsh-autosuggestions.zsh
===== Message from zsh-syntax-highlighting-0.7.1,1: -- Add the line below to *the end of* your .zshrc to enable highlighting. source /usr/local/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
The second part of the job is to add two additional scripts to your zshrc config file as shown below.
# ADDITIONAL COMPLETIONS zsh-autosuggestions
if [ -e /usr/local/share/zsh-autosuggestions/zsh-autosuggestions.zsh ]
then
source /usr/local/share/zsh-autosuggestions/zsh-autosuggestions.zsh
fi
# ADDITIONAL COMPLETIONS zsh-syntax-highlighting
if [ -e /usr/local/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh ]
then
source /usr/local/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
fi
Now your zsh(1) shell will color the commands and also underline the existing files/directories and also showing grey additional completion of last time usage from history.
Not sure if now zsh(1) makes ALL the bells and whistles that fish(1) shell does but its definitely very small difference now if you are a big fish(1) fan π
EOF
Β