Skip to main content
Wondering Willow
← All posts

Adventures in Dotfiles - Some Basic Principles

Mark Nenadov

· 4 min read

Introduction

Having previously shared some thoughts about the culture surrounding dotfiles repos in The Joys Of Dotfiles, I figure it is about time I write something about my own dotfiles.

I’ve had a GitHub-hosted Mac dotfiles repo since 2023. While it’s unlikely to wow anyone, I take a lot of joy in steadily refining it over time.

I wanted to share some basic principles (both actualized and aspirational).

Me First

I would be delighted if others find utility in my dotfiles. That, of course, is the reason I’ve published them as opposed to keeping them private. However, their structure, outlook, and approach fundamentally cater to my preferences and needs.

Avoid Assumptions or Enforce Them

I want to prevent the first principle (“me first”) from smuggling in too many environmental assumptions even to the detriment of my purposes. I believe eliminating (or reducing) assumptions is a clear win for anyone who might use these scripts.

Generally, I try to add tripwires that short-circuit things if an important assumption is not true (ie., the require_macos function).

Functions Over Aliases

While aliases have their value (and I use them frequently), as time goes on I find myself replacing complex aliases with zsh functions.

Functions allow for parameter validation, usage messages, and sanity checks: things aliases can’t provide.

Here are some examples of former aliases that have become functions for various reasons

function flushdns() {
    require_macos || return 1
    sudo dscacheutil -flushcache; 
    sudo killall -HUP mDNSResponder   
}

function guessos() {
    if [[ $# -eq 0 ]]; then
        echo "Error: No ip address specified. Usage: guessos <ip_address>"
        return 1
    fi

    sudo nmap -O $1
}

Clear Conflicts Proactively

I think there’s something to be said for generally being very explicit about “clearing the field” for what you are about to define.

Here’s a concrete example: Explicitly unalias a function name before defining it.

Overriding an alias with a function is sort of a weird thing with weird dynamics. I prefer to ensure I’m not walking into these sort of weird situations and I think it pays off in the long run.

For this purpose, I made a function, which unaliases silently (since the alias probably won’t exist):

function silent_unalias() {
  if [[ $# -eq 0 || -z "$1" ]]; then
    echo "Usage: silent_unalias <alias_name>" >&2
    return 1
  fi
  
  unalias "$1" 2>/dev/null
}

This may seem pedantic or over the top to some, but I think it’s a justified precaution.

Break Things Down

This is just basic modularity. I am a fan of having a small .zshrc that fits on one screen. Then you can mass include a zsh folder like so:

for module in ~/.zsh/*.zsh; do
	source "$module"
done

This allows me to split off aliases into their own file and also institute some broad over-arching categories for the functions I’m introducing (for instance: networking, file system, etc.)

Invest In The Installer

Though mine isn’t quite where I want it yet, I’ve made some strides in improving the installer. The time I’ve taken to tweak and improve my installer has paid a good return in terms of making my dotfiles a delight to work with.

I personally like to split the phases into functions, for example:

  • do_brew (silly name, but I just had to…)
  • link_dotfiles
  • install_language_runtimes
  • etc.

A good installer script should be safe, repeatable, and fast, which makes working with dotfiles over time genuinely enjoyable.

Conclusion

What do you think? I’d love to hear how others are managing their own setup — feel free to share ideas or links!

I’ll continue iterating on these files over time, but hopefully some of these practices give you ideas for your own setup. I’ll likely post some further thoughts at some point. Happy hacking.