Whilst reorganising my dotfiles I avoided superfluous dotfile managers and the limitations of the "version control $HOME" method by instead including first-line comments of the form ln in files which needed to be symlinked. Examples: ~: head -n 1 ~/dotfiles/helix/config.toml # ln ~/.config/helix/config.toml ~: head -n 1 ~/dotfiles/polybar/config.ini ; ln ~/.config/polybar/config.ini ~: head -n 1 ~/dotfiles/x/keyboard.conf # ln /etc/X11/xorg.conf.d/00-keyboard.conf The files can be scanned and symlinks can be created with some awk magic: find ~/dotfiles -type f \ | xargs -I {} awk 'NR == 1 && $2 == "ln" { system("ln -sf " FILENAME $3) }' {} If we change our awk to just print, add a substitution for ~, ignore the .git directory when searching for files and package everything up in a function we end up with: list_symlinks () { find $(dirname $0) \ -type f ! -path '*.git/*' \ | xargs -I {} awk 'NR == 1 && $2 == "ln" { print FILENAME " " $3 }' {} \ | sed "s|^./|$(pwd)/|;s|~|$HOME|" } list_symlinks can then be piped into awk to create utility commands for managing symlinks: # create symlinks list_symlinks | awk '{system("ln -sf " $1 " " $2)}' # remove all symlinks list_symlinks | awk '{system("rm " $2)}' # list symlinks list_symlinks | awk '{ print $2 " -> " $1 }' | column -t Using readlink we can enhance our symlink list to show broken symlinks: echo -e "$( \ [[ $(readlink $2) = $1 ]] \ && echo "\033[32m✓" \ || echo "\033[31m✗" \ ) $2 -> $1 \033[0m" Example output: ✓ ~/.i3/config -> ~/dotfiles/i3/config ✓ ~/.zshrc -> ~/dotfiles/zsh/.zshrc ✗ ~/.xinitrc -> ~/dotfiles/x/.xinitrc ✗ ~/.gitconfig -> ~/dotfiles/git/.gitconfig We can also use the power of awk to remove old links when we update our files (this sits nicely in a git post-commit hook). git diff HEAD~ | awk '/\-\S*\s+ln/ { if(prevLine ~ /@@ -1/) { system("rm " $3) } } { prevLine=$0 }' See [1]my dotfiles for a full script which includes escalating with sudo when required. What I like about this method is that: * it still allows a self-contained dotfile directory (mine is ~/dotfiles). * a separate record of symlinks isn't required because symlinks are specified in the linked files themselves. * checking links is trivial. * the only dependencies are commands common to most *nix distributions. Try it and see what you think. References 1. https://github.com/dansalias/dotfiles