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 as follows.
# BASICS umask 022 export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin export EDITOR=vi export PAGER=less # USE ftp(1) PASSIVE MODE export FTP_PASSIVE_MODE=1 # DISABLE less(1) HISTORY export LESSHISTSIZE=0 # IMITATE sockstat(1) ON LINUX case $( uname ) in (Linux) alias sockstat="netstat -lnptu --protocol=inet,unix" ;; esac # ZSH HISTORY export HISTSIZE=655360 export HISTFILE="${HOME}/.zhistory" export SAVEHIST=${HISTSIZE} # ZSH HISTORY SEARCH bindkey -M vicmd '/' history-incremental-pattern-search-backward bindkey -M vicmd '?' history-incremental-pattern-search-forward # ZSH HISTORY SEARCH FOR vi(1) INSERT MODE bindkey -M viins '^R' history-incremental-pattern-search-backward bindkey -M viins '^F' history-incremental-pattern-search-forward # ZSH HISTORY MAPPINGS bindkey '^[[A' up-line-or-search bindkey '^[[B' down-line-or-search bindkey "^R" history-incremental-search-backward # ZSH USE SHIFT-TAB FOR REVERSE COMPLETION bindkey '^[[Z' reverse-menu-complete # ZSH LAST ARG FROM EARLIER COMMAND WITH [ALT]-[.] bindkey '\e.' insert-last-word # ZSH BEGIN/END OF LINE bindkey "^A" beginning-of-line bindkey "^E" end-of-line # KEY BINDINGS case "${TERM}" in (cons25*|linux) # PLAIN BSD/LINUX CONSOLE bindkey '\e[H' beginning-of-line # HOME bindkey '\e[F' end-of-line # END bindkey '\e[5~' delete-char # DELETE bindkey '[D' emacs-backward-word # ESC+LEFT bindkey '[C' emacs-forward-word # ESC+RIGHT ;; (*rxvt*) # RXVT DERIVATIVES bindkey '\e[3~' delete-char # DELETE bindkey '\eOc' forward-word # CTRL+RIGHT bindkey '\eOd' backward-word # CTRL+LEFT # RXVT WORKAROUND FOR screen(1) UNDER urxvt(1) bindkey '\e[7~' beginning-of-line # HOME bindkey '\e[8~' end-of-line # END bindkey '^[[1~' beginning-of-line # HOME bindkey '^[[4~' end-of-line # END ;; (*xterm*) # XTERM DERIVATIVES bindkey '\e[H' beginning-of-line # HOME bindkey '\e[F' end-of-line # END bindkey '\e[3~' delete-char # DELETE bindkey '\e[1;5C' forward-word # CTRL+RIGHT bindkey '\e[1;5D' backward-word # CTRL+LEFT # XTERM WORKAROUND FOR screen(1) UNDER xterm(1) bindkey '\e[1~' beginning-of-line # HOME bindkey '\e[4~' end-of-line # END ;; (screen) # GNU SCREEN bindkey '^[[1~' beginning-of-line # HOME bindkey '^[[4~' end-of-line # END bindkey '\e[3~' delete-char # DELETE bindkey '\eOc' forward-word # CTRL+RIGHT bindkey '\eOd' backward-word # CTRL+LEFT bindkey '^[[1;5C' forward-word # CTRL+RIGHT bindkey '^[[1;5D' backward-word # CTRL+LEFT ;; esac # ZSH COMPLETION CASE (IN)SENSITIVE # zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' # ZSH DISABLE USER COMPLETION FOR THESE NAMES zstyle ':completion:*:*:*:users' ignored-patterns \ dladm dbus distcache dovecot list ftp games gdm gkrellmd gopher gnats \ adm amanda apache avahi backup beaglidx bin cacti canna clamav daemon \ sshd sync sys syslog uucp vcsa smmsp svctag upnp unknown webservd xfs \ listen mdns fax mailman mailnull mldonkey mysql man messagebus netadm \ hacluster haldaemon halt hsqldb mail junkbust ldap lp irc xvm libuuid \ nscd ntp nut nx ident openldap operator pcap pkg5srv postfix postgres \ netcfg nagios noaccess nobody4 openvpn named netdump nfsnobody nobody \ proxy privoxy pulse pvm quagga radvd rpc rpcuser shutdown statd squid \ www-data news nuucp zfssnap rpm '_*' # ZSH COMPLETION OPSTIONS zstyle ':completion:*' completer _expand _complete _correct _approximate _history zstyle ':completion:*' matcher-list '' '' 'l:|=* r:|=*' 'l:|=* r:|=*' zstyle ':completion:*' list-colors '' zstyle ':completion:*' users root zstyle ':completion:*' menu select zstyle :compinstall filename '~/.zshrc' autoload -Uz compinit autoload -U colors && colors compinit # ZSH OTHER FEATURES unsetopt beep setopt no_beep setopt nohashdirs setopt extended_glob setopt auto_cd setopt auto_menu setopt list_rows_first setopt multios setopt hist_ignore_all_dups setopt append_history setopt inc_append_history setopt hist_reduce_blanks setopt always_to_end setopt no_hup setopt complete_in_word limit coredumpsize 0 # ZSH zshcontrib(1) zmv autoload zmv alias zmv_to_lower='zmv "*" "\${(L)f}"' alias zmv_to_upper='zmv "*" "\${(U)f}"' alias zmv_to_capital='zmv "*" "\${(C)f}"' alias zmv_to_hypen='zmv "*" "\$f:gs/ /-/"' alias zmv_to_underscore='zmv "*" "\$f:gs/ /_/"' # COLOR grep(1) export GREP_COLOR='1;32' export GREP_COLORS='1;32' export GREP_OPTIONS='--color=auto' alias grep='grep --color' alias egrep='egrep --color' # FreeBSD ifconfig(8) CIDR NOTATION export IFCONFIG_FORMAT=inet:cidr # SET ls(1) COLORS export LSCOLORS='exExcxdxcxexhxhxhxbxhx' export LS_COLORS='no=00:fi=00:di=00;34:ln=00;36:pi=40;33:so=00;35:bd=40;33;01:cd=40;33;01:or=01;05;37;41:mi=01;05;37;41:ex=00;32' # DISABLE XON/XOFF FLOW CONTROL (^S/^Q) stty -ixon # COLOR LIST # 30 - black # 34 - blue # 31 - red # 35 - magenta # 32 - green # 36 - cyan # 33 - yellow # 37 - white # COLOR PROMPT cSRV="%F{magenta}" case $( whoami ) in (root) cUSR="%F{red}" cPMT="%F{red}" ;; (*) cUSR="%F{green}%B" cPMT="" ;; esac cTIM="%F{cyan}%B" cPWD="%F{magenta}%B" cSTD="%b%f" export PS1="$cTIM%T$cSTD $cSRV%m$cSTD $cUSR%n$cSTD $cPWD%~$cSTD $cPMT%#$cSTD " export PS2="$cTIM%T$cSTD $cUSR>$cSTD $cPWD" # SET PROPER ENCODINGS case ${TERM} in (cons25*) export LC_ALL=en_US.ISO8859-1 ;; (*) export LC_ALL=en_US.UTF-8 ;; esac # ALIASES alias rehash='hash -r' alias make='env LANG=C LC_ALL=C make' alias h='history' alias c='clear' alias vim='vim -i NONE' alias fetch='fetch -Rr --no-verify-peer --no-verify-hostname' alias wget='wget -c -t 0' # LS/GLS/EXA case $( uname ) in (FreeBSD) if /usr/bin/env which exa 1> /dev/null 2> /dev/null then alias bls='/bin/ls -p -G -D "%Y.%m.%d %H:%M"' alias gls='gls -p --color=always --time-style=long-iso --group-directories-first --quoting-style=literal' alias ls='exa --time-style=long-iso --group-directories-first' elif /usr/bin/env which gls 1> /dev/null 2> /dev/null then alias bls='/bin/ls -p -G -D "%Y.%m.%d %H:%M"' alias ls=' gls -p --color=always --time-style=long-iso --group-directories-first --quoting-style=literal' else alias ls=' /bin/ls -p -G -D "%Y.%m.%d %H:%M"' fi ;; (OpenBSD) export PKG_PATH=http://ftp.openbsd.org/pub/OpenBSD/$( uname -r )/packages/$( uname -m )/ [ -e /usr/local/bin/colorls ] && alias ls='/usr/local/bin/colorls -G' ;; (Linux) if /usr/bin/env which exa 1> /dev/null 2> /dev/null then alias gls='ls -p --color=always --time-style=long-iso --group-directories-first --quoting-style=literal' alias ls='exa --time-style=long-iso --group-directories-first' else alias ls='ls -p --color=always --time-style=long-iso --group-directories-first --quoting-style=literal' fi ;; esac alias la='ls -A' alias ll='ls -l' alias exa='exa --time-style=long-iso --group-directories-first'
If for any reason WordPress would mess the above config up here is the plain text version – https://raw.githubusercontent.com/vermaden/scripts/master/zshrc – available from my GitHub scripts repository.
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.
# IMPORT DOAS/SUDO if [ -f ~/.zshrc.DOAS.SUDO ] then source ~/.zshrc.DOAS.SUDO else echo "NOPE: file ~/.zshrc.DOAS.SUDO absent." fi # BASICS export PATH=${PATH}:~/scripts:~/scripts/bin:~/.cargo/bin export EDITOR=vi export VISUAL=vi export BROWSER=firefox export MANWIDTH=tty export ENV=${HOME}/.shrc export IFCONFIG_FORMAT=inet:cidr export LC_COLLATE=C # BASICS DESKTOP export DISPLAY=:0 export MOZ_DISABLE_IMAGE_OPTIMIZE=1 export _JAVA_OPTIONS='-Dawt.useSystemAAFontSettings=on' export NO_AT_BRIDGE=1 # ENABLE ICONS IN exa(1) case ${TERM} in (rxvt) : ;; (xterm*) : ;; (*) alias exa='exa --icons' ;; esac # ALIASES alias Grep=grep alias grpe=grep alias grepMAC='grep -i -E "[0-9a-f]{2}\:[0-9a-f]{2}\:[0-9a-f]{2}\:[0-9a-f]{2}\:[0-9a-f]{2}\:[0-9a-f]{2}"' alias grepIP='grep -E "([0-9]+\.){3}[0-9]+"' alias cls='printf "\033[H\033[J"' alias e=exa alias bat='bat --color=always' alias x='xinit ~/.xinitrc -- -dpi 75 -nolisten tcp 1> /dev/null 2> /dev/null' alias ffmpeg='ffmpeg -hide_banner' alias mupdf='mupdf -r 120' alias tac='tail -r' alias lsof='lsof -w' alias less='less -r --chop-long-lines' alias more='less -r --chop-long-lines' alias pstree='pstree -g 2' alias lupe='lupe -noshape -mag 2 -nohud -geometry 300x200 -noreticle -noiff' alias parallel='parallel --no-notice --progress -j 3' alias pv='pv -t -r -a -b -W -B 1048576' alias caja='caja --browser --no-desktop' alias evince=atril alias we="curl -4 http://wttr.in/Lodz\?Q\?n 2> /dev/null | sed '\$d' | sed '\$d'" alias cclive='cclive -c' alias yu='youtube-dl -c -i -f best --skip-unavailable-fragments' alias aria2c='aria2c --file-allocation=none' alias dig=drill alias cssh='cssh -o "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"' alias ssh='ssh -o LogLevel=quiet -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' alias feh="feh --scale-down \ --auto-rotate \ --auto-zoom \ --fontpath ~/.fonts \ --font ubuntu/8 \ --menu-font ubuntu/8 \ --title-font ubuntu/8" alias wget='wget -c --no-check-certificate \ -U "Opera/12.16 (X11; FreeBSD 13.0 amd64; U; en) Presto/3 Version/12"' alias scp='scp -o ControlMaster=yes \ -o ControlPath=/tmp/%r@%h:%p \ -o UserKnownHostsFile=/dev/null \ -o StrictHostKeyChecking=no' # SHORT HISTORY ALIASES h() H() alias h='< ~/.zhistory grep -i' alias H='< ~/.zhistory grep' # SHORT GREP FUNCTIONS alias g='grep -i' alias G='grep' # SHORT QUERY FUNCTIONS q() q() { if [ ${#} -eq 1 ] then /bin/ls | grep --color -i ${1} 2> /dev/null else echo "usage: q string" fi } # SHORT QUERY FUNCTIONS Q() Q() { if [ ${#} -eq 1 ] then /bin/ls | grep --color ${1} 2> /dev/null else echo "usage: Q string" fi } # SHORT QUERY FUNCTIONS qq() qq() { if [ ${#} -eq 1 ] then find . \ | grep -i ${1} 2> /dev/null \ | cut -c 3-999 \ | grep --color -i ${1} 2> /dev/null else echo "usage: qq string" fi } # SHORT QUERY FUNCTIONS QQ() QQ() { if [ ${#} -eq 1 ] then find . \ | grep ${1} 2> /dev/null \ | cut -c 3-999 \ | grep --color ${1} 2> /dev/null else echo "usage: QQ string" fi } # FUNTIONS / INTELIGENT CD() dc() { if [ -f "${@}" ] then cd "${@%/*}" return 0 fi if [ -d "${@}" ] then cd "${@}" return 0 fi echo "${0}: no such file or directory: ${@}" return 1 } # FUNTIONS / PORTS / ports-check() ports-check() { CUT='Major OS version upgrade detected.' # ${CMD} nice -n 20 portsnap auto ${CMD} nice -n 20 gitup ports echo ${CMD} nice -n 20 portmaster -L --index-only \ | grep -v "${CUT}" \ | awk '/ [Nn]ew / { print substr($0,9,9999) }' echo VULNS=$( ${CMD} pkg audit -F 2>&1 | grep ' vulnerable' | sort -u | sed 's/\ is\ vulnerable://g' ) echo Vulnerabilities: if [ "${VULNS}" = "" ] then echo None. else echo "${VULNS}" fi echo pkg updating \ -d $( date -j -f "%s" "$( pkg query -a %t | grep -v "${CUT}" | sort | tail -1 )" "+%Y%m%d" ) } # FUNTIONS / PORTS / ports-rebuild() ports-rebuild() { # OPTIONS local PORTS='multimedia/ffmpeg' # local PORTS='multimedia/ffmpeg audio/lame sysutils/exfat-utils sysutils/fusefs-exfat' for PORT in ${PORTS} do ${CMD} pkg unlock -y ${PORT} 1> /dev/null 2> /dev/null ${CMD} idprio 10 env BATCH=yes DISABLE_VULNERABILITIES=yes make -C /usr/ports/${PORT} build deinstall install clean & MAKE=${!} ${CMD} rctl -a process:${MAKE}:pcpu:deny=40 ${CMD} wait ${MAKE} # ${CMD} pkg lock -y ${PORT} done } # FUNTIONS / PORTS / ports-build() ports-build() { case ${#} in (0) ${CMD} nice -n 20 portmaster -y --no-confirm -m 'BATCH=yes' -d -a ;; (*) ${CMD} nice -n 20 portmaster -y --no-confirm -m 'BATCH=yes' -d $@ ;; esac ${CMD} nice -n 20 find /var/db/pkg -type d -depth 1 -exec rm -rf {} ';' 2> /dev/null } # FUNTIONS / PKG / pkg-defunct() pkg-defunct() { pkg version -Rl\? | cut -wf1 } # FUNTIONS / PORTS / pkg-version() pkg-version() { pkg version -I -l '<' | awk '{print $1}' } # FUNTIONS / PORTS / pkg-size() pkg-size() { pkg info -as | sort -k 2 -h | tail -100 } # FUNTIONS / BMI bmi() { # 1=HEIGHT 2=WEIGHT if [ ${#} -ne 2 ] then echo "usage: $( basename ${0} ) HEIGHT WEIGHT" echo echo "table:" echo " UNDER WEIGHT LESS - 18.4" echo " NORMAL WEIGHT 18.5 - 24.9" echo " OVER WEIGHT 25.0 - 29.9" echo " OBESITY 30.0 - MORE" echo return 1 fi local BMI=$( echo "${2} / ( ${1} * ${1} ) * 10000" | bc -l ) printf "%.1f\n" "${BMI}" } # FUNTIONS / BFP bfp() { if [ ${#} -ne 4 ] then echo "usage: $( basename ${0} ) HEIGHT WEIGHT AGE SEX" echo echo "SEX: f - female" echo " m - male" return 1 fi case ${4} in (m) SEX=1 ;; (f) SEX=0 ;; esac local BMI=$( echo "${2} / ( ${1} * ${1} ) * 10000" | bc -l ) local BFP=$( echo "( 1.2 * ${BMI} ) + ( 0.23 * ${3} ) - ( 10.8 * ${SEX} ) - 5.4" | bc -l ) printf "%.1f%%\n" "${BFP}" } # FUNTIONS / BMR bmr() { if [ ${#} -ne 3 ] then echo "usage: $( basename ${0} ) WIEGHT HEIGHT AGE" echo return 1 fi local RESULT=$( echo "( 10 * ${1} ) + ( 6.25 * ${2} ) - ( 5 * ${3} ) + 5" | bc -l ) if echo ${RESULT} | grep -q '^\.' then echo -n 0 fi echo ${RESULT} | awk -F '.' '{print $1}' } # FUNTIONS / MATH math() { local SCALE=2 local INPUT=$( echo "${@}" | tr 'x' '*' | tr ',' '.' ) local RESULT=$( echo "scale=${SCALE}; ${INPUT}" | bc -l ) if echo ${RESULT} | grep -q '^\.' then echo -n 0 fi echo ${RESULT} } # FUNTIONS / MAH2WH conv_mah_2_wh() { if [ ${#} -ne 2 ] then echo "usage: $( basename ${0} ) mAh V" echo return 1 fi local MAH2WH=$( echo "${1} * ${2} / 1000" | bc -l ) printf "%.1f Wh\n" "${MAH2WH}" } # FUNTIONS / WH2MAH conv_wh_2_mah() { if [ ${#} -ne 2 ] then echo "usage: $( basename ${0} ) Wh V" echo return 1 fi local WH2MAH=$( echo "${1} / ${2} * 1000" | bc -l ) printf "%.1f mAh\n" "${WH2MAH}" } # FUNTIONS / CM2IN conv_cm_2_in() { if [ ${#} -ne 1 ] then echo "usage: $( basename ${0} ) INCH" echo return 1 fi local CM=$( echo "${1} / 2.54" | bc -l ) printf "%.1f cm EQUALS %.1f inch(es)\n" "${1}" "${CM}" } # FUNTIONS / IN2CM conv_in_2_cm() { if [ ${#} -ne 1 ] then echo "usage: $( basename ${0} ) INCH" echo return 1 fi local INCH=$( echo "${1} * 2.54" | bc -l ) printf "%.1f inch(es) EQUALS %.1f cm\n" "${1}" "${INCH}" } # FUNTIONS / REMOVE SSH known_hosts KEY ssh_known_hosts_key_remove() { if [[ -z "${1}" ]] then echo "usage: ${0} [host]" echo " Removes specified host from ~/.ssh/known_hosts file." else sed -i '' -e "/${1}/d" ${HOME}/.ssh/known_hosts fi } # FUNTIONS / CAL cal() { if which gcal 1> /dev/null 2> /dev/null then local TEST="${@}" if [ "${TEST}" = "-3" ] then gcal -s 1 . | sed 1,2d | sed 3d else gcal -s 1 ${@} fi else cal ${@} fi } # FUNTIONS / DAY day() { if [ ${#} -eq 0 ] then echo "usage: ${0##*/} DAY-OF-MONTH" return 1 fi cal $( date +%Y ) \ | env GREP_COLOR="07;32" grep --color=always -EC 6 " $1 |^$1 | $1\$" \ | env GREP_COLOR="07;33" grep --color=always -B2 -A6 -E 'Mo|Tu|We|Th|Fr|Sa|Su' \ | grep -v -- --; } # FUNTIONS / SSH-COPY-ID if ! which ssh-copy-id 1> /dev/null 2> /dev/null then ssh-copy-id() { echo 'INFO: ssh-copy-id(1) is not available' echo 'HINT: cat ~/.ssh/id_rsa.pub | ssh USER@HOST "cat >> ~/.ssh/authorized_keys"' } fi
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.
Here are the ~/.zshrc.DOAS.SUDO contents.
% cat ~/.zshrc.DOAS.SUDO SUDO_WHICH=0 SUDO=0 DOAS_WHICH=0 DOAS=1 ROOT=0 # CHECK doas(8) WITH which(1) if which doas 1> /dev/null 2> /dev/null then DOAS_WHICH=1 else DOAS_WHICH=0 fi # CHECK sudo(8) WITH which(1) if which sudo 1> /dev/null 2> /dev/null then SUDO_WHICH=1 else SUDO_WHICH=0 fi # CHECK USER WITH whoami(1) if [ "$( whoami )" = "root" ] then ROOT=1 fi # CHOOSE ONE FROM doas(8) AND sudo(8) if [ ${DOAS_WHICH} -eq 1 -o ${SUDO_WHICH} -eq 1 ] then if [ ${DOAS} -eq 0 -a ${SUDO} -eq 1 -a ${SUDO_WHICH} -eq 1 ] then CMD=sudo elif [ ${DOAS} -eq 1 -a ${SUDO} -eq 0 -a ${DOAS_WHICH} -eq 1 ] then CMD=doas elif [ ${DOAS} -eq 1 -a ${SUDO} -eq 1 -a ${DOAS_WHICH} -eq 1 ] then CMD=doas fi elif [ ${ROOT} -eq 1 ] then CMD='' else echo "NOPE: This script needs 'doas' or 'sudo' to work properly." exit 1 fi unset SUDO_WHICH unset DOAS_WHICH unset ROOT
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.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.
% tail -13 ~/.zshrc
# 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