$Id: shell4.txt,v 1.4 2002-04-30 00:40:57+09 tomokuni Exp tomokuni $ シェルを使おう - Bourne Shell と C Shell - lilo-bk 友國哲男 (tomokuni@netfort.gr.jp) # この文書について # この文書の著作権は友國哲男 (tomokuni@netfort.gr.jp) が有します。 # この文書は GNU フリー文書利用許諾契約書 (GFDL) の第 1.1版 またはそれ以降の # 任意の版が定める条件の下で複製、配布、変更することができます。 # # Copyright (c) 2002 Tetsuo Tomokuni All rights reserved. # Permission is granted to copy, distribute and/or modify this document # under the terms of the GNU Free Documentation License, Version 1.1 # or any later version published by the Free Software Foundation; # with the Invariant Sections being LIST THEIR TITLES, with the # Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. # A copy of the license is included in the section entitled "GNU # Free Documentation License". # # GFDL は LICENSE ファイル、若しくは http://www.gnu.org/copyleft/fdl.html # を参照してください。 所要時間: 90 分(質疑応答込み)を予定 abstract: 過去三回で主に Bourne Shell についてお話ししました。 Shell スクリプトでは主に Bourne Shell を使用しますので、 そのような講義の内容になったのですが、 普段は C Shell を お使いになる方であれば、 「(C Shell で書かれている)この部 分は (Bourne Shell で)どう書けばいいのか」、 またはその逆 の疑問をもたれた方もいらっしゃると思います。 また C Shell に興味がある方もいらっしゃると思いますので、 今回はこの二 つの Shell の長所や短所に触れながら、 違いや特徴について 「私が分かる範囲で」お話しします。 Content 0 はじめに 歴史的背景から 1 機能面から見た相違 1.1 メタキャラクタ 1.2 変数操作 1.3 配列 1.4 演算 2 仕様面から見た相違 2.1 変数代入 2.2 変数のスコープ 2.3 制御文 2.4 その他の文法 2.5 リダイレクト/パイプ 2.6 ファイルディスクリプタ 3 実装面から見た相違 3.1 シェルスクリプト 3.2 変数 3.3 処理の例外 3.4 構文解釈 4 まとめ スクリプト言語として見て Acknowledgement 0 はじめに 歴史的背景から Bourne Shell - 最初の Shell - おそらくどの UNIX システムにも搭載されている ... (オリジナルの Bourne Shell は)対話的には用いられることは 少ないが、移植性や互換性があるため主にスクリプトで用いられる。 C Shell - Bourne Shell でなかった機能の補完と使いやすさ/理解しやすさを考慮して設計 - 文字通り C 言語に近い文法を持っている - コマンドライン補完機能、ジョブコントロール (jobs,fg,bg 等) を 初めて実装した ... どちらかというと対話的なことを考えてされている。 また C 言語の文法に近いことから、 C のプロトタイプ(実験) としてのスクリプト言語としても用いられる。 以下では Bourne Shell のプロンプトを '$' で、 C Shell のプロンプトを '%' で、それぞれ表記する。 1 機能面から見た相違 1.1 メタキャラクタ Bourne Shell になくて C Shell にあるもの ~ (チルダ) -> ホームディレクトリ [^...] -> ... に列挙されている文字以外 {a,b,...} -> a,b,... という文字列のリスト Bourne Shell と C Shell で同じもの *, ?, [...] 1.2 変数 C Shell の変数は 0 個以上の句のリストとして扱われている Bourne Shell になくて C Shell にあるもの 1) 配列 $var[num] -> num によって選択された var の値(リストの num 番目) 2) $#var(${#var}) -> var の句の数 3) $?var(${?var}) -> var の set/unset 情報 (boolean 値) 4) $< -> 一行読み込んでその値を返したもの 5) 修飾子 $var:h -> パス名の最後の部分を除いたパス名 $var:t -> パス名の最後の部分だけ $var:r -> 拡張子を除いた部分 $var:e -> 拡張子だけ $var:s/x/y/ -> x を y に置換する $var:q -> 文字列をクォートする $var:x -> 文字列を空白やタブで区切り、クォートする Bourne Shell による実現方法(配列の扱いについては次節で紹介) 1) target=`set -- $var; shift ; echo $1` 2) var_num=`set -- $var; echo $#` 3) var_def=`var_def=${var+1}; echo ${var_def:-0}` 4) read line 5) cut, sed, tr 等と `...` を用いて実現する。 - echo $var | sed -e 's/^\(.*\)\/\([^\/]*\)/\1/' - echo $var | sed -e 's/^\(.*\)\/\([^\/]*\)/\2/' - echo $var | sed -e 's/^\(.*\)\.\([^\.]*\)/\1/' - echo $var | sed -e 's/^\(.*\)\.\([^\.]*\)/\2/' - echo $var | tr x y - "$var" - for elem in $var; do var_q="${var_q:+"$var_q "}\"$elem\" "; done; \ var=$var_q Bourne Shell にあって C Shell にないもの 環境変数 IFS ... 従って C Shell では IFS の操作はできない 変数参照(置換) ${var-word}, ${var:-word} ${var=word}, ${var:=word} ${var+word}, ${var:+word} ${var?word}, ${var:?word} 置き換えが必要なもの Bourne Shell C Shell $@ <-> $argv $1, $2, $3, ... <-> $argv[1], $argv[2], $argv[3] $? <-> $status Bourne Shell と C Shell で同じもの $0, $$, $!, $* 1.3 配列 C Shell ではもともと変数がリストとして扱われており 最初から配列アクセスが可能である。 Bourne Shell では配列機能がないが、 eval を使用する ことで配列(連想配列)のようなものを便宜的に作ることは 可能である。 # 定義 hoge_1=hoge hoge_2=hogehoge hoge_3=hogehogehoge ... # アクセス num=1 while [ $num -lt $max ] do eval echo '$'hoge_$num done 1.4 演算 C Shell には演算も機能としてある。 % @ i++ # 変数 i をインクリメント 演算子としては C で使えるものは大抵使える。 (優先順位は、、、 C とほとんど一緒のようだがきちんとは 調べていません。) Bourne Shell では配列と同じく演算機能もないが、 外部コマンドである expr を用いれば実現可能である。 $ i=`expr $i + 1` 2 仕様面から見た相違 2.1 変数代入 Bourne Shell での代入は $ var=hoge であるが、 C Shell では % set var=hoge or % set var = (hoge fuga) となる。 Bourne Shell では '=' の前後は空白を入れられなかったが、 C Shell では入れてもよい。その場合は両側にいれないと いけない。片側だけではダメである。 2.2 変数のスコープ C Shell の変数には以下の 2 種類がある。 - 環境変数 ... 子シェルにも引き継がれる変数。 setenv で定義する。 - Shell 変数 ... カレントシェルのみで使用できる変数。 set で定義する。 (親)% setenv VAR HOGE (親)% set var = hoge (親)% echo $VAR HOGE (親)% echo $var hoge (親)% csh (子)% echo $VAR HOGE (子)% echo $var var: Undefined variable. Bourne Shell の変数には C Shell の意味での種類はないが、 最初から用意されている環境変数とユーザーが定義する Shell 変数がある。 環境変数は子シェルにも引き継がれるが、 Shell 変数は export コマンドで 外から見えるようにしてやらないと子シェルからは参照できない。 (親)$ VAR=HOGE (親)$ var=hoge (親)$ echo $VAR HOGE (親)$ echo $var hoge (親)$ export VAR (親)$ sh (子)$ echo $VAR HOGE (子)$ echo $var ... 何も出ない(echo での改行出力のみ)。 2.3 制御文 1) if 文 Bourne Shell では if command1 then ... elif command2 ... else ... fi となるが、 C Shell では if (exp1) then ... else if (exp2) then ... else ... endif という記述になる。 ここで注意したいのは、 Bourne Shell では if の 判断にはコマンドの終了ステータスを使用しているが、 C Shell では式の評価をしているところが決定的に 違う部分である。 2) while 文 if 文と同じように Bourne Shell では while command do ... done となり、 C Shell では while (exp) ... end となる。 Bourne Shell では更に until 文も用意されている。 3) foreach 文 Bourne Shell では for name in list do ... done となり、 C Shell では foreach name (list) ... end となる。特に機能的な違いはない。 4) 文字列分岐 Bourne Shell では case 文を用いて case $var in pat1) ... ;; pat2) ... ;; ... patn) ... ;; esac となる。 C Shell では C 言語と同じく switch-case 文を用いて switch ($var) case pat1: ... breaksw case pat2: ... breaksw ... case patn: ... breaksw endsw と書く。 共に pat にはメタキャラクタが使用できる。 Bourne Shell の case 文は pat 一つにつき ';;' が必要だが、 C Shell の switch-case 文には breaksw でターミネートしない ことも可能である(breaksw があるか endsw まで文を実行する)。 5) その他の文 - eval, exit 文は双方とも同じである。 - exec 文も同じであるが、 Bourne Shell では更にファイルディスクリプタ の切り替えも行える。 (2.6 節参照) - Bourne Shell での . コマンドは source コマンドが対応する。 - Bourne Shell での trap 文は C Shell での onintr 文が対応するが 文法が異なる。 また onintr は任意のシグナルが扱えない(SIGINT のみ)。 - shift 文は双方に存在するが、 Bourne Shell での shift 文の引数は 数字を取り "$@" に対して shift する数を表すが、 C Shell での引数は シェル変数を取りその名前のシェル変数のリストを 1 つだけシフトする ことを表す。 - wait 文は Bourne Shell では引数(子プロセスのプロセス ID)を 取ることが出来るが、 C Shell ではできない。 - 繰り返しを表す repeat 文やラベルにジャンプする goto 文は C Shell にしか存在しない。 - 逆に read 文、':' コマンド、 getopts コマンドは Bourne Shell にしか存在しない。 2.4 その他の文法 alias, function alias 文は基本的に同じ意味だが、 alias 定義をする際の % alias ls 'ls -F' # '=' がある $ alias ls='ls -F' # '=' がない という書き方の相違と、 C Shell では alias に引数が取れる (Bourne Shell では取れない)ことが異なる。 % alias search 'find . -name \!* -print' (カレントディレクトリ以下で引数で指定された名前をもつファイルの 一覧が表示される。) Bourne Shell では代わりに function が用意されており、 上の例であれば $ search () { find . -name $* -print; } と書ける。 C Shell と Bourne Shell で同じもの クォート '', "", `` 短絡演算子 &&, || サブシェル実行 () バックグラウンド実行 & 2.5 リダイレクト/パイプ C Shell では以下のリダイレクト/パイプが使用可能である。 cmd > file cmd の標準出力を file に書き込む cmd >& file cmd の標準出力/標準エラー出力を file に書き込む cmd >> file cmd の標準出力を file に追加する cmd >>& file cmd の標準出力/標準エラー出力を file に追加する cmd < file file の内容を cmd の標準入力へ読み込ませる cmd < word ヒアドキュメント cmd1 | cmd2 cmd1 の標準出力を cmd2 の標準入力に繋ぐ cmd1 |& cmd2 cmd1 の標準出力/標準エラー出力を cmd2 の標準入力に繋ぐ しかし、例えば「標準エラー出力のみ後段のコマンドの標準入力に繋ぐ」 ことができない。 2.6 ファイルディスクリプタ このような C Shell の制限は、実は任意のファイルディスクリプタ制御が できないことに起因する。 一方 Bourne Shell では、任意のファイルディスクリプタをユーザーが exec を使用することで処理でき、その結果複雑な処理も可能になる。 exec 3<&0 0&1 1>outfile while read line do case "$line" in [!#]*'='*) eval $line ;; esac done exec 0<&3 3<&- exec 1>&4 4>&- この例では read コマンドも活用しているが、これも C Shell には存在しない。 3 実装面から見た相違 3.1 シェルスクリプト 1) シェルのオプション Bourne Shell ... sh コマンドに直接オプションを与えて制御できる。 また set コマンドのオプションでも制御できる。 (つまりインタラクティブシェルのときでも途中で 状態を変更できる) C Shell ... csh コマンドに直接オプションを与える。 2) シェルスクリプト起動 Bourne Shell ... 起動時に何も初期設定ファイルを読み込まない。 C Shell ... 起動時に ~/.cshrc があればそれを読み込む。 .cshrc にいろいろ設定を書いていると、 C Shell スクリプトを 実行する度に ~/.cshrc を読み込むので、実行速度が遅くなる。 そこで C Shell スクリプトには、最初に #!/bin/csh -f と -f オプション(初期設定ファイルを読み込まない)を追加して 書いていることが多い。 3.2 変数 変数の処理に関しても実装面での相違がある。(1.2 節を参照) 1) 予約されたシェル変数 Bourne Shell ... $*, $@, $0, $1, ..., $9, $#, $$ 等 特殊文字と数字だけである。 C Shell ... $argv, $status 等一般文字の組み合わせたものが 予約されている。 2) 変数の処理 Bourne Shell ... 置換や代入する機能は存在するが、直接的に 文字列操作はできない。 C Shell ... 限定的ではあるが文字列操作ができるが、 汎用的な意味での置換処理をする機能はない。 3.3 処理の例外 C Shell には重大な例外の処理がある。 それは '!' (エクスクラメーションマーク)である。 '!' はヒストリを参照する際に使用されるが、そのプライオリティが 高く設定されている(と思う)ため、いつでもシェルで展開される。 % ls foo bar baz % !! foo bar baz % '!!' foo bar baz % "!!" foo bar baz このように ' (シングルクォート)や " (ダブルクォート)で囲んだ場合でも ヒストリに展開されてしまう。 ただしこの場合でも ' や " で囲んだ中のヒストリは alias を サーチしないので、違う結果になることに注意。 % alias ll 'ls -alF' % ll foo bar baz % 'll' 'll' ll: Command not found. この結果として、例えば引数を取るような alias を定義する場合に 注意が必要になる。 % alias search 'find . -name !* -print' alias search 'find . -name -print' % search hoge find: paths must precede expression Usage: find [path...] [expression] % alias search 'find . -name \!* -print' % search hoge ./tmp/hoge 3.4 構文解釈 Bourne Shell では改行または ';' (セミコロン)で文の解釈をし 前のものから一文ずつ実行する。 C Shell でも区切りは改行または ';' と同じであるが、構文解析が 改行が基本にあるため、常に行全体を解析して評価する。 そのため途中で ';' があったり、また前の状態に依存した文を 記述すると意図していない動作をすることがある。 $ alias cmd='if [ $? = 0 ]; then echo Success.; else echo Fail.; fi' $ cmd Success. % alias cmd 'if ($status == 0) then echo Success.; else echo Fail.; endif' % cmd if: Improper then. $ if [ x"$foo" = x ]; then bar=$foo; fi $ echo $bar (何も出力されない) % if ($?foo) setenv bar $foo foo: Undefined variable. C Shell には前節で述べた例外の他にも、構文解釈の仕方による例外解釈がある。 それは " クォート中で " あるいは $ 、改行を使用した場合である。 sh$ foo="You said, \"let's go.\"" sh$ echo $foo You said, "let's go." csh% set foo = "You said, \"let's go.\"" Unmatched '. sh$ foo="\$bar $HOME" sh$ echo $foo $bar /home/tomokuni csh% set foo = "\$bar $HOME" bar: Undefined variable. sh$ foo="line 1 \ > line 2" sh$ echo $foo line 1 line 2 sh$ echo "$foo" line 1 line 2 csh% set foo = "line 1 \ line 2" csh% echo $foo line 1 line 2 csh% echo "$foo" Unmatched ". 4 まとめ スクリプト言語として見て 2.6 節や 3.3 節、 3.4 節で挙げたような相違がある所以は、ひとえに 「 Bourne Shell の悪いユーザーインターフェースを改善しよう」 という C Shell を作り上げる際の動機(主にヒストリ機能の充実と 文法を C 言語のようにすること)に集約されていると思われる。 (ユーザーインターフェースを重視する姿勢) 互換性の面だけではなく、上記のような事情によって Bourne Shell が所謂 「シェルスクリプト」として使用される場面で使われるのであろう。 ただし、プロトタイプとしてのスクリプトや配列を使用するようなスクリプト に関しては C Shell を使用するメリットはあると思われる。 (現在では単に配列を使用するだけであれば bash や zsh のような Bourne Shell 系の高機能シェルを使用すればいいという話もあるが。) Acknowledgement 参考にしたもの プロフェッショナルシェルプログラミング (アスキー, ISBN4-7561-1632-9)