The Idiosyncrasies of Bash’s quotes

I have used Bash a my default shell for several years and written a fair amount of script with it. There is a good list of pitfalls that has helped me steer clear of the great number of mistakes that can break your scripts.

After I switched my shell to Fish, I started to get the impression that I spend too much time thinking about those Bash pitfalls instead of writing scripts. I wanted to write a simple script that I would call ca that would call git commit -a -m '...' for me where the ... would be all the arguments to ca. So I would enter this:

ca One two three

And get this:

git commit -a -m 'One two three'

I tried to do this in Fish and was looking for something like $* or $@ and could not find something. What I found was $argv which is an array with all the arguments. So my script was just:

git commit -a -m "$argv"

Since that took me minutes to figure out something that was so easy and a little convoluted in Bash, I was reminded of Linus Torwalds who said “CVS hasn’t rotten my brain” about the design principles of git in a talk (9:02). So I do think that Bash has convoluted my mind and I am trying to get rid of that.

If you compare Bash to Fish or even Python, it is painful.

Command line arguments

Do you know the difference between $*, $@, "$* and "$@"? All them contain all command line arguments. But how do they with spaces in the arguments exactly? And how many words will each be?

Take a look at this program and try to say what the output would be.

#!/bin/bash
# Copyright © 2014 Martin Ueding <dev@martin-ueding.de>

arg-echo() {
    local index=1
    for arg
    do
        echo $index "$arg"
        (( index++ ))
    done
    echo
}

arg-test() {
    echo '$*'
    arg-echo $*

    echo '$@'
    arg-echo $@

    echo '"$*"'
    arg-echo "$*"

    echo '"$@"'
    arg-echo "$@"
}

arg-test one two "string with space"

The output is the following with Bash 4.3.11(1):

$*
1 one
2 two
3 string
4 with
5 space

$@
1 one
2 two
3 string
4 with
5 space

"$*"
1 one two string with space

"$@"
1 one
2 two
3 string with space

Would you have known that? And a friend put it concisely, I could not say it better:

“The sole fact that I am supposed to know this is a fault in the design.”

In Bash, the right thing for my git script would be git commit -am "$*". In Fish, there is only $argv and "$argv". The first gives the arguments and does not split spaces again, the latter makes it one big string. They relate to "$@" and "$*" in Bash.