FOSS Unleashed

Shell detection: bash, rc or xs?

A while ago, I needed (for admittedly a fairly silly reason) to have a file that could be sourced by both bash and xs, but then it would source appropriate files for the shell. This means the file had to be both legal bash, and legal xs. At the time I was aware of rc (and es), so now we’re here to do the same with rc and xs:

# bash
test -n ''^'' && { echo bash; exit 0; }
# rc/xs
test -z ''^'' && { fn testx { ~ $1 asdf && echo rc || echo xs}; testx asdf }

This has a few caveats. Firstly the bash test must be first. An external test program must exist in the path. And the command’s arguments cannot have “asdf” as the first argument when you’re calling from an instance of xs. bash requires the trailing semi-colons, and it does need to exit before it attempts to parse the rest of the file.

How does this work?

First off, we filter for bash. We use the ''^'' string in both tests. bash will resolve this to a string of ^, which is a non-empty string, test -n tests for a non-empty string. However, both xs and rc will resolve the string as an empty string, since ^ is the concatenation operator. The concatenation of two empty strings is an empty string. Then we follow through and (ab)use the differences between how arguments are passed in both shells. rc does not support named arguments, and the values of the $* and $1 variables are set to the arguments of the function. xs supports named arguments, and in fact requires them, at all times the $* and $1 variables are set to the arguments of the script.

You might also note the lack of if commands. In all cases, there is no if command that is syntaxically valid in more than one of these shells. Instead we must rely on logical shortcutting via the && and || operators, thankfully all three shells accept the same syntax.