9

Up until this month, my shell configs have been pretty simple (just a .bashrc or .bash_profile with some aliases mainly), but I have been refactoring it so I can get different behavior depending on whether I'm using zsh, and bash. They first source a generic shell config file that should work for whatever, then specialize for the specific shell being used (I symlink to this).

I was surprised today when ls stopped working. It turned out that during refactoring .bashrc, there was an alias

alias ls='ls --color=always'

that was breaking things for ls in bash on Terminal in OSX. Once I saw that BSD ls likes -G for color, but GNU (or whatever was on Ubuntu) likes --color, it was clear that quite a few options differ.

My question is, what is the best way to account for differences in options and such between BSD and GNU coreutils? Should I test for an env variable in if blocks to see what OS is being used and apply correct behavior? Or does it make more sense to make separate config files for each OS?

While answers to these questions may be subjective, it seems like a rundown of the scope of differences between BSD and GNU coreutils and strategies to work around these to make a generic config usable on most *nix would be fairly objective.

1
  • Changing shells won't fix anything, and ls -c is different from ls --color. Edited your question to fix.
    – Mikel
    Commented Mar 18, 2014 at 12:37

3 Answers 3

9

The only reliable way to write scripts that support different operating systems is to only use features that are defined by POSIX.

For things like your personal shell configurations, you can use hacks that fit your specific use case. Something like the following is ugly, but will accomplish the goal.

if ls --version 2>/dev/null | grep -q 'coreutils'; then
    alias ls='ls --color=always'
else
    alias ls='ls -G'
fi
5
  • I've been playing around with different methods, and I think your "ugly" solution is pretty good and not even that ugly. As it turns out, I hadn't noticed the mismatch in options for ls earlier because I was using GNU coreutils from Ports. This is an example why doing an 'if' on $OSTYPE can fail to deliver desired results.
    – labyrinth
    Commented Dec 7, 2013 at 6:27
  • Is there a way to suppress the error that comes from "if ls --version" when GNU coreutils aren't present (but still be able to test for coreutils)?
    – labyrinth
    Commented Dec 7, 2013 at 16:48
  • Oh, nevermind about suppressing the error. I just redirect stderr to /dev/null before piping to grep and it works as I'd like.
    – labyrinth
    Commented Dec 7, 2013 at 16:54
  • 3
    Rather than look for coreutils explicitly, why not just test whether the color flag works, e.g. if ls --color=auto -d / >/dev/null 2>&1; then ....
    – Mikel
    Commented Mar 18, 2014 at 12:32
  • @mikel if you are only concerned about color (like in the example), that is fine. If you care about other features as well, checking for coreutils is useful.
    – jordanm
    Commented Mar 18, 2014 at 16:15
3

Littering the code with if statements to perform a switch on the type of coreutils does work, however the clean programming solution for handling different types is to use polymorphism. Since this is Bash, we don't have polymorphism per se, but I've been messing around with a way to fake it. The only requirement is that your .bashrc file etc. be organized into functions.

First I create a test for the coreutils platform type:

get_coreutils_platform() {
    local ls_version="$(ls --version 2>/dev/null)"
    if [[ "$ls_version" == *"GNU coreutils"* ]]; then
        echo gnu
    else
        echo bsd
    fi
}

Then we can dispatch based on the type:

platform=$(get_coreutils_platform)
define_standard_aliases_$platform
configure_shell_vars_$platform

Here's the BSD implementation:

define_standard_aliases_bsd() {
    define_standard_aliases
}

configure_shell_vars_bsd() {
    configure_shell_vars
    export CLICOLOR=1
}

(Note we use the CLICOLOR variable to turn on colors instead of using an alias, which seems a littler cleaner)

And the GNU impelementation:

define_standard_aliases_gnu() {
    define_standard_aliases
    alias ls='ls --color=auto'
}

configure_shell_vars_gnu() {
    configure_shell_vars
}

For completeness' sake, here's a sample implementation of the "abstract base":

define_standard_aliases() {
    alias ll='ls -l'
    alias l.='ls -d .*'
}

configure_shell_vars() {
    export EDITOR=vim
}
4
  • This is much cleaner, unless you're on the 0.1% of systems with e.g. GNU ls but not GNU cat installed (maybe it's really old and they have fileutils but not textutils). I'd be tempted to use ls in your dispatcher rather than cat, especially since none of your aliases involve cat.
    – Mikel
    Commented Mar 18, 2014 at 12:49
  • Also, you shouldn't need --color=auto in your second and third aliases, since the first alias adds that option to ls.
    – Mikel
    Commented Mar 18, 2014 at 12:50
  • 1
    @Mikel whoa, I totally didn't realize that alias was recursive. That lets me make the example much simpler. Commented Mar 18, 2014 at 13:00
  • Much better. :-)
    – Mikel
    Commented Mar 18, 2014 at 13:01
0

Not a direct answer to your question, but I have wrapper scripts to handle things like this rather than extra complication in the .bashrc For example here is my l script that handles your case here in a cross platform way:

http://www.pixelbeat.org/scripts/l

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .