“$@” の謎

シェルスクリプトの引数や,スクリプト内関数の引数へのアクセスについて.

$@ が引数へのアクセスを提供する.これは Positional Parameters と呼ばれ「配列のようなもの」と考えられる.

#!/bin/sh

printf "argc=%d\n" "$#"

i=1
while [ $# -gt 0 ]
do
    printf "argv[%d]=\"%s\"\n" "$i" "$1"
    shift
    i=$((i+1))
done

# bottom of file

たとえば,上のスクリプトに適当な引数を付けて実行すると,以下のような出力となる.

$ ./args0.sh "This is a pen." "How are you doing?"
argc=2
argv[1]="This is a pen."
argv[2]="How are you doing?"

shift を用いて引数(配列)の位置をずらしながら $1 にアクセスすることで全引数にアクセスしている.

#!/bin/sh

FUNC () {

    printf "argc=%d\n" "$#"
    
    i=1
    while [ $# -gt 0 ]
    do
        printf "argv[%d]=\"%s\"\n" "$i" "$1"
        shift
        i=$((i+1))
    done
    
}

FUNC $@

# bottom of file

これを関数に渡すときは注意が必要で,$@ をクオートしない(17行目)と以下のように,スペースでバラバラに区切られた引数として関数に渡ってしまう.

$ ./func0.sh "This is a pen." "How are you doing?"
argc=8
argv[1]="This"
argv[2]="is"
argv[3]="a"
argv[4]="pen."
argv[5]="How"
argv[6]="are"
argv[7]="you"
argv[8]="doing?"

これを防ぐために,関数に渡すときは "$@" とクオートするのがベストプラクティスだ.

#!/bin/sh

FUNC () {

    printf "argc=%d\n" "$#"
    
    i=1
    while [ $# -gt 0 ]
    do
        printf "argv[%d]=\"%s\"\n" "$i" "$1"
        shift
        i=$((i+1))
    done
    
}

FUNC "$@"

# bottom of file

とすると,以下のように一般に期待される結果となる.

$ ./func1.sh "This is a pen." "How are you doing?"
argc=2
argv[1]="This is a pen."
argv[2]="How are you doing?"

でも,何も背景を理解しないままプログラムの字面から期待してしまうのは,argc=1argv[1]=""This is a pen." "How are you doing?"" あたりなのだが…,この辺のことは,POSIX の仕様2.5.2 Special Parameters あたりを解読すれば理解できるのだろうが,正直ちょっと意味不明だ.

ついでに,もう一つの Positional Parameter である $* と対比しておくと,

#!/bin/sh

FUNC () {

    printf "argc=%d\n" "$#"
    
    i=1
    while [ $# -gt 0 ]
    do
        printf "argv[%d]=\"%s\"\n" "$i" "$1"
        shift
        i=$((i+1))
    done
    
}

FUNC "$*"

# bottom of file

は,以下の様に動く.

$ ./func2.sh "This is a pen." "How are you doing?"
argc=1
argv[1]="This is a pen. How are you doing?"