dt.iki.fi

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".

I want startup scripts and everything that has a #!/bin/sh shebang to use the lightest possible shell by default, but I still want my trusty bash in interactive terminal sessions, and for complex scripts.

Checkbashisms-up-

As the article suggests, I install checkbashisms and run it against all installed files, first in /usr/bin and then all other files (I could have done this in one command, but it's easier to divide it into several steps):

sh
$> find /usr/bin/ -type f -perm -o=r -print0 | \ xargs -0 gawk '/^#!.*( |[/])sh/{printf "%s\0", FILENAME} {nextfile}' 2>/dev/null | \ xargs -0 checkbashisms $> pacman -Qlq | grep -v '/usr/bin/' | \ xargs -d'\n' gawk '/^#!.*( |[/])sh/{printf "%s\0", FILENAME} {nextfile}' 2>/dev/null | \ xargs -0 checkbashisms

This finds all scripts that have a #!/bin/sh or similar shebang and checks them with checkbashisms. 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)
(test -a/-o)
(trap with signal numbers)
(type)
($_)

This one is 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 prove 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.

Then I continued to test my own scripts:

sh
$> find "$HOME" -type f -perm -o=r -print0 | \ xargs -0 gawk '/^#!.*( |[/])sh/{printf "%s\0", FILENAME} {nextfile}' 2>/dev/null | \ xargs -0 checkbashisms

This is where I had to open scripts in an editor and decide to either change the shebang to #!/bin/bash or fix the script.

Now the most important step:

# ln -sfT dash /usr/bin/sh

Reboot-up-

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-up-

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

Now I can rewrite my regularly executed conky scripts to use dash! Because conky has /bin/sh hardcoded.