rcmdnk's blog

デザイン時計【アットマーク】 インテリア リビング 簡単 設置 取付

シェルスクリプトを書いてる時に$@を使って ハマったのでそれについて。

Sponsored Links

$@$*

$@はシェルスクリプト 1 ではスクリプトや関数の中で全ての引数を表します。

$ f(){ echo $@;}
$ f a b c
a b c

な感じ。

ここで、次の様なスクリプトを考えてみます。

atmark.sh
1
2
3
4
5
6
#!/usr/bin/env bash
if [ "$@" != "" ];then
  echo "$@"
else
  echo empty
fi

これを実行してみると

$ ./atmark.sh a
a
$ ./atmark.sh
./atmark.sh: line 2: [: unary operator expected
empty!
$ ./atmark.sh a b
./atmark.sh: line 2: [: too many arguments
empty!

と、引数が1つの場合はよさ気ですが、 引数がない場合と2つの場合はエラーが出ています。

これらは割りとよく見るエラーですが、 [ A = B ]としたい時に左辺か右辺が空白だったり 複数項になってしまったりする時に出ます。

変数を扱っていて、 空の時やスペースを含む文字列が入る可能性がある場合に "で囲わないと起こります。

ただ、上ではきちんと"で囲っています。 ここでの勘違いは $@を、単にスペース(正確にはIFS文字)で区切って並べて 表すだけの記号だと思っていたこと。

ここで、$@と同じ様なものとして$*があることを思い出したら 意味がわかりました。

シェルスクリプトの配列の操作でも@*が出てきて、 ${a[@]}とか${a[*]}とかすると同じ様に 配列全てを表しますが、 "で囲うと、@の場合は

"${a[0]}" "${a[1]}" "${a[2]}"...

となりますが、*の場合は

"${a[0]} ${a[1]} ${a[2]}..."

となります。

これと同じことが言えるようで、"$@"

"$1" "$2" "$3"...

と展開されて、"$*

"$1 $2 $3..."

と展開されます 2

さらに、配列の場合でも$@の場合でもそうなんですが、 中身が1つもない場合には"$@"は空の文字列ではなく、 その場に何もないものとして扱われます。

なので、引数がない時、

if [ "$@" = "" ];then

if [ = "" ];then

と同等で

if [ "" = "" ];then

とは異なります。

if [ "$*" = "" ];then

の場合は

if [ "" = "" ];then

になります。

-f-dの罠

もう一つ、上の事と同時にはまったこと。

与えられた文字列に対応するファイルやディレクトリをチェックする時に

$ if [ -f $file ];then echo $file is a file;fi

みたいなことをしますが3、 勿論空文字を与えると

$ if [ -f "" ];then echo $dir is a file;fi
$

と何も表示されません。 変数fileを使って色々試してみると

$ file=""
$ if [ -f "$file" ];then echo $file is a file;fi
$ # no output
$ if [ -f $file ];then echo $file is a file;fi
is a file

と、"で囲った場合には上の""と同様にファイルと判定されませんが、 何も囲わなかった場合は何故か真判定が出ています。

試しに、何も書かないと

$ if [ -f ];then echo $file is a file;fi
is a file

とコレも真判定。

これは、-fだけがテストされるので、この場合、 判別式としてではなく、単なる-fと言う文字列が判別に使われている様です 4

なので、何が起こるかというと、 上の"$@"の件で、このファイルやディレクトリ判定を行う部分があると、 文字列が空の場合に真になってしまいます。

この場合は上の=による空文字チェックの場合と違い エラーが出ないので、空文字チェックの部分を直した後に エラーが出なくなったけど何故か答えが違うな、というところで ハマりました。

まとめ

$@$*"に囲まれた時は配列と同様の扱いで、 特に"$@"(配列の場合も同様)で要素がない場合は 空文字ではなく、そこに何もないものとして処理される、 と言う点を注意しておこう、ということ。

文字列判定やらファイル名の判定やらでは そもそも配列を居れる必要が無いので、 その様な場では要素数を調べたり"$*"を使ったりして "$@"は使うのは間違いだ、ということで。

Sponsored Links
  1. ここではBash 4.3でやっています。

  2. Bashでは通常配列は0から始まります。 引数の方は$0がスクリプトの場合はファイル名になるので (コマンドラインで関数を定義したりするとシェル (/bin/bash)だったり) 1から始まって$*$1から入っています。

  3. -fでファイル、-dでディレクトリ。

  4. 判別式ではない単なる文字列が与えられた時は、空白なら、一文字でもあればになります。 なので、先の例でも=に関しても両辺が何もなしと判定される様なものなら if [ = ];then...と言う形になって、=がただの文字列として扱われ になります。

Sponsored Links

« sd_clにback機能追加 シェルスクリプトでの配列のソート#Bash »