- PR -

シェル変数を引数にしたときの空白の扱いについて

1
投稿者投稿内容
コセキ
会議室デビュー日: 2001/12/07
投稿数: 13
投稿日時: 2002-07-01 09:28
こせきと申します。
シェル変数内での空白の扱いについて教えてください。

以下のような引数を表示するだけのコマンドprintargを
作っておいて、

コード:
/* printarg.c */

# include <stdio.h>

main(int argc, char *argv[]) {
  int i;
  for (i = 1; i < argc; i++) {
    printf("%d:%s\\n", i, argv[i]);
  }
}



下のシェルスクリプトを実行すると、

コード:
#! /usr/bin/sh

./printarg 'aaa bbb' 'ccc ddd eee';

echo --------------------------

VAR="'aaa bbb' 'ccc ddd eee'"
./printarg $VAR;

echo --------------------------

./printarg "$VAR";



結果は下のようになりました。

コード:
1:aaa bbb
2:ccc ddd eee
--------------------------
1:'aaa
2:bbb'
3:'ccc
4:ddd
5:eee'
--------------------------
1:'aaa bbb' 'ccc ddd eee'



最初と最後の結果については予想通りだったのですが
2番目のものが意外でした。

この2番目のような引数の渡し方をする既存のシェルスクリプトが
あるのですが、VARの値を変えることで、1番目のように空白を含む値を
コマンドに渡すことはできないでしょうか。

また、2番目のように空白で分割する処理はあまり使い道がないように
思うのですが(実際zshでやってみると2番目と3番目の結果は
同じになるようですし)なぜこのように処理されるんでしょうか?


ご存知のかたいらっしゃいましたら、教えてください。
よろしくお願いいたします。
H2
ぬし
会議室デビュー日: 2001/09/06
投稿数: 586
お住まい・勤務地: 港
投稿日時: 2002-07-01 23:55
私は主にbashを使っているのでbashで説明しますね。ここの内容なら、そんなにshと変わらないはずなので・・・。(違ったらごめんなさい)

> また、2番目のように空白で分割する処理はあまり使い道がないように
> 思うのですが(実際zshでやってみると2番目と3番目の結果は
> 同じになるようですし)なぜこのように処理されるんでしょうか?

bash> VAR="'aaa bbb' 'ccc ddd eee'"
bash> printarg $VAR

とやると、まずVAR変数が文字列へ展開されます。
その際、変数の中の"や'はただの文字列として扱われますので、VARは¥'aaa bbb¥' ¥'ccc ddd eee¥'と変換されてしまいます。実際に実行されるコマンドラインは

bash> printarg ¥'aaa bbb¥' ¥'ccc ddd eee¥'

と同じになります。つまり'は¥でエスケープされるのでクオートしたことにならず、'の間にある空白が無視されずに単語分割されてしいます。実行結果は
1:'aaa
2:bbb'
3:'ccc
4:ddd
5:eee'
となってしまいます。

う〜ん、簡単な対処法はちょっと思いつきません。別の変数に格納するという方法以外ではシェル上で回避するのは無理なんじゃないかと思います。

注)半角だと2重になってしまいますので、¥は全角で書いています。

[ メッセージ編集済み 編集者: H2 編集日時 2002-07-02 00:00 ]
H2
ぬし
会議室デビュー日: 2001/09/06
投稿数: 586
お住まい・勤務地: 港
投稿日時: 2002-07-02 10:27
追加レス: zshとbashの違い。

zshとbashで引数の展開のステップが違います。zshの場合、引数内の変数を展開した後に単語分割を行いません。そのため、zshでは2番目と3番目の結果が同じになります。

bashの場合、"$VAR"とやると、VARの中身が "" 内に展開されます。"" はクオートと呼ばれ、単語分割を無視しますので、3番目の結果のように一つの引数として扱われます。

$VAR だけだと、単語の分割が変数の展開後に行われますので、2番目の結果になります。(前述したように、変数内の ' はエスケープされます)

zshの場合、
履歴展開 (History Expansion)
→ プロセス置換 (Process Substitution)
パラメータと変数の展開 (Parameter Expansion)
→ コマンド置換 (Command Substitution)
→ 算術式展開 (Arithmetic Expansion)
→ ブレースの展開 (Brace Expansion)
→ パス名の展開 (Filename Expansion)
→ パス名の作成 (Filename Generation)

bashの場合、
ブレースの展開 (brace expansion)
→ チルダの展開 (tilde expansion)
パラメータと変数の展開 (parameter and variable expansion)
→ コマンド置換 (command substitution)
→ 算術式展開 (arithmetic expansion)
単語の分割 (word splitting)
→ パス名の展開 (pathname expansion)

ちなみに、zshでも明示的に指示すれば単語の分割を行えます。詳しくはmanページを見てください。

[ メッセージ編集済み 編集者: H2 編集日時 2002-07-02 10:30 ]

[ メッセージ編集済み 編集者: H2 編集日時 2002-07-02 10:32 ]
H2
ぬし
会議室デビュー日: 2001/09/06
投稿数: 586
お住まい・勤務地: 港
投稿日時: 2002-07-02 10:40
すみません、続けて書き込みます・・・。zshのマニュアルを読んでたら有効な方法が見つかりましたので。

> この2番目のような引数の渡し方をする既存のシェルスクリプトが
> あるのですが、VARの値を変えることで、1番目のように空白を含む値を
> コマンドに渡すことはできないでしょうか。

zsh上でならば
コード:

#! /usr/bin/sh
./printarg 'aaa bbb' 'ccc ddd eee';
echo --------------------------
VAR="'aaa bbb' 'ccc ddd eee'"
./printarg ${(z)VAR};
echo --------------------------
./printarg "$VAR";


としてみてください。(z)というフラグをつけることでパラメータ展開時にクオートを取り込んだ単語分割もやってくれます。

結果は
1:aaa bbb
2:ccc ddd eee
--------------------------
1:'aaa bbb'
2:'ccc ddd eee'
--------------------------
1:'aaa bbb' 'ccc ddd eee'

です。' が邪魔でしたら、${(Q)${(z)VAR}}としてみてください。
(Q)は一番最上位のクオートを取り除きます。

[ メッセージ編集済み 編集者: H2 編集日時 2002-07-02 10:48 ]
MMX
ぬし
会議室デビュー日: 2001/10/26
投稿数: 861
投稿日時: 2002-07-02 10:41
VARはクォーティングで調理済みですから、平の文字列
のコマンド解釈を強制しないと。

eval ./printarg $VAR;

でドウでしょう。
H2
ぬし
会議室デビュー日: 2001/09/06
投稿数: 586
お住まい・勤務地: 港
投稿日時: 2002-07-02 10:50
> う〜ん、簡単な対処法はちょっと思いつきません。別の変数に格納するという方法以外で
> はシェル上で回避するのは無理なんじゃないかと思います。

(前言撤回します)
おおっ。なるほど、すごくシンプルだ。evalはそういう使い方をするんですね。シェルは奥が深いなぁ・・・。
コセキ
会議室デビュー日: 2001/12/07
投稿数: 13
投稿日時: 2002-07-02 19:57
H2さん、MMXさん、ありがとうございました!
目からウロコが落ちました。

回答いただいな内容を手がかりに
bashのマニュアルも読んでみたんですが

  • ダブルクォートで囲まれた文字列の各文字は、その文字自体の値を持つ
  • 変数展開は(ダブルクォートの中で起こったのでなければ)単語の分割を引き起こす

というあたりがポイントなのですね。
で、evalがそれらの単語(というか引数)を結合して、もう一度シェルに
処理させると。

コマンドラインの裏ではこんな複雑な処理が行われているんですねー。
びっくり です。

ちなみにスペースをうまく扱ってくれなかったのは
Apache Antの起動スクリプトなんですが、evalを加えてやってみます。
どうもありがとうございました。


#あ、あとすいません、/usr/bin/shは/bin/shの間違いです。変なとこ間違えてた…
1

スキルアップ/キャリアアップ(JOB@IT)