Installing things with style

Brett Weir, March 27, 2023

If you don't know that it exists, the coreutils install command is incredibly easy to overlook. I mean, really, what could the install command possibly do? Copy files? There's already cp for that.

Well, it turns out, there's a lot more that a command that copies files can do, and by learning this one weird command, you can save a lot of typing and express, in one command, ideas that would normally take several.

The install command

Your first, and perhaps only, encounter with the install tool (unless you're a package maintainer) is likely when following installation instructions for single binary software distributions.

Let's say we want to install kubectl. We've chosen the single binary installation path to get it onto our machine:

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
$ curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   138  100   138    0     0    916      0 --:--:-- --:--:-- --:--:--   920
100 45.8M  100 45.8M    0     0  17.6M      0  0:00:02  0:00:02 --:--:-- 20.8M

So how do I get the binary installed? The first guess would be to use the cp command:

sudo cp -f kubectl /usr/local/bin/

But there's also the install command:

sudo install kubectl /usr/local/bin/

At this point, using install feels identical to cp, but bear with me, because it gets better.

Set owner / group

Often, when adding things to a filesystem, you want them to be owned by a specific user. Usually this is root, but it could be another login user, a system user, or something weird like nobody.

A first attempt to set the owner would probably be:

sudo cp -f kubectl /usr/local/bin/
sudo chown root:root /usr/local/bin/kubectl

But did you know you can use install, too? install can take the place of chown with the -o / --owner and -g / --group flags:

sudo install -o root -g root kubectl /usr/local/bin/

By default, the above will assign the user or group of the parent process. That means, in many cases, no extra flags are even needed to get the behavior you want. That means our install command can be simplied to the following:

sudo install kubectl /usr/local/bin/

One line instead of two? Not bad, not bad. But install does more than that.

Set file permissions

Another common task when installing things is setting file permissions. You want your binaries to be executable, right? Maybe not by everyone, but by some people? Or maybe you are copying a text file and want for it to decidedly not be executable, hmm?

To achieve this, let's add chmod to our previous non-install command:

sudo cp -f kubectl /usr/local/bin/
sudo chown root:root /usr/local/bin/kubectl
sudo chmod 0755 /usr/local/bin/kubectl

Well, guess what? install has an app for that. It can take the place of chmod with the -m / --mode flag:

sudo install -o root -g root -m 0755 kubectl /usr/local/bin/

By default, install sets the file permissions to 0755, which means that it's readable/executable by everyone, but writable only by the owner. For executable commands, this is almost always what you want, so for the most common case, no arguments are needed, and the command can be reduced to:

sudo install kubectl /usr/local/bin/

Now it's one line instead of three. But, again, we're not done yet.

Create parent directories

Up until now, we've been installing into /usr/local/bin/, which pretty much always exists. But maybe we want to do something different, like build a root filesystem or Debian package, and we need to build up the directory structure as part of our installation.

Say we want to install kubectl to /opt/custom/path/to/kubectl instead of our usual location. If you were to use mkdir, you'd need to first create /opt/custom/ then /opt/custom/path/, and then /opt/custom/path/to/, in that order:

sudo mkdir /opt/custom/
sudo mkdir /opt/custom/path/
sudo mkdir /opt/custom/path/to/

To avoid doing all that, you can add the -p / --parents flag to auto-create parent directories:

sudo mkdir -p /opt/custom/path/to/

So now your complete non-install command looks something like this:

sudo mkdir -p /opt/custom/path/to/
sudo cp -f kubectl /opt/custom/path/to/
sudo chown root:root /opt/custom/path/to/kubectl
sudo chmod 0755 /opt/custom/path/to/kubectl

install, on the other hand, offers the -D option, which behaves just like the mkdir -p command above, ensuring kubectl has a place to go. With install, our command becomes the following:

sudo install -D kubectl /opt/custom/path/to/kubectl

Four lines reduced to just one. But that's still not all that install can do.

Backup existing files

Sometimes, especially on a personal workstation, you might want to keep the old version of a file around for a little while. Maybe you're upgrading your kubectl binary, but aren't quite sure if the new one will work with your existing cluster.

You might create a little backup of the file first. Let's add the following to our previous non-install command:

sudo mkdir -p /opt/custom/path/to/
sudo cp -f /opt/custom/path/to/kubectl /opt/custom/path/to/kubectl~
sudo cp -f kubectl /opt/custom/path/to/
sudo chown root:root /opt/custom/path/to/kubectl
sudo chmod 0755 /opt/custom/path/to/kubectl

With the install command, you can use the -b flag, which is much less verbose:

sudo install -b -D kubectl /opt/custom/path/to/kubectl

Reduce executable size

install supports stripping debug symbols from executables with the -s / --strip flag. This is often useful when building from source or preparing an executable for distribution.

Let's assume for the moment that kubectl still has debug symbols (even though it doesn't). Our non-install command adds the strip command:

sudo mkdir -p /opt/custom/path/to/
sudo cp -f /opt/custom/path/to/kubectl /opt/custom/path/to/kubectl~
sudo cp -f kubectl /opt/custom/path/to/
sudo chown root:root /opt/custom/path/to/kubectl
sudo chmod 0755 /opt/custom/path/to/kubectl
sudo strip /opt/custom/path/to/kubectl

This is getting really unwieldy, but with install, we just add -s:

sudo install -s -b -D kubectl /opt/custom/path/to/kubectl

Perform idempotent installations

Idempotence is a fancy way of saying that no matter how many times an action is performed, the result will be the same as if the action had been performed only once.

install's -c / --compare flag checks file content, ownership, and permissions. If none of these things have changed, install will not modify the target file at all.

This is great! This means you can run your install script as many times as you like and still get the same result.

I don't even want to try the non-install version, but since you've read this far, I'd feel bad if I didn't try. Here goes nothing:

# install.sh
if [ -e /opt/custom/path/to/kubectl ] ; then
    cmp kubectl /opt/custom/path/to/kubectl
    if [ $? -ne 0 ] ; then
        sudo mkdir -p /opt/custom/path/to/
        sudo cp -f /opt/custom/path/to/kubectl /opt/custom/path/to/kubectl~
        sudo cp -f kubectl /opt/custom/path/to/
        sudo chown root:root /opt/custom/path/to/kubectl
        sudo chmod 0755 /opt/custom/path/to/kubectl
        sudo strip /opt/custom/path/to/kubectl
    fi
fi

Wow, that's awful! It definitely works, but this is an awful bit of logic to lug around.

With install, we can turn on idempotent behavior with -c, and our command becomes:

sudo install -c -s -b -D kubectl /opt/custom/path/to/kubectl

When all is said and done, this one-liner replaces the entire script I wrote above, and does exactly the same thing, probably faster, and way less prone to typing errors.

Conclusion

The install command is a fine example of a convenience function. It provides a terse, easy-to-understand one-liner that is also self-documenting.

Using install instead of the equivalent individual commands makes the intent of your installation script very clear, and achieve more precise results with less effort.

And the best part? install is always there. Even in a stripped down busybox userland, you can expect to find it, because it's part of coreutils.

So have fun, use it wherever you like, and be one of the cool people who knows about the ultra-convenient, specialized relative of cp known as install.