標準入出力すりかえのテクニック:ステップ・バイ・ステップ・シェルスクリプト(3)
シェルスクリプトは、単にプログラムを次々に実行するだけのものではありません。それぞれのプログラムが連係するように、あるプログラムの結果を別のプログラムへと自動的に受け渡す機能があります。それがリダイレクトとパイプです。
echoとはやまびこか?
今回は、前回作成したコマンドの中身の解説から始めましょう。
echo "Hello World"
このシェルスクリプトはいったい何をやっているのでしょう? これは誰が何を実行して、どうして画面に文字が表示されるのでしょうか? ふだんなにげなく使っているコマンドでも、少し深く突っ込んでみるとLinux(UNIX)の仕組みや特徴が見えてきます。
実はechoは、「bashの内部コマンド」の1つであり、bash自身によって実行されています。意外に思われた方も多いかもしれません。なぜなら「echo」は外部コマンドとしても存在する(通常は/bin/echo)からです。でも、普通に設定されたLinuxで実行されるechoはbashの内部コマンドのほうです。試しにやってみましょう。
$ echo --version --version $ /bin/echo --version echo (GNU sh-utils) 1.16
上は“--version”という文字列をそのまま表示するだけですが、下は“--version”を「バージョンを表示しろ」というオプションと理解して、/bin/echoのバージョンを表示してます。
このように内部コマンドのechoと外部コマンドのechoにはわずかな違いがあります。なぜ2つあるのかは私もわかりません。伝統的にUNIXでそのようになっているようです。
いずれにせよ、echoは指定された「引数」を「標準出力」に出力するようプログラムされています。echoの基本仕様はたったこれだけです。ではこの「引数」と「標準出力」について少し詳しくお話してみましょう。
引数とは?
「引数」とは、コマンドを実行する時にコマンドの後ろに(スペースを空けて)書かれるもののことです。引数を指定することにより、コマンドに違った働きを与えることができます。
引数の中でも通常マイナス(-)で始まり、コマンドに特定の役割を与えるものを「オプション」と呼びます。上記の例で説明すると、内部コマンドのechoは引数“--version”をオプションとは理解せずにそのまま表示しているだけですが、外部コマンドのほうのechoは“--version”をオプションとして理解し、プログラムのバージョンを表示しています。
このように、Linuxのほとんどすべてのコマンドは何らかのオプション(--helpや--versionなど)を指定することができるようになっています。また引数には「ワイルドカード」も使用できます。ワイルドカードは任意の複数文字にマッチする“*”と、任意の一文字にマッチする“?”、および指定された任意の文字のうち1文字にマッチする“[ ]”が使用でき、ファイル名とマッチされます。例えば“abc”“a3b”“a45b”“.abc”の4つのファイルが存在したとすると、
$ echo * abc a3b a4b
とやればカレントディレクトリのファイルがピリオド(.)で始まるものを除いてすべて表示されます。ビリオドではじまるファイルはLinuxでは伝統的に隠しファイル、設定ファイルとして使われいるので間違って消したり(rm *などして)しないようになっています。かといってまったく見ることができないわけではなく、以下のようにすればピリオドで始まるファイルにもマッチします。
$ echo .* . .. .abc
ではいろいろと例を挙げてみます。下記では、1文字に合致する“?”と、囲まれた任意の文字の中の1文字に合致する[ ]を紹介します。
$ echo a?b abc a3b $ echo a[34]* a3b a45b
[ ]はまた、[a-z]や[5-9]などの指定もできます。[a-z]はaからzのアルファベット小文字全て、[5-9]は5から9までの数字にマッチします。
このワイルドカードはMS-DOSやWindowsのcommand.comにも同じような機能があるように見えますが、MS-DOSなどでは、実はそれぞれのアプリケーションがワイルドカードを展開しています。一方、Linux(UNIX)では、シェルがワイルドカードを展開したうえでアプリケーションに渡しています。これは使う人にとってはどうということのない違いですが、アプリケーションを作る側としては、「MS-DOSのプログラム=ワイルドカードの展開を自前でやる」「Linuxのプログラム=ワイルドカードは一切気にしないでよい」という違いがあります(注:MS-DOSのCコンパイラなどにはワイルドカード展開ルーチンが標準で付属していて、プログラマーが意識する必要はないようになっています)。
標準出力と、そのすり替え
こんどは標準出力について説明しましょう。「標準出力」とは、プログラムとOSのあいだでデータをやり取りをする「橋」の役割をします。プログラムが何かを標準出力に出力すると、それはOS(この場合はLinux)に渡されて解釈され、結果として画面に文字が表示される仕組みになっています。このような仕組みになっているおかげで、出力先を「コマンドを実行する段階で」変更することができます。
例えば、echoコマンドが引数の内容を画面に出力する例を紹介しました。
echo "Hello World"
一方、シェルスクリプトファイルの作成の方法として次のような例も紹介しました。
$ echo 'echo "Hello World"' > helloworld
echoが出力した結果を「helloworld」というファイルに出力する方法でした。
では、echoは、画面に出力するときと、ファイルに出力する時でプログラム上の処理を分けているのでしょうか? いいえ、そうではありません。echoあくまでも渡された引数を標準出力に出力する仕事以外はしないのです。2つ目の例にある不等号(>)は「リダイレクト」と呼ばれ、「橋の架け替えをするもの」つまり「標準出力の行き先を変更するもの」です。
OSは図1のようにさまざまなデバイスやファイルへの入出力をサポートします。デバイスとは周辺装置一般のことを指し、画面、キーボード゛、モデム、CD-ROM、メモリカード、テープ装置……とコンピュータで使えるありとあらゆる装置はデバイスとしてOSでサポートされています。
通常、実行されたコマンドは図2のように標準出力と画面、標準入力とキーボードが結びつけられています。
このリダイレクト処理は、echoコマンドをbashが起動する直前に行っています。上記のシェルスクリプトの処理は次のように行われています。
- 入力されたコマンドechoをbashが認識する。
- 同時にbashは標準出力がhelloworldというファイルにリダイレクトされたことを認識する
- bashがechoコマンドを起動する直前にechoの標準出力をファイルへ変更する(すりかえる)
- echoコマンドは標準出力へ文字を出力する。echoはファイルへ変更されていることは全く意識しない。
- echoにより標準出力(bashによりファイルへすりかえられている)へ出力された情報をOSが受け取り、ファイルへ書き込む。
この「すりかえ」は、細かい内部処理で言うと「fork」というプロセスを作る処理と「exec」というプログラムを行うことで実現されています。しかしこれについては、また別の機会に書きたいと思います。
“cat=ねこ” のこと?
標準出力に対応するものとして、「標準入力」というものが存在します。以下の内容のファイルを作成し、実行してみましょう。ファイル名を「helloworld2」とします。
Helloworld2 cat <<EOF Hello World EOF
さっそく実行してみましょう。
$ helloworld2 Hello World
Helloworld2では、「≪」と最後の「EOF」にはさまれた部分を「標準入力」として取り込み、catというプログラムへ渡して実行させています。実はここにある最初の“EOF”は、ほかの文字列であってもなんでもかまいません。ただし、最後の「EOF」(3行目のEOF)は、必ず行頭から始まっている必要があります(スクリプト内でタブなどを使って整形している場合は注意する必要があります)。
標準入力は標準出力と同じように、デフォルトでは画面から(つまりプロンプトから)の入力に割り当てられています。しかしこのように、その標準入力も、リダイレクトを使って「すりかえ」ができます。
catというコマンドは詳しく言うと、「与えられた引数をファイルとして認識し、それらをつなげて標準出力へ出力する。引数が存在しない場合は、標準入力から読み込んで標準出力へ出力する」というコマンドです。今回の例題では、「引数が存在しない場合」の使い方に相当します。もともとはこのcatという名前は「ファイルをつなげる」という意味の英語のconcatenateから来ています。残念ながら「ねこ」ではありません。
ではこのcatを使って、いろいろやってみましょう。
$ echo "Hello World" > testfile
すでに解説したとおり、リダイレクトによりファイルへ文字を出力しています。これをcatで出力してみます。
$ cat < testfile Hello World
この例はどうでしょうか? さきほどとは逆向きのリダイレクト(<)ですね。これは「標準入力」を「ファイル」に「すりかえ」することを表します。catにしてみれば、標準入力がすりかえられたことなどおかまいなしですから、最初の例と全く同じように標準入力から読み込んで標準出力へ出力しているだけです。
標準出力と標準入力をつなげる
次にこれはどうでしょうか?
$ echo "Hello World" | cat
縦棒(|)は「パイプ」と呼ばれ、1つめのコマンド(パイプの左側)の「標準出力」を2つ目のコマンドの「標準入力」に渡す働きをします。1つめのプログラム(またはコマンド)の標準出力をOS内部の「パイプ」というファイルに保存し、2つめのプログラムにそのファイルを渡している、という理解をするとわかりやすいかもしれません。ここでも2つのプログラムとも、パイプを意識することは決してありません。単に標準出力、標準入力に対して読み書きしているだけです。
この「すりかえ」ができることはシェルスクリプトの基本となります。ほかのプログラム言語やスクリプトと違い、シェルスクリプトとは元々存在するプログラムを組み合わせて、リダイレクトなどを駆使し、ある機能を組みあげていくものなのです。
では、それはperlなどのスクリプト言語とどう違うのでしょうか?。Perlはすばらしいスクリプト言語で、世界でいちばん使われているといっても過言ではありません。これまでの例でやったようなことは当然すべてPerlでも実現できます。ただ、Perlでなにかをやりたいときは、ほとんどすべてを「Perlの言葉」で表現しなくてはなりません(もちろんPerlでも外部コマンドの起動はできます)。
一方シェルスクリプトでは、それぞれ独立したプログラムを組み合わせていきます。標準入出力型のプログラムであれば、ここで紹介したリダイレクトとパイプを使って自由に組み合わせることができますし、そうでなくても、そのコマンドを実行するだけのことも当然できます。
もしあなたが新しい標準入出力型のプログラムを作ったとしましょう。そのコマンドにはまだ並べ替え機能がなかったとします。その場合であっても、シェルスクリプトであれば、以下のように簡単に、結果を並べ替えて表示する機能を実現できます。
$ my_program | sort
今後シェルスクリプトを勉強していく中では、つねにこの「すりかえ」機能を頭にいれながらすすめていくことにしましょう。
Copyright © ITmedia, Inc. All Rights Reserved.