Hatena::ブログ(Diary)

ゴー!ゴー!けいじ!!

2010-03-23

今日は22時からNHKを見よう

密かに楽しみにしている番組、それが「プロフェッショナル -仕事の流儀-

NHKの毎週火曜日22時から放送されている番組でございます。

その本日ゲストは「三浦知良」。奥さん、カズですよ、カズ。

そりゃ、サッカーファンであれば絶対見なければいけません。わたくしが中学生の頃(もちろん、まだJリーグなんてありません。。。)、何の番組か忘れましたが、サントスに移籍して間もないカズの映像を見て泣きそうになりました。その頃は、まだジャポネーゼがサッカーなんてできるのか?なんてバカにされていた時代だったのに、カズが実力でプロになってる姿を見て本当に感動しました。そういえば、あの頃って、日本がワールドカップに出るなんて夢にも思えなかった時代。今やワールドカップ出場が当たり前になりつつあるなんて、未だに信じられません。

そんな時代からプロとしてやっているカズの言葉を聞けるなんて。奥さん、見ないとあきません。

ってわけで、本日3月23日火曜日)の22時〜23時までの1時間。皆さん、是非みましょー

第4章:リダイレクションによるファイル操作

最初に

第1章から書式や変数などの話しばかりで少し飽きてきましたが、どれもシェルスクリプトを書くためには必要な知識なので何とかがんばっていきたいと思いまする。

第4章は25ページほどで、ファイル操作関連の話しがメイン。やっとこさなんだかプログラムっぽっくなってきました。

ファイルディスクリプタ

ファイルに何か書き込みたいときは、ファイルディスクリプタを使ってファイルアクセスをする。このファイルディスクリプタの番号のうち、0,1,3番は予約されており重要なので覚えるべし。

  • 0番 標準入力(通常はキーボードからの入力を受け取る)
  • 1番 標準出力(通常は端末画面への出力、これに書き込めば端末画面にメッセージを出力できる)
  • 2番 標準エラー (通常は端末画面への出力)

リダイレクション

通常の入力や出力先を変更することをリダイレクトと言う。リダイレクト記号として「>」「<」を使用する。「>」でファイルに書き込むと、元々のデータクリアされるので、追記させたい場合は「>>」で行う。

「>」でファイルへリダイレクトすると、正確には1番のディスクリプタを経由するもをファイルへ向けると言う意味になる。エラーはディスプリタ番号2なので、「>」でファイルに向けてもエラーメッセージファイルに書き込まれない。それを踏まえ、通常のリダイレクトを正確に書くと下記のようになる。

$ cat xxx 1> abc
xxx: No such file or directory

上記のようにすると、エラーメッセージはディスプリタ2番なのでファイルabcに書き込まれず、画面に表示される。

もし、エラーメッセージファイルに向けるリダイレクトは以下の通り。これを利用すれば、エラーメッセージだけ別のファイルで保存することができる。

$ cat xxx 2> abc
$ cat: xxx: No such file or directory

もし、エラーメッセージと通常の出力結果も1つのファイルに入れたい場合は下記のようにする。

$ cat abc xxx > nnn 2>&1

リダイレクトの記号「>」は、どの場所に書いてもいいが、通常は最後に書くのが普通。もしリダイレクトを複数に書いたりした場合は「左から右」へとなる。例えばあるコマンドの実行結果やエラーメッセージをすべて消し去るときの例は下記のようになる。下記のコマンドだと、コマンドの標準出力を/dev/nullへ書き込み、それから標準エラーファイルディスプリタ2)も標準出力(ファイルディスプリタ1)へ行くように書き込んでいる。すでに標準エラーは/dev/nullへ向かっているので、結果、標準エラーも/dev/nullへ行く。

コマンド > /dev/null 2>&1

リダイレクトは、変数でも可能。

STDOUT=/dev/null
コマンド > $STDOUT

標準出入力を扱うものとしてパイプ(|)がある。パイプの左側の標準出力をパイプの右側の標準入力にすると言う意味になる。

リダイレクトを使った書き込み

echoコマンドは標準出力にメッセージを出力する。もし、echoコマンドで標準エラーメッセージを出力したい場合は下記のように書く。

echo "Error messages." 1>&2

もし、3以上のファイルディスプリタへ書き込みしたい場合は

$ echo abcdef 1>&8
$ exec 8> hhh
$ echo abcdf 1>&8

ディスプリタは、リダイレクト先のファイルがなければ自動で作ってくれる。

リダイレクトでの読み込み

標準入力も、もちろんリダイレクトできる。キーボードからの入力の代わりにファイルをそのままリダイレクトする。例としては以下の通り。

commmand < file

また、ファイルではなく書いてある文字列とかをそのまま入力することも可能。

command << word

これを「ヒア・ドキュメント」と言う。

ファイルサイズを0にする。

/dev/nullを利用する。エディタで開いて中身を消しても1バイトだけ残ってしまうので。下記のどちらでも大丈夫

$ cat /dev/null > file
$cp /dev/null file

ヒア・ドキュメント(Here Documents)

1つのシェルスクリプトの中に、入力する文字も同時に書いてしまう方法のこと。最初に現れた文字列がもう一度出てきたら、そこまでを入力として認識する。一般的にはENDなどを使用。以下にサンプルをば。最初のEND以降で、2回目のENDまでが標準入力として扱う。

>|sh>

$ cat << END

> This is a

> input data.

> END

||<

上記のとき、変数を標準入力から渡すことも可能。逆に、$DIRなどを変数としてではなく文字列として「$DIR」として扱わせる方法は「\$DIR」と文字列だけをクォートする方法と、下記のようにヒア・ドキュメント全体をクォートする方法がある。後者の方がわかりやすくていい。

$ DIR=document
$ cat << \END
> This is a
> here $DIR.
> END
This is a
here $DIR.

ほかにもクォートする方法として、ENDをシングルクォーテーションで括弧っても同じようになります。('END')

<<の後にハイフン(-)をつけると、ヒア・ドキュメントの部分の先頭のタブ(スペースはダメ)は入力されていないものとして扱う。インデントを無視させたいときに使用する。例えば下記のような書き方をしておけばわかりやすい。

then
      command  <<- END
            This is a here document.
      END
fi

第3章:シェル関数、組み込みコマンド

とりあえず、この第3章ではシェルスクリプトをさらに小さくした1つの固まりのコマンドだと言う程度の理解で十分で、詳しくは第9章でやるらしい。この章では、関数を使う上での注意点を見ていくようだ。

シェル関数

古くからある機能で、よく使われる機能で、一連のコマンドを単に処理するだけ。関数なので戻り値も設定できる。たとえば、ls -lと同じ動作をするシェル関数lslを作成する。

$ lsl(){
> ls -l
> }

動作状態による違い

定義した関数を実行すると、それはそのシェルの中で動作する。しかし、関数内でリダイレクトとかをさせると、サブシェルを作って動作する。

関数が、サブシェルで動作するかどうかによって、動作上の違いある。

ディレクトリ

関数終了時のディレクトリが変わる。

関数内でディレクトリを移動する動作を行ったとき、カレントシェルの場合にはその変更内容が残る。しかし、サブシェルの場合には、関数が終了すれば元のディレクトリに戻る。

変数

関数終了時に変数の内容が変わる。

関数内で変数に対して何か処理を行ったとき、カレントシェルの場合にはその変更内容が残る。しかし、サブシェルの場合には関数が終了すれば元の値に戻る。

exitコマンド

シェルが終了するかどうかが変わる。

関数内でexitコマンドを実行したとき、カレントシェルで実行している場合にはそのシェル自体が終了する。しかし、サブシェルの場合にはその関数が終了するだけ。

引数に関する注意

位置パラメータの$0の値が、シェルスクリプトとシェル関数では異なる。シェルスクリプトだと、$0はそのスクリプトの名前が格納されるが、シェル関数ではそのシェル関数を呼び出したシェルが格納される。

シェル関数の場合

$ display(){
> echo $0 $1 $2 $3
> }
$ display aaa bbb ccc
/bin/bash aaa bbb ccc

シェルスクリプトの場合

$ cat display
#! /bin/sh
echo $0 $1 $2 $3
$ ./display aaa bbb ccc 
./display aaa bbb ccc

変数に関して

シェルスクリプトを1つ作成すると、そのシェルスクリプトで使用させる変数や関数で使用される変数すべてが格納されるテーブルが1つ用意される。1つのシェルスクリプト内の複数の関数で、同じ変数名を使用しているとその変数は同じ変数として処理されてしまう。なので、変数を使用するときは異なる変数名を使うように気をつけるべし。

関数をカレントシェルで動作させる方法

通常、シェルスクリプトに書かれているコマンドは、そのシェルスクリプト内で動作する。決して、そのシェルスクリプトを実行したシェル上で動く訳ではない。しかし、下記のようにシェルスクリプトを実行するとカレントシェルでそのスクリプト内のコマンドを実行することが可能なる。

$ . シェルスクリプト

ちょっとわかりにくいのでソースを見ながら。まず、下記のようなシェルスクリプト書く。

$ cat aaa
#! /bin/sh
pse()
{
ps -ax | sort -bn
}

下記のようにこのスクリプトファイルを実行しても、pseが実行されるわけではありませんし、その後でpseと実行したって実行されるわけがない。

$ ./aaa
$ pse
pse; not found

しかし、先ほどのファイルを.(ドットコマンド)を使って実行するとpseをカレントシェルで実行可能になる。

$ . ./aaa
$ pse
ps -ax | sort -bnの実行結果が表示される

このようなシェルスクリプトは、現在シェル環境を設定するためのシェルスクリプトとなっている。そこで、このようにシェル環境を設定するためのスクリプトファイルは、ファイルの最後に.shとつけ、さらに、直接実行されないように読み取りのみの権限にしておく。

組み込みコマンド

シェルにはじめから組み込まれているコマンドを組み込みコマンドと言う。個別のコマンドを個別に走らせるより組み込みコマンドを使用したほうが速さやリソース的に効率的らしい。詳しくは後の章で記載されているので、この章では簡単に見ていく。

ヌルコマンド(:)

何もしないが、必ずいつも成功する(真の結果を返す)コマンド。条件判定を必ず真にしたいときによく使用される。たとえば下記に示すようなwhileの場合。下記の例だとヌルコマンドでwhileの条件が必ず真にあるので無限ループになる。

while :
do
  if ....
  then
     break
  if
done
ドットコマンド(.)

新しくプロセスを作らずに、現行のシェルプロセスを使って指定されたファイルを読み込み実行する。その結果、指定されたファイルに記載されている変数や関数が現行のシェルで使えるようになる。

breakコマンド

forやwhileのループから抜けるときに使うコマンド。いくつかのループをまとめて抜けるときには、breakの後に数字をパラメータとして渡すと抜けることができる。

cdコマンド

指定したディレクトリに移動するコマンド。パラメータがなければ、そのユーザのホームディレクトリに移動する。

continueコマンド

forやwhileでいったんループ内の処理を終えて、またループの最初の処理から行わせたいときに使用する。引数を渡すことで、ネストしている何番目のループに戻るかなんてことも可能。

echoコマンド

引数の部分を標準出力に表示するコマンド。特殊コードを出力させるときだけSystem V系とBSD系で異なるらしい。詳しくは12章の汎用性でやる。

evalコマンド

1行に複数のコマンド実行が入っているとき、いっぺんに複数のコマンドを実行してくれる。

execコマンド

execコマンドは、引数のコマンドをカレントシェルプロセスと置き換えて実行する。

exit

シェルを終了させたいときに使用する。引数を渡すとこのシェルの終了コードを設定することができ、その終了コードは$?に格納される。通常は、正常終了したときには0を格納する。

exportコマンド

パラーメタで指定された変数を、このタイミング以降で実行されたシェルやコマンドで使用できるようにする。通常、変数などは他のシェルからは見えないはずだが、それを見えるようにするのがexportコマンド。詳しくは第5章で。

pwdコマンド

現在いる完全パスを表示する。

readコマンド

キーボードからの入力をreadの後に指定されたものに格納する。readの後の複数の変数を用意すると、複数の入力値も格納できる。このとき、単語をスペースで区切るように使うスペースなどの区切り文字をIFS(Internal Field Separators)と言う。スペースのほかにタブ改行もIFSにあたる。詳しくは第4章、第8章で。

readonlyコマンド

指定された変数を読み出し専用にする。あんまし使用されないみたい。

returnコマンド

シェル関数から抜けるコマンド。returnの後に指定した番号がその関数の終了コードになる。

setコマンド

シェルオプションをオンにしたりオフにしたりするコマンド。setで位置パラメータの値を設定すると、下記のように位置パラメータの値が全部クリアされるので注意。

$ cat aaa
#! /bin/sh
echo $0 $1 $2 $3 $4
set aa bb cc
echo $0 $1 $2 $3 $4
$ ./aaa 1 2 3 4 5
./aaa 1 2 3 4
./aaa aa bb cc

上記のようにsetで設定し直すことによって、$4の値がリセットされ出力されない。

shiftコマンド

このコマンドは位置パラメータを左にずらす。

testコマンド

ある条件を判定し、条件が真の場合は0、偽の場合は0以外の値を返す。条件として様々な判定をさせることができ、ファイルの読み取りチェックや文字列の長さチェックなどもテスト可能。何が判定できるかあまりに多いので、manを読んだり、サンプルソースで出てきたときにその都度勉強しなおすべし。

trapコマンド

シグナルを受け取ったときに、指定したアクションを実行することが可能。詳しくは第5章で。

typeコマンド

typeコマンドのパラメータにはコマンド名を指定する。パラメータで指定したコマンドの場所などを教えてくれる。サンプルを以下に示す。

$ type date
date is /bin/date
$
umaskコマンド

ファイルを生成するときに、読み書きなどどのモード(権限)で作るかを決定するコマンド。

unsetコマンド

指定した変数や関数を消去するコマンド。ただし、シェルが始めから持っている変数(PATHやIFS)などはunsetできない。

waitコマンド

引数に動作しているプログラムプロセスIDを渡す。そのプログラムが終わるまで待つ。使い方としては、バックグラウンドで走っている処理が終わるのを待ち、終わったら処理を行いたい場合などに使用する。