$Id: shell3.txt,v 1.3 2002-04-28 10:53:27+09 tomokuni Exp tomokuni $ シェルを使おう - 応用編 - 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 # を参照してください。 所要時間: 60 分(質疑応答込み)を予定 abstract: 前回まででひととおり基本的な Shell の機能や使用方法等は 解説しました。今回は応用編として主にパイプ/リダイレクトを 中心に、実際のスクリプト例を見ながら進めていきます。 これによって、少しでも UNIX の面白さが伝われば幸いです。 Content 0 今回の例題スクリプト 1 ファイルディスクリプタとリダイレクト/パイプ 2 ループ文のパイプ/リダイレクト処理 3 eval を用いた連想配列 4 trap を用いたシグナル処理 5 例題スクリプトの解説 Acknowledgement 0 今回の例題スクリプト (popcheck.sh) 1 ファイルディスクリプタとリダイレクト/パイプ 1.1 仕組み リダイレクトとパイプの原理及び注意点は次の通りである。 - リダイレクトより先にパイプのファイルディスクリプタ処理を行う。 - 左から順に評価される。 - dup2(2) はオブジェクトの複製をする。 - パイプはサブシェルで実行される。 (実装によってはパイプの最後段のみカレントシェルで実行されるものがある。 この場合(当然だが)最後段での変数操作の結果がその後も有効である。) (例) $ command >file 標準出力が file に出力される。 $ command 2>file 標準エラー出力が file に出力される。 $ command >file 2>&1 標準出力と標準エラー出力ともに file に出力される。 $ command 2>&1 >file 標準出力が file に出力され、標準エラー出力は標準出力 (この場合は端末)に出力される。 $ command1 | command2 command1 の標準出力が command2 の標準出力にパイプで結合される。 $ command1 2>&1 | command2 まず command1 の標準出力が command2 の標準出力にパイプで結合され、 その後パイプの前段の標準出力に command1 の標準エラー出力が結合され、 結果として command1 の標準出力と標準エラー出力が command2 の 標準出力に結合される。 1.2 標準エラー出力のみをパイプ パイプの後段に標準エラー出力のみを渡すにはどうしたらよいだろうか? (例 1) $ command1 2>&1 >/dev/null | command2 (1) 標準出力がパイプで command2 に接続 (2) 標準エラー出力を標準出力にリダイレクト (3) (もともとの)標準出力を /dev/null にリダイレクト 結果的に もともとの標準出力 ... /dev/null へ もともとの標準エラー出力 ... command2 へパイプ となる。 (例 2) $ command1 3>&1 >/dev/null 2>&3 | command2 (1) 標準出力がパイプで command2 に接続 (2) ダミー(ファイルディスクリプタ 3 番)を標準出力にリダイレクト (3) (もともとの)標準出力を /dev/null にリダイレクト (4) 標準エラー出力をダミーに→その結果標準出力にリダイレクト 結果的に もともとの標準出力 ... /dev/null へ もともとの標準エラー出力 ... command2 へパイプ ダミー(fd 3) ... オープンしたまま(コマンド終了後クローズ) となる。 (例 3) $ command1 3>&1 1>&2 2>&3 3>&- | command2 (1) 標準出力がパイプで command2 に接続 (2) ダミー(ファイルディスクリプタ 3 番)を標準出力にリダイレクト (3) (もともとの)標準出力を標準エラー出力にリダイレクト (4) (もともとの)標準エラー出力をダミーに→その結果標準出力にリダイレクト (5) ダミー(ファイルディスクリプタ 3 番)をクローズ 結果的に もともとの標準出力 ... 端末(画面)へ もともとの標準エラー出力 ... command2 へパイプ ダミー(fd 3) ... 一時的にオープンされて処理後クローズ となる。 (参考) [lilo:22219] より ping の結果を more で確認しつつ ping の終了コードを得る場合は、 次のようにするとよい。 $ stat=`exec 4>&1; { ping -c hoge 2>&1 4>&-; echo $? 1>&4; } | \ > more 1>&2 4>&-`; echo $stat 2 ループ文のパイプ/リダイレクト処理 for や while 等のループ文にもリダイレクトやパイプが使える。 (例) $ for i in *.txt; do echo $i; done > textfile.list ただし、シェルスクリプト中のループ文でリダイレクトやパイプを 使用した場合、 Bourne Shell ではそのループ文がサブシェルで 実行されてしまうので、変数等の扱いには十分注意しなければならない。 (これは Debian パッケージの simh/simh-unix-images を使って 仮想 PDP11 上の UNIX V7 の /bin/sh で確認) (例 1) リダイレクト、パイプ無し $ cat roop1.sh n=0 while read line do n=`expr $n + 1` echo "$n: $line" done echo "total line = $n" $ ./roop1.sh < roop1.sh 1: n=0 2: while read line 3: do 4: n=`expr $n + 1` 5: echo "$n: $line" 6: done 7: echo "total line = $n" total line = 7 $ cat roop1.sh | ./roop1.sh 1: n=0 2: while read line 3: do 4: n=`expr $n + 1` 5: echo "$n: $line" 6: done 7: echo "total line = $n" total line = 7 (例 2) リダイレクト使用 $ cat roop2.sh n=0 while read line do n=`expr $n + 1` echo "$n: $line" done < $0 echo "total line = $n" $ ./roop2.sh 1: n=0 2: while read line 3: do 4: n=`expr $n + 1` 5: echo "$n: $line" 6: done < $0 7: echo "total line = $n" total line = 0 (例 3) パイプ使用 $ cat roop3.sh n=0 cat $0 | while read line do n=`expr $n + 1` echo "$n: $line" done echo "total line = $n" $ ./roop3.sh 1: n=0 2: cat $0 | while read line 3: do 4: n=`expr $n + 1` 5: echo "$n: $line" 6: done 7: echo "total line = $n" total line = 0 roop2.sh や roop3.sh のような場合にも $n を保存する方法はある。 それは exec を使ってファイルディスクリプタを切り替えることで 実現可能である。(次節参照) (注意) 最近の Bourne Shell 系の Shell (bash,zsh,ash) は 更にこれらとはちがう挙動を示すので、これまた要注意である。 bash,sh(bash へのリンク),ash,pdksh ... roop2.sh のみ $n が 7 になる zsh ... roop2.sh, roop3.sh 共に $n が 7 になる 3 eval を用いた連想配列 bash や zsh 等には既に配列の機能が備わっているが、オリジナルの Bourne Shell ではその機能はない。しかし eval を使うことで連想配列 (まがい?)が実現できる。 (例) $ i=1 $ eval M_$i='"January"' $ echo $M_1 January これを応用すると例えば次のようなことが可能となる。 $ cat array.sh exec 3<&0 0<&1 1>&{tempfile}) することによって、 while 文をカレントシェルで 実行することにする。 こうすることで、 240--254 の部分で $max を参照でき、また 256--261 の 部分で先程のカレントシェルで実行された {} の内部で格納された連想配列を 呼び出すことによってサブジェクトを表示できるようになっている。 (単に表示するだけなら {} の内部の閉じたところで echo させてもよいが、 こうしておくことによって後々の変更やメンテナンスに有用である。 ただしあまり沢山連想配列を使うとメモリを消費してしまうが、実際には それほど気にならないだろう。) 132 行目と 237 行目で exec によってそれぞれもとのファイル ディスクリプタに戻しておき、その後のスクリプトに影響がないようにしている。 (132 行目はサブシェルで実行されているので別にいらないが一応つけておく) Acknowledgement 参考にしたもの Bourne Shell 自習テキスト (http://www.tsden.org/takamiti/shText/) プロフェッショナルシェルプログラミング (アスキー, ISBN4-7561-1632-9) LILO ML でのシェル関連スレッド 確認に使ったシェル しらいさん作の FD shell (hp.vector.co.jp/authors/VA012337/soft/fd/fd2.html) PDP11 エミュレーション上の UNIX v7 の /bin/sh その他 Bourne Shell 系のシェル (bash, zsh, ash, pdksh)