Sharing .dotfiles cross-platform with a shell script

Attila Szeremi⚡ - May 29 '18 - - Dev Community

Throughout my career I have been in many Linux environments. I had the one at my first workplace where I would have my own .vimfiles settings set. Then at home when I installed Ubuntu for the first time, I would have a .bashrc file for storing some aliases. Then on my Windows on my Linux-like cygwin environment I had to copy that same .bashrc over, and maybe I created a .screenrc to configure GNU screen. Then on my personal DigitalOcean server I had another Linux environment, and I had to copy my configuration again. Then I installed Linux on a new computer and needed to find and copy my aliases again... and so on and so on.

I have probably had to manually copy parts of local Linux configuration over and over again twenty times or so before I realized that this just has to be automated in some way so that I wouldn't have to suffer so much getting all my configuration ready any time I have to deal with a new Linux environment. In this article, I'm going to show you what I did to solve this problem of mine.

Why do I opt for shell scripts in the title? That's so to minimize the amount of dependencies needed to be installed in order to get my .dotfiles up and running on any server.

Versioning dotfiles with Git

First of all, what best way to save the state of several files (.bashrc, .gitignore_global, bin/) for keeping them in sync than by using version control?

There is a thing one might think as issues when considering to version files in your home directory...

The dotfiles you would like to version is a very small subset of files/directories in your home directory

That's true. If you started versioning your home directory, all the other unrelated files/directories would show up as unversioned, muddying up your git status output all the time.

To solve this, I just created a .gitignore file in my home directory with this in it: *. Basically to ignore everything.

It's not a problem at all. Whenever I want to add new file, I can just do git add -f <file> to bypass the ignores.

Cloning .dotfiles in new environments

Now that we have our dotfiles versioned in Git, we'll need to be able to clone it into new environments to make use of them.

On any new Linux environment ideally we would like to clone our dotfiles into our home directory. Unfortunately in every Linux environment your home directory is not empty, and Git doesn't give you a way to clone into an existing directory.

So we'll need to clone into a new directory. We could call it dotfiles, and it could be within our home directory.

Now after that we would need to copy the files over from within the dotfiles directory to the home directory. There are some issues with that again though that would be good to address:

  1. It's usually non-trivial for non-Linux-gurus recursively copy files from within a directory including files starting with a dot (most dotfiles we would like to version)
  2. Our new Linux environment might come with some .bashrc file already there that we might not want to just blindly override it with ours. Maybe this was actually an older Linux environment of ours which has an old .bashrc of ours with some interesting stuff in it.
  3. Besides, worrying about all this also just wastes time. It would be good to get all our dotfiles ready ASAP on any Linux environment such that it would never feel like a chore.

Why not automate all this then?

Using an installer as a bash script

I'm not a big fan of bash scripts, but their portability is pretty good, so I opted for making one of those.

Where would the bash script go? Well it would be versioned within our dotfiles of course. I myself put it in bin/install_dotfiles.sh.

The answer to the problem of how to include files starting with a dot when copying files from * is by setting:

shopt -s dotglob
Enter fullscreen mode Exit fullscreen mode

You now know what you need... but you might forget it. So just leave this in your bash script so you wouldn't have to remember!

The other thing is what to do to avoid overriding your existing files.

If you didn't know, there's an argument you can pass to cp which keeps numbered backups for any files that would be overridden by a copy. It's --backup=numbered.

So the initial install script would copy all the files over with backups including the .git folder and .gitignore file to make sure your home folder is versioned but ignores all files.

The bash script would basically contain:

#!/bin/bash
shopt -s dotglob
cd ~/dotfiles
cp -rv --backup=numbered * ~

Enter fullscreen mode Exit fullscreen mode

And that's it, we have a portable way to sync dotfiles across Linux environments with a simple initial installer bash script you would have to run only once per new environment.

Addition for Macs

Unfortunately one more dependency is needed for this to work on a Mac.

To make cp on macOS work the same way as in Linux (for --backup=numbered), you'll need to make sure to install coreutils (e.g. with brew install coreutils), then make sure the GNU cp command is used by using this code after setopt:

# Wrapper for cp to work in macOS.
cp() {
    if [ -x "$(command -v gcp)" ]; then
        command=gcp
    else
        command=cp
    fi
    command $command "$@"
}
Enter fullscreen mode Exit fullscreen mode

Last extension: dist files

You might have some local configuration files you wouldn't want to version, but would like to have a default "dist" version to be installed initially on new Linux environments. For that, you could have a folder called, say, .dotfiles.dist with all such files. Then at the end of your script file after copying the files, you could then do:

cp -rv --backup=numbered .dotfiles.dist/* ~

Enter fullscreen mode Exit fullscreen mode

In case you're interested, you can see my dotfiles installer script here.

. .
Terabox Video Player