DEV Community

loading...

Sharing .dotfiles cross-platform with a shell script

Attila Szeremi⚡
Hi, I like to do PHP (Laravel, ZF) and JS (React, Vue)
・4 min read

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.

Discussion (5)

Collapse
dmfay profile image
Dian Fay

Don't copy, symlink! That way you don't have to reinstall every time you tweak something. I use dotbot to keep my dotfiles up to date.

Collapse
amcsi profile image
Attila Szeremi⚡ Author • Edited

Apologies for the confusion. I opt for versioning your home folder directly with Git. As such, no copying necessary.

The bash script in the article is only for the initial install per environment that would copy all the files including the .git folder over to the home directory.

I have amended the article to be more clear about this.

Collapse
spoike profile image
Mikael Brassman

Was going to reply the same. I wrote my own bash script to symlink in files to the home directory. You tweak the files and can commit them without having to copy everything over again.

Should look into dotbot, I don’t think it existed when I first started to version control my dot files.

Collapse
ameliagapin profile image
Amelia Gapin

Yup, dotbot is what I use as well. I have shell script that installs a ton of packages I typically use and also kicks off a run of dotbot. It works pretty well on both Mac and Linux and doesn't result in having to do a bunch of work around having my home directory being a repo.

Collapse
alloro profile image
Sascha Diefenthäler

I'd say use stow