Your shell is bad, but doesn't have to be

I get a lot of questions on why I use a shell as opposed to a GUI for things. A shell is often annoying to do some basic things even if it's much more powerful and generally a tradeoff. Deleting all files ending in .txt is hard in a gui, but in a shell is just rm *.txt. Meanwhile, going up a directory in a gui is easy. Just hid the back button on the mouse. In a shell it's cd .. which is a lot to type for something so simple. Another common issue that I had was different operating systems or even Linux distros having different commands to do basically the same thing. On Debian it's sudo apt install <package> and Mac it's brew install <package>. This gets quite annoying to have to remember what system you are on, and understand the differences in them.

Aliases, functions, and bliss

All of the above complaints can go away. Changing directories can be shortened.

# Changing dirs with just dots.
alias .='cd ../'
alias ..='cd ../../'
alias ...='cd ../../../'
alias ....='cd ../../../../'

This is just one example. Maybe you want to have commands that work on both Mac and Linux. What if it even matters if you are running Wayland or X11? Still can be easily solved.

if command -v pbcopy > /dev/null 2>&1; then
  alias xclip='pbcopy'
elif command -v wl-copy > /dev/null 2>&1; then
  alias xclip='tee >(wl-copy) | wl-copy -p'

The above makes xclip be the actual xclip if there are no better options, or sets it to the Mac or Wayland version if those exist instead. No need to remember what system you are on, or what software stack. It automatically adapts.

Package management.

I'll mostly let the code speak for itself as it's easy to understand, but this makes traveling between machines nearly seamless as there is nothing new to learn or remember.

if command -v xbps-install > /dev/null 2>&1; then
  export IS_VOID=1
elif command -v emerge > /dev/null 2>&1; then
  export IS_GENTO=1
elif command -v pacman > /dev/null 2>&1; then
  export IS_ARCH=1
elif command -v apt-get > /dev/null 2>&1; then
  export IS_DEBIAN=1
elif command -v apk > /dev/null 2>&1; then
  export IS_ALPINE=1
elif [[ "$OSTYPE" == "darwin"* ]]; then
  export IS_MAC=1
  export IS_BSD=1

if [ "${IS_ARCH}" = "1" ]; then
  ## Package Manager - pacman/yay
  if command -v yay > /dev/null 2>&1; then
    alias pi='yay -S'
    alias pr='yay -R'
    alias psearch='yay -Slq | fzf --multi --preview 'yay -Si {1}' | xargs -ro yay -S'
    alias pu='yay -Syu --devel --timeupdate'
    alias oneshot='yay -S --asdeps'
    alias orphans="yay -Qtdq | yay -Rns -"
    alias pi='sudo pacman -S'
    alias pr='sudo pacman -R'
    alias psearch='pacman -Slq | fzf --multi --preview 'sudo pacman -Si {1}' | xargs -ro sudo pacman -S'
    alias pu='sudo pacman -Syu'
    alias oneshot='sudo pacman -S --asdeps'
    alias orphans="pacman -Qtdq | sudo pacman -Rns -"
  alias mirrorupdate="sudo pacman-mirrors --geoip && sudo pacman -Syyu"
  alias etc-update="sudo pacdiff"
  alias asdep="sudo pacman -D --asdeps"
  alias explicit="sudo pacman -D --asexplicit"

elif [ "${IS_DEBIAN}" = "1" ]; then
  ## Package Manager - apt
  alias pi='sudo apt install'
  alias pr='sudo apt remove'
  alias psearch='apt search'
  alias pu='sudo apt update && sudo apt upgrade'
  alias orphans='apt autoremove'

elif [ "${IS_GENTOO}" = "1" ]; then
  ## Package Manager - portage/emerge
  alias gsync='sudo eix-sync'
  alias pi='sudo emerge -av --autounmask'
  alias oneshot='sudo emerge -av --oneshot'
  alias pu='sudo emerge --update --deep --with-bdeps=y --newuse --keep-going @world --ask'
  alias pub='sudo emerge --update --deep --with-bdeps=y --newuse --keep-going @world --ask --binpkg-changed-deps'
  alias pr='sudo emerge --depclean -av'
  alias psearch='eix -r'
  alias pclean='sudo qpkg -c'
  alias howlong='sudo watch --color genlop -uic'
  alias etcupdate='sudo -E etc-update --automode -3'

elif [ "${IS_ALPINE}" = "1" ]; then
  ## Package Manager - apk
  alias pi='sudo apk add'
  alias pr='sudo apk del'
  alias psearch='sudo apk search'
  alias pu='sudo apk update && sudo apk upgrade'

elif [ "${IS_VOID}" = "1" ]; then
  ## Package Manager - void/xpbs
  alias pi='sudo xbps-install -S'
  alias prr='sudo xbps-remove -R'
  alias pr='sudo xbps-remove'
  alias psearch='sudo xbps-query -Rs'
  alias pu='sudo xbps-install -Su'
  alias orphans='sudo xpbs-remove -o'

elif [ "${IS_MAC}" = "1" ]; then
  ## Package Manager - brew
  function pi {
      brew install "${@:1}"
      brew bundle dump --force --file=$HOMEBREW_BREWFILE
  function pic {
      brew install cask "${@:1}"
      brew bundle dump --force --file=$HOMEBREW_BREWFILE
  function pr {
      brew uninstall cask "${@:1}"
      brew bundle dump --force --file=$HOMEBREW_BREWFILE
  alias psearch='brew search'
  alias pu='brew update && brew upgrade'
  alias orphans='brew autoremove'

elif [ "${IS_BSD}" = 1 ]; then
  ## Package Manager - freebsd
  alias pi='sudo pkg install'
  alias pr='sudo pkg remove'
  alias psearch='sudo pkg search'
  alias pu='sudo pkg update && sudo pkg upgrade'


There is a combination of aliases and functions used in there. Aliases are simple commands, functions can have more advanced actions attached, but both act as a "normal" command as far as a user sees.

Adding sane defaults

A lot of the time you want to do the same thing. When you want to rm files, most of the time you probably want to remove a directory recursively, as opposed to deleting everything inside, and using rmdir. You can alias over commands and add default arguements.

# Default flags
alias cp='cp -R -i -v'
alias mv='mv -i -v'
alias mkdir='mkdir -p -v'
alias df='df -h'
alias du='du -h -s'
alias dd='dd status=progress bs=4M conv=fdatasync '
alias wgetpaste='wgetpaste -Xx'
alias sudo='sudo '  # Makes sudo work with aliases
alias ssh='TERM=xterm ssh'  # Fixes some issues with ssh on some terminals

More use of functions

I mentioned that functions can do a bit more than a basic alias. Here's a few more examples of that in action.

# Easy extract
extract () {
  if [ -f $1 ] ; then
      case $1 in
            *.tar.bz2)      tar xvjf $1   ;;
            *.tar.gz)       tar xvzf $1   ;;
        *.tar.xz)       tar xvJf $1   ;;
            *.bz2)          bunzip2 $1    ;;
            *.rar)          unrar x $1    ;;
            *.gz)           gunzip $1     ;;
            *.tar)          tar xvf $1    ;;
            *.tbz2)         tar xvjf $1   ;;
            *.tgz)          tar xvzf $1   ;;
        *.txz)          tar xvJf $1   ;;
            *.rar)          unrar $1      ;;
            *.zip)          unzip $1      ;;
            *.Z)            uncompress $1 ;;
            *.7z)           7z x $1       ;;
          *)           echo "don't know how to extract '$1'..." ;;
      echo "'$1' is not a valid file!"

# Makes directory then moves into it
function mkcdr {
    mkdir -p -v $1
    cd $1

# Creates an archive from given directory
mktar()  { tar cvf  "${1%%/}.tar"     "${1%%/}/"; }
mktgz()  { tar cvzf "${1%%/}.tar.gz"  "${1%%/}/"; }
mktbz()  { tar cvjf "${1%%/}.tar.bz2" "${1%%/}/"; }
mkzip()  { 7z a -r  "${1%%/}.zip"     "${1%%/}/"; }
mk7zip() { 7z a -r  "${1%%/}.7z"      "${1%%/}/"; }


I know this was a short post that was heavy in raw text from files, but it's meant to highlight some of my dotfiles that make my life much easier, and I think would be useful to many others. Even if you don't use the exact lines, you can see how things are used, get new ideas, and maybe make something even better for your workflow than you could just grab from using my files blindly. I didn't go deeply into things that bash can't do that ZSH can, but I may do a more in depth dive of my .zshrc to pull out some of those things at a later date. These are only snippits of my dotfiles. They can all be found, in full here for further reading and extraction. ZSH related files are .zshrc .zshenv .zsh_aliases as I have split my zsh for easier reading and editing.


Here are the aliases I use to open my zsh files for editing.

alias zshrc="$EDITOR ~/.zshrc"
alias zshenv="$EDITOR ~/.zshenv"
alias zshaliases="$EDITOR ~/.zsh_aliases"