Use dash as /bin/sh

This article only slightly expands on what the archwiki says about it.

Linking /bin/sh to dash instead of bash is not the same as "changing your default shell".

Why

Using #!/bin/sh at the top of a script currently defaults to bash which is not very fast. I want startup scripts and everything that has a #!/bin/sh shebang to use the lightest possible shell, but I still want my trusty bash in interactive terminal sessions, and for complex scripts.

Some programs1 have /bin/sh hardcoded for executing external commands.

Checkbashisms

As the article suggests, I install checkbashisms, but I ran it against all files, not just those in PATH or those installed:

sh
find / -xdev -type f -perm -o=r -print0 | \ xargs -0 gawk '/^#!.*( |[/])sh/{printf "%s\n", FILENAME} {nextfile}' 2>/dev/null | \ while read -r line; do checkbashisms --extra "$line" [ $? = 1 ] && echo "################" done | \ tee checkbashisms.txt

Better to be root here so that find finds everything. This oneliner does not change anything.

Let's break it down:

  • find limits its search to actual files on this partition (-xdev) that are at least readable by the owner.
  • gawk searches for any "shebang" that contains the word sh (so not e.g. ash or bash or dash) - this would also include something like #! /usr/bin/env sh - and returns the filenames only.
  • checkbashisms then checks these files with a little --extra output, and I added a visual divider.
  • Results are not only printed to stdout but also saved as checkbashisms.txt with tee.

You will want to repeat this for other partitions.

Open the file checkbashisms.txt in your editor and go through the results line by line.

It finds a lot of false positives as far as dash is concerned.

From the man page:

Note that the definition of a bashism in this context roughly equates to "a shell feature that is not required to be supported by POSIX"; this means that some issues flagged may be permitted under optional sections of POSIX, such as XSI or User Portability.

These are definitely false positives for dash:

(local foo)
(local foo=bar)
(local x y)
(trap with signal numbers)
(type)
($_)
(jobs)
(read with option other than -r) - dash recognizes '-p'

This one could be problematic though:

(echo -e)

dash will echo a literal -e in this case. I had a closer look at a few scripts that use this but decided it's nothing to worry about (famous last words).

Then, some scripts deliberately test for bashisms in their scripts, creating more false positives (I particularly noticed various libtool scripts). Others use $RANDOM, which is undefined in dash, but provide fallbacks.

The only one that really seems to be problematic is xdg-settings, which uses [[ expr1 == expr2 ]] instead of [ expr1 = expr2 ] a few times (but strangely only in KDE-specific functions).

Additionally, I've seen (alternative test command ([[ foo ]] should be [ foo ])) or ([^] should be [!]) when it's inside a grep or sed pattern, hence false positive.

Ultimately there isn't much you can do for system files (and my system did not break from those mentioned here). But files you are the owner of (under your $HOME) you might want to open in an editor and decide to either change the shebang to #!/bin/bash or fix the script.

For such cases you might want to re-check single scripts

Now the most important step:

#> ln -sfT dash /usr/bin/sh

Reboot

I had to fix only one thing - my ~/.xinitrc which already has the #!/bin/sh shebang needs the HOSTNAME environment variable which bash sets automatically. So, in my ~/.bash_profile, which still gets executed (my login shell is still bash), I just need to export HOSTNAME.

Totally unscientific performance comparison - before the change:

$> systemd-analyze
Startup finished in 2.468s (kernel) + 8.171s (userspace) = 10.639s 
graphical.target reached after 2.545s in userspace

After the change:

$> systemd-analyze
Startup finished in 2.506s (kernel) + 2.563s (userspace) = 5.070s 
graphical.target reached after 2.558s in userspace

Of course I'd have to test more thoroughly, but this looks good enough.

Make it permanent

I cannot prevent bash updates from over writing the symlink, but I can add a pacman hook that immediately reverts it after every update. I put this in /etc/pacman.d/hooks/dash-as-sh.hook:

cfg
[Trigger] Type = Package Operation = Install Operation = Upgrade Target = bash [Action] Description = Re-pointing /bin/sh symlink to dash... When = PostTransaction Exec = /usr/bin/ln -sfT dash /usr/bin/sh Depends = dash

  1. For example conky