2009年7月10日金曜日

わかってるようでわかっていなかった/bin/shスクリプトの挙動

シェルスクリプト(/bin/sh)で、コマンドが実行されるとき、最終的に引数がどうなるか・・・わかっていたつもりで、全然わかってなかったんだ、ということ。



まず、動作確認用に、こんなかんじで、引数を確認できるプログラムを用意しておきます。

% cat arg.c
#include <stdio.h>
int
main( int argc, char* argv[] )
{
    for ( int i=0; i < argc; i++ ) {
        printf("%d: |%s|\n", i, argv[i]);
    }
    return 0;
}

コンパイルします。

% gcc -std=c99 -o arg arg.c

余談ですが、いつのまに、こんなエラーが出るようになっていたのかと。浦島太郎な気分。

% gcc -o arg arg.c
arg.c: In function 'main':
arg.c:5: error: 'for' loop initial declaration used outside C99 mode

% gcc -v
Using built-in specs.
Target: i386-undermydesk-freebsd
Configured with: FreeBSD/i386 system compiler
Thread model: posix
gcc version 4.2.1 20070719  [FreeBSD]

ここからが本題。



こんなシェルスクリプトを

% cat test.sh
#! /bin/sh



LIST="aa bbb ccc"
./arg $LIST

実行してみると

% sh test.sh
0: |./arg|
1: |aa|
2: |bbb|
3: |ccc|

まあ、こうなりますよね。引数は、バラバラになると。



次の例。変数をダブルクォートで囲む場合

% cat test2.sh
#! /bin/sh



LIST="aa bbb ccc"
./arg "$LIST"

実行すると

% sh test2.sh
0: |./arg|
1: |aa bbb ccc|

この場合は、引数は1個のまま、まとめられます。



ここからちょっと改変して、実行するコマンドも、変数に入れてしまった場合。

% cat test3.sh
#! /bin/sh



LIST="aa bbb ccc"
CMD="./arg $LIST"
$CMD

実行してみると

% sh test3.sh
0: |./arg|
1: |aa|
2: |bbb|
3: |ccc|

そうなるでしょうね。



私がどうしたらいいのか悩んだのは、次の例。引数がばらされないようにするには、どうしたらいいんでしょうか。
ダブルクォートをエスケープしてやればいい?

% cat test4.sh
#! /bin/sh



LIST="aa bbb ccc"
CMD="./arg \"$LIST\""
$CMD

実行してみたら・・・

% sh test4.sh
0: |./arg|
1: |"aa|
2: |bbb|
3: |ccc"|

うおぉっ、そうきましたか。



気持ち悪いんですが、evalを使うと

% cat test5.sh
#! /bin/sh



LIST="aa bbb ccc"
CMD="./arg \"$LIST\""
eval $CMD

このときは

% sh test5.sh
0: |./arg|
1: |aa bbb ccc|

ああ、できるんですね。



shのマニュアルを読んでみたら、へーそうだったんだ、と納得。わかってるようでいて、ぜんぜんわかっていなかったり、忘れていたり。



FreeBSDのshのマニュアル
http://www.freebsd.org/cgi/man.cgi?query=sh&apropos=0&sektion=0&manpath=FreeBSD+7.2-RELEASE&format=html



この中に、「Word Expansions」という節があって、そこに、上記の疑問へのそのものずばりな解説が書かれていました。

Word Expansions



This clause describes the various expansions that are performed on words. Not all expansions are performed on every word, as explained later.



Tilde expansions, parameter expansions, command substitutions, arithmetic expansions, and quote removals that occur within a single word expand to a single field.  It is only field splitting or pathname expansion that can create multiple fields from a single word.  The single exception to this rule is the expansion of the special parameter @ within double-quotes, as was described above.



The order of word expansion is:



1.   Tilde Expansion, Parameter Expansion, Command Substitution, Arithmetic Expansion (these all occur at the same time).



2.   Field Splitting is performed on fields generated by step (1) unless the IFS variable is null.



3.   Pathname Expansion (unless the -f option is in effect).



4.   Quote Removal.



The `$' character is used to introduce parameter expansion, command substitution, or arithmetic evaluation.


「sh -vx」で実行すると、わかりやすいような、わかりにくいような感じですけど・・・

% sh -vx test4.sh
#! /bin/sh



LIST="aa bbb ccc"
+ LIST=aa bbb ccc
CMD="./arg \"$LIST\""
+ CMD=./arg "aa bbb ccc"
$CMD
+ ./arg "aa bbb ccc"
0: |./arg|
1: |"aa|
2: |bbb|
3: |ccc"|

これからわかるように、そもそも、変数CMDの中身は、

./arg "aa bbb ccc"

となっている。



最後の

$CMD

がどう解釈さるのか、よく理解できていなかったのですが、shのマニュアルによれば、1で、変数が展開されるので

./arg "aa bbb ccc"

になる。



2で、空白の位置で分割がされて

「./arg」 「"aa」 「bbb」 「ccc"」

になる。もうここでダメですね。



ただ、4のquote removalで、「"aa」はどうなってるんだ?という気がする・・・



うーん、わかったような、わかんないような。



■ 過去記事





0 件のコメント:

コメントを投稿