パイプでつなぐ
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
開始行:
[[FrontPage]]
2008/08/12からのアクセス回数 &counter;
#contents
* はじめに [#a4aced3f]
Amazonの協調フィルタリングで話題になった「集合知(collecti...
そこにはこれが正解というものはなく、仮説と検証のサイクル...
日々研究開発されています。
仮説と検証のサイクルでは、生のデータから分析可能なデータ...
その処理内容は様々です。このため、データ加工では短期間に...
業務です。
そんな時に役にたつのが、これからご紹介します
- UNIXのコマンドをパイプでつないで、データを加工する方法...
- シェルスクリプトを使って、パイプライン処理によりきめ細...
です。
シェルには、sh, ksh, bash, csh, tcsh等いろいろな種類があ...
((bashでも以下に紹介する機能は使用できます))
* パイプでつなぐ [#g6c751b8]
1968年、UNIXの産みの親であるKen ThompsonとDennis Ritchie...
と呼ばれる大型のオペレーティングシステムを開発していた頃、
「インタラクティブで便利なコンピュータサービス」が欲しい...
彼らは、「ベル研の文書処理システムを作る」と言って予算を...
(システムメモリ16Kバイト、ユーザメモリ8Kバイト、ハードデ...
という現在のマイコンチップ以下のハードウェアを購入し、
その上に現在のUNIXシステムのコマンド群とroffと呼ばれる文...
今では、Linuxの普及により誰でもUNIXの環境を持つことができ...
この機会に、UNIXの「パイプでつなぐ」を試してみてください。
** UNIXの3大発明 [#i61b3a24]
ここでは、「パイプでつなく」をテーマにUNIXの偉大な発明の...
- fork : 子プロセスの生成
- pipe : パイプ
- dup : リダイレクト
について、その仕組みを例題を使いながら、わかりやすく説明...
*** パイプを使った処理の例 [#a948992b]
- 単語処理の例
パイプを使った処理の例として、「tr」コマンドのmanページに...
#pre{{
$ man tr | tr -cs 'A-Za-z' '\n' | sort -u | wc -l
492
}}
このようにUNIXのコマンドをパイプでつなぐことによって簡単...
- リアルタイムの例
「単語処理の例」は、別にパイプを使わなくてもファイルを使...
パイプがファイルと決定的に異なる点は、そのリアルタイム性...
図は、マウスイベントを別Windowにプロットする例です。
&ref(demo_pipeline.jpg);
このデモでは、xevコマンドとEvent2Plot, Graphのシェルスク...
#pre{{
$ xev | Event2Plot | Graph
}}
** パイプのつなぎ方 [#pfaae892]
pipeシステムコールは、本当に不思議な関数です。自分が出力...
マニュアルの説明を読んだだけでpipeシステムコールの使い方...
- 最初にパイプを生成します。
&ref(pipe.jpg);
- 次にforkシステムコールを使って、子プロセスを生成します
&ref(fork.jpg);
- 親のin、子のoutをクローズします
&ref(close.jpg);
- 親の1番のファイル記述子(stdout)をクローズし、dupします...
&ref(dup.jpg);
** パイプを実現する3つのシステムコール [#wece4043]
パイプをつなぐときに必要なシステムコール
- fork
- pipe
- dup
について、おさらいも含めて説明します。
*** fork システムコールのおさらい [#mcf73ea6]
forkシステムコールの仕様を簡単に書くと、
#pre{{
呼び出し形式
#include <unistd.h>
pid_t
fork(void);
機能
呼び出し元のプロセスをコピーして、新しいプロセスを生成...
戻り値
成功すると、子プロセスには0が返され、
親プロセスには、子プロセスのプロセスIDが返されます。fork...
}}
です。
forkでは、
- 子プロセスは、変数、ファイルなどのリソースを親プロセス...
- 親と子プロセスは、forkのリターン値で別の処理に切り分け...
ます。
簡単なプログラムで、上記の仕様を確認してみましょう。
#pre{{
#include <stdio.h>
#include <unistd.h>
main()
{
int pid;
if ((pid = fork()) == -1) {
fprintf(stderr, "can't fork\n");
}
else if (pid == 0) {
// child
fprintf(stdout, "this is a child process\n");
fprintf(stderr, "pid(child)=%d\n", pid);
}
else {
// parent
fprintf(stdout, "this is a parent process\n");
fprintf(stderr, "pid(parent)=%d\n", pid);
}
}
}}
以下のようにコンパイルして、実行すると
#pre{{
$ cc -o ex1 ex1.c
$ ex1
this is a parent process
pid(parent)=6556
this is a child process
pid(child)=0
}}
と出力され、親と子プロセスの分岐とpidの値が正しくセットさ...
次に、標準出力(1)とエラー出力(2)をリダイレクトでフ...
shでは n> のようにリダイレクトするファイル記述番号を指定...
- 1>1.out の指定により、標準出力をファイル(1.out)にリダイ...
- 2>2.out の指定により、標準エラー出力をファイル(2.out)に...
しています。
#pre{{
$ ex1 1>1.out 2>2.out
$ more *.out
<< 1.outの内容 >>
this is a parent process
this is a child process
<< 2.outの内容 >>
pid(parent)=6570
pid(child)=0
}}
と、親と子プロセスが同じファイルに書き込み、ファイルの共...
*** pipe システムコールの使い方 [#ka7a7042]
pipeシステムコールの仕様を簡単に書くと、
#pre{{
呼び出し形式
#include <unistd.h>
int
pipe(int fildes[2]);
機能
パイプを生成し、ペアのファイル記述子を割り当てます。1...
2番目(fildes[1])が書き込み用となります。
fildes[1]に書き込まれたデータは、fildes[0]から読み込ま...
これにより、あるプログラムの出力を他のプログラムの入力に...
読み込みまたは書き込みのファイル記述子のいずれかがクロ...
widowedとなったパイプに書き込むと書き込みプロセスはSIGPIP...
widowedにすることで、読み込みプロセスにEnd-Of-Fileを送る...
読み手がパイプのデータをすべて読み込んだ後やwidowedになっ...
0が返されます。
戻り値
成功すると0を返し、そうでない場合には-1を返します。
}}
パイプは、
- 単一方向のデータフローを持つバッファです
- バッファが満杯になった場合には、書き込みプロセスはスリ...
- 読み込みプロセスは、バッファに書き込まれるまでスリープ...
- 書き込み側がクローズするとパイプからの読み込みでは0(E...
- 読み手側でクローズされたパイプに書き込もうとするとSIGPI...
のように機能します。
簡単なプログラムで、上記の仕様を確認します。
#pre{{
// ex2.c : pipe example
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define BUFSIZE (128)
main()
{
int pid;
int fd[2];
// create a pipe.
if (pipe(fd) == -1) {
fprintf(stderr, "Can't create pipe\n");
exit(1);
}
printf("fd[0]=%d, fd[1]=%d\n", fd[0], fd[1]);
if ((pid = fork()) == 0) {
// child
char buf[BUFSIZE];
int len;
// close pipe out.
close(fd[1]);
// read message.
len = read(fd[0], buf, BUFSIZE);
printf("len=%d, buf=%s", len, buf);
len = read(fd[0], buf, BUFSIZE);
close(fd[0]);
fprintf(stderr, "child finishied. len=%d\n", len);
}
else {
// parent
char buf[] = "hello world\n";
// close pipe in.
close(fd[0]);
// write message.
write(fd[1], buf, sizeof(buf));
close(fd[1]);
fprintf(stderr, "parent finishied\n");
}
}
}}
プログラムを実行すると
#pre{{
$ ex2
fd[0]=3, fd[1]=4
parent finishied
len=13, buf=hello world
child finishied. len=0
}}
と出力され、
- pipeシステムコールによってfdに3, 4のファイル記述子が割...
- 親プロセスがhello world\nを書き込み、書き込み用ファイル...
- 子プロセスが、hello world\nを読み込み、次の読み込みをす...
ます。((最初の読み込みで、len=13となっているのは、文字列...
*** dup システムコールの使い方 [#ic4ba39d]
同様にdupシステムコールの仕様を簡単に書くと、
#pre{{
呼び出し形式
#include <unistd.h>
int
dup(int fildes);
機能
既存のファイル記述子を複製し、新しく生成されたファイル...
プロセスには、getdtablesize()で返される大きさのファイル記...
新しいファイル記述子には、未使用の内もっとも小さい値が返...
戻り値
正常終了の場合、0以上の値が返され、そうでない場合には-1...
}}
となります。
UNIXのファイル記述子の割り当てルールは、
- 新しいファイル記述子には、未使用の内もっとも小さい値が...
ので、特定の値のファイル記述子を割り当てたい時には、
- dupの直前にそのファイル記述子をクローズする
ことで実現できます。
リダイレクトやパイプは、この単純なルールを使って実現され...
dupの動作を確認するために、以下のサンプルプログラムを作成...
#pre{{
// ex3.c : dup example
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define BUFSIZE (128)
main()
{
int pid;
int fd[2];
// create a pipe.
if (pipe(fd) == -1) {
fprintf(stderr, "can't create pipe\n");
exit(1);
}
if ((pid = fork()) == 0) {
// child
int in;
// close pipe out.
close(fd[1]);
// close stdin.
close(0);
in = dup(fd[0]);
close(fd[0]);
fprintf(stderr, "in=%d\n", in);
fprintf(stderr, "child : exec wc\n");
execl("/usr/bin/wc", "wc", 0);
}
else {
// parent
int out;
// close pipe in.
close(fd[0]);
// close stdout.
close(1);
out = dup(fd[1]);
close(fd[1]);
fprintf(stderr, "out=%d\n", out);
fprintf(stderr, "parent: exec echo 'hello world'\n");
execl("/bin/echo", "echo", "hello world", 0);
}
}
}}
プログラムを実行すると、
#pre{{
$ ex3
out=1
parent: exec echo 'hello world'
in=0
child : exec wc
1 2 12
}}
と出力されます。
((execlは、引数で指定されたコマンドを実行します。実行プロ...
- 親プロセスのパイプの出力用ファイル記述子は、1(標準出...
- echo 'hello world'を実行します
- 子プロセスのパイプの入力用ファイル記述子は、0(標準入...
- echoの結果'hello world'を標準入力から読み込みwcを実行し...
wcの出力結果は、
#pre{{
$ echo 'hello world' | wc
1 2 12
}}
と同じであり、親プロセスと子プロセスの間でパイプが正常に...
** パイプをつなぐサンプル [#hbf674b0]
パイプのつなぎ方が分かったところで、最初に紹介したパイプ...
パイプライン処理の特徴として、以下のことが挙げられます。
- 確認しながら、パイプをつなぐ
- 複雑な処理は、単純な処理をパイプでつなぐ
- 途中経過を確認しながら、パイプでつなぐ
*** 確認しながら、パイプをつなぐ [#mb5f3fb6]
いくつかの処理を確認しながら、パイプを増やしていくのがパ...
最初のサンプルで、man trの結果から単語を切り出す部分をみ...
trの処理は、
- -cオプションは、指定されたパターン以外の文字列を置換の...
- -sオプションは、同じ文字に置換された場合、それを1個にま...
この結果、英字以外の文字列は、改行(\n)1個に置き換わり、...
実際にその処理をみてみましょう。
#pre{{
$ man tr | tr -cs 'A-Za-z' '\n'
TR
BSD
General
Commands
Manual
TR
途中省略
Std
POSIX
standard
BSD
July
BSD
}}
と出力されます。
単語が抽出できたことを確認し、同じ単語を1つにまとめます。
sortコマンドの
- -uオプションは、入力をソートするとき重複する行を1つにま...
先ほどのパイプラインに、sort -uを追加して重複を取り除きま...
#pre{{
$ man tr | tr -cs 'A-Za-z' '\n' | sort -u
A
ALL
AM
AN
AR
ASCII
AT
Additionally
Any
As
B
BI
BSD
以下省略
}}
と出力され、英単語とは思えない1または2文字の単語が含まれ...
これは、manページが、
&ref(man.jpg);
のように文書整形コマンドによって、強調文字、アンダーライ...
これをターミナルに出力するために、制御コードが付加されて...
#pre{{
$ man tr | od -c | more
0000000 T R ( 1 ) ...
0000020 B S ...
0000040 n e r a l C o m m a n ...
0000060 a n u a l ...
0000100 T R ( ...
0000120 N \b N A \b A M \b M E \b E ...
0000140 t \b t r \b r - - ...
0000160 s l a t e c h a r a c ...
0000200 \n \n S \b S Y \b Y N \b N O ...
0000220 P S \b S I \b I S \b S \n ...
0000240 t \b t r \b r [ - \b - C ...
0000260 c s \b s u \b u ] _ \b s ...
0000300 \b r _ \b i _ \b n _ \b g _ ...
0000320 \b s _ \b t _ \b r _ \b i _ ...
0000340 g _ \b 2 \n t \b ...
以下省略
}}
のように強調文字では、
- N \b Nのように文字を表示し、バックスペースで戻し、再度...
アンダラインでは、
- _ \b s のように先に_を表示し、バックスペースで戻し...
しているのです。
正しい結果がでるようにするには、バックスペースとその前の...
そこで、sedコマンドを追加します。(以下のコマンドで^Hは、...
#pre{{
$ man tr | sed -e 's/.^H//g' | tr -cs 'A-Za-z' '\n' | sor...
A
ALL
ASCII
Additionally
Any
As
BSD
C
COLLATE
COMPATIBILITY
CTYPE
}}
と正しく単語が抽出できました。
確認が終わった後で、moreをwc -lに変えると
#pre{{
$ man tr | sed -e 's/.^H//g' | tr -cs 'A-Za-z' '\n' | sor...
436
}}
正しい単語の数を得ることができました。
このように一つずつ確認しながらパイプをつなぐことによって...
*** 複雑な処理は、単純な処理をパイプでつなぐ [#t78619d9]
例えば、WebのアクセスログからGoogleの検索でブログにアクセ...
- Googleの検索からアクセスされたログには、http://www.goog...
- PukiWikiを使ったブログには、index.phpという文字が含まれ...
この条件を使って
#pre{{
$ grep http://www.google.co.jp/search /var/log/apache2/ac...
218.228.8.55 - - [09/Aug/2009:06:43:41 +0900] "GET /~take...
%2F%E6%9C%80%E5%88%9D%E3%81%AE%E4%B8%80%E6%AD%A9 HTTP/1.1...
.google.co.jp/search?q=avr+isp&hl=ja&lr=lang_ja&client=fi...
:ja:official&start=50&sa=N" "Mozilla/5.0 (Windows; U; Win...
.1.2) Gecko/20090729 Firefox/3.5.2"
以下省略
}}
のように簡単に目的のログだけを抽出することができます。
もちろん、grepの正規表現を使って1つのgrepで検索することも...
AかつBのような時、
$ grep A | grep B
のようにパイプでつなぐのが簡単かつ確実です。特に急いでい...
*** 途中経過を確認しながら、パイプでつなぐ [#i6e9f386]
2つ目のサンプル、
#pre{{
xev | Event2Plot | Graph
}}
では、Event2PlotとGraphというシェルスクリプトを使いました。
引数やオプションが多い場合には、それをシェルスクリプトに...
Graphは、graphコマンド((GNU plotutils パッケージに含まれ...
#pre{{
#! /bin/sh
graph -T X -x 0 100 -y 0 100 -m 0 -S 5
}}
- 1行目の#! は、使用するシェル(コマンド・インタープリタ...
- graphのコマンドとオプションをセットしています
エディタでGraphを入力した後に、シェルで実行できるように実...
#pre{{
$ chmod +x Graph
}}
Graphを起動して、X,Yの座標をブランク区切りで入力すると、...
#pre{{
$ Graph
10 30
50 50
70 20
}}
これでGraphの完成です。((Macの場合MacPortのgraphはXウィン...
次にxevコマンドを実行し、太い線の正方形の中で、マウスを動...
#pre{{
MotionNotify event, serial 23, synthetic NO, window 0xa00...
root 0x1fd, subw 0x0, time 236035801, (68,77), root:(...
state 0x0, is_hint 0, same_screen YES
MotionNotify event, serial 23, synthetic NO, window 0xa00...
root 0x1fd, subw 0x0, time 236035818, (66,106), root:...
state 0x0, is_hint 0, same_screen YES
}}
のようにMotionNotify eventの次の行に(68,77)のようにマウス...
これをawkを使ってX Y座標に変換しているのが、Event2Plotで...
先ほどと同様に
#pre{{
#! /bin/sh
awk -F, '
/MotionNotify/ {
getline;
gsub(/\(/, "", $4);
gsub(/\)/, "", $5);
printf("%d %d\n", $4, 100 - $5);
}'
}}
をEvent2Plotに入力して、
chmod +x Event2Plot
を実行してください。
xevとEvent2Plotをパイプでつないで、
#pre{{
$ xev | Event2Plot
6 59
27 85
37 82
42 81
43 80
42 80
41 79
40 79
39 79
38 79
以下省略
}}
最初、ある程度マウスを動かすと画面に座標が表示されます。(...
ここまで、確認できたらGraphと連結してみましょう。
#pre{{
$ xev | Event2Plot | Graph
}}
無事、最初の図と同じように出力されます。
グラフにプロットされるデータの途中経過がどのようになって...
このような場合には、teeコマンドで途中のデータをファイルに...
((tailコマンドの-fオプションによって、ファイルに追加され...
#pre{{
$ xev | Event2Plot | tee /tmp/1 | Graph &
$ tail -f /tmp/1
58 50
65 54
73 65
72 71
62 80
57 81
途中省略
終了するには、Ctrl-Cを入力してください
}}
リアルタイムとまではいきませんが、途中経過が表示されます。
** その他のパイプ(pipe)の使われ方 [#j46d5541]
*** グルーピング [#r0f82b15]
初期のUNIXでは、ファイルシステムを跨ぐファイルのコピーに...
そこで、tarコマンドとパイプを使って以下のようにコピーしま...
#pre{{
$ tar cf - . | (cd /mnt/bak; tar -xf -)
}}
これまでは、()で括ってパイプを使ったことはありませんで...
処理で、()で括ったコマンドは入出力が共通になります。
((実装上は別のシェルプロセスを起動し、コマンドを実行する...
*** バッククォート(`) [#p3d27cbb]
バッククォートで括られたコマンドの出力をパイプでつないで...
例えば、変数NOWに今の時刻をセットしたい場合に、
#pre{{
$ NOW=`date`
$ echo $NOW
Wed Aug 12 20:15:11 JST 2009
}}
とすると、変数NOWにdateコマンドの実行結果がセットされ、ec...
また、あるコマンドの実行結果を別のコマンドの引数にしたい...
例)変数iの値に1を足した値を表示する場合(シェル内の四則...
#pre{{
$ i=1
$ echo `expr $i + 1`
2
}}
これを使えば、変数の値を変える場合には、
#pre{{
$ i=`expr $i + 1`
$ echo $i
2
}}
とすればよいことが分かります。
*** ヒアドキュメント(<<EOF) [#u89a4f77]
シェルスクリプト内に記述したドキュメントにシェル変数の置...
ヒアドキュメントは、テンプレート(ひな形)として使用され...
sh, awk等のプログラムをシェルスクリプト内で生成し、実行す...
簡単な例を使ってヒアドキュメントの変数置換を確かめてみま...
#pre{{
$ NOW=`date`
$ cat <<EOF
> What time is it now?
> It's $NOW.
> EOF
What time is it now?
It's Thu Aug 13 17:56:49 JST 2009.
}}
- 変数NOWに現在時刻がセットされ、
- <<EOFがヒアドキュメントの開始です。<<の後には任意の文字...
- スクリプトではなく、端末などで実行すると第2プロンプトの...
- 出力は、期待通りNOWが現在時刻に置換されています
awkではshと同様に変数を$を使って参照するため、ヒアドキュ...
そのような場合には、<<\EOFのように<<の後に\を付けます。
#pre{{
$ cat <<\EOF
> What time is it now?
> It's $NOW.
> EOF
What time is it now?
It's $NOW.
}}
* 簡単シェルスクリプト [#vf5f7efd]
パイプをつないでデータを加工する方法がわかったところで、...
まずは、シェルのおさらいからはじめましょう。ここではsh, b...
** おさらい [#b051ca79]
ここでは、シェルのおさらいも兼ねて、
- リダイレクト
- ファイル名展開
- シェル変数と環境変数
- 特殊変数
- クォーティング
について、例題を交えながら説明します。
*** リダイレクト [#l97cf876]
UNIXのCプログラムでは、main関数がプログラムの開始部分(...
シェルがプログラムを起動する前に、標準入力、標準出力、標...
リダイレクトの仕組みは、dupシステムコールの例題でも示した...
- 新しい接続先のオープン
- 切り替える入出力のクローズ
- dupシステムコールを使った入出力の付け替え
によって実現されています。
主なリダイレクト指定方法は、
- < を使った標準入力の指定
- > を使った標準出力の指定
- >> を使ったファイルへの追加出力指定
があります。
では、実際にリダイレクトを使って簡単なファイルを作成しま...
#pre{{
$ echo test > /tmp/test.out
$ cat /tmp/test.out
test
$ echo more test >> /tmp/test.out
$ cat /tmp/test.out
test
more test
$ wc </tmp/test.out
2 3 15
}}
存在しないファイルに>>を使った場合、新たにファイルを作成...
また、すでにファイルが存在する場合、>を使ったリダイレクト...
((ファイルのrw属性は変更されません))
*** ファイル展開 [#k8dc7d65]
シェルでは、メタ記号を使って複数のファイル名に展開するこ...
メタ記号の一覧を以下に示します。
|メタ記号|説明|
|?|任意の一文字にマッチする|
|[abef]|[]で囲まれた文字のいずれかの一文字にマッチする|
|[a-z]|a-zの間に含まれる一文字にマッチする|
|[^abc]|[の直後に^が指定されるとそれ以降に指定した文字列...
|*|任意の文字列にマッチする|
ファイル展開はディレクトリ内のファイルに対して行われます。
例えば、ファイルが.bakで終わるすべてのファイルを削除する...
#pre{{
$ rm *.bak
}}
のように指定します。
サンプルソースのディレクトリには、以下のファイルがありま...
#pre{{
$ ls -a
. 2.out Graph ex2 images
.. Event2Plot Makefile ex2.c range
.cproject Ex5.sh Pipeline.txt ex3 range.c
.project Ex6.sh Tgrep.sh ex3.c tgrep
.settings Ex7.sh ex1 ex4 tgrep.c
1.out Ex8.sh ex1.c ex4.c
}}
しかし、echo *では
#pre{{
$ echo *
1.out 2.out Event2Plot Ex5.sh Ex6.sh Ex7.sh Ex8.sh Graph ...
Tgrep.sh ex1 ex1.c ex2 ex2.c ex3 ex3.c ex4 ex4.c images r...
tgrep.c
}}
となり、.で始まるファイルが含まれていません。
UNIXでは、.で始まるファイル、ディレクトリはメタ記号の展開...
それでは、.*とするとどうなるでしょう。
#pre{{
$ echo .*
. .. .cproject .project .settings
}}
.で始まるファイルの他に、.(カレントディレクトリ)と..(...
メタ記号の展開で間違ってカレントディレクトリや親ディレク...
ファイル展開では.で始まるファイルはメタ記号では展開されま...
この性質を利用して.profile等各種コマンドのユーザ設定ファ...
それでは、.ではじまるファイル名を指定するには、以下のよう...
#pre{{
$ echo .[^.]*
.cproject .project .settings
}}
*** シェル変数 [#hf55402a]
シェルには、同一シェル内だけで有効なシェル変数と子プロセ...
シェル変数に値をセットするには、変数名と値の間に空白を入...
代入する値の中に空白が含まれる場合シングルクォート(')また...
シェル変数名にはアルファベット、数字、アンダースコア(_)が...
慣例としてループの添え字を除いて、変数名には大文字が多く...
簡単なシェル変数の例を以下に示します。
#pre{{
$ HOST_NAME=IES00
$ IN_FILE=""
$ PS="ps -ef"
}}
順番に見ていきましょう。
- シェル変数HOST_NAMEに"IES00"をセットします
- シェル変数IN_FILEに空文字をセットします
- シェル変数PSに"ps -ef"をセットします
変数を参照するには、変数名の前に$を付けます。
変数は、空白や.などの変数名に使えない文字を区切りとします...
ダブルクォート"や${}で括って参照します。
#pre{{
$ $PS
UID PID PPID C STIME TTY TIME CMD
0 1 0 0 0:17.04 ?? 0:24.09 /sbin/...
0 15 1 0 0:01.72 ?? 0:01.83 /usr/l...
0 16 1 0 0:26.06 ?? 0:41.26 /usr/s...
以下省略
$ VAR=hello
$ echo $VAR
hello
$ echo ${VAR}_again
hello_again
$ echo using"$VAR"
usinghello
}}
また、シェル変数には配列指定も可能であり、変数名[添え字]...
すべての配列要素を参照する場合には、${配列名[@]}とし、要...
#pre{{
$ LINE[0]=foo
$ LINE[1]=bar
$ echo ${LINE[@]}
foo bar
$ echo ${#LINE[@]}
2
}}
変数をクリアするには、unset 変数名を使います
#pre{{
$ unset LINE
$ echo ${LINE[@]}
}}
*** 環境変数 [#t79199e5]
つぎに環境変数について説明します。環境変数を定義するには...
すでに定義されているシェル変数を環境変数に切り替えるには...
#pre{{
$ export VAR
}}
新規に環境変数を定義する場合には、exportの後に変数名=値を...
#pre{{
$ export VAR=hello
}}
どうやってシェル変数と環境変数が使い分けられているのか、...
Cのmain関数の宣言形式に、
#pre{{
#include <stdio.h>
int main(int argc, char * argv[], char * env[])
}}
というのがありますが、最後のenvが環境変数の配列をシェルか...
簡単なCのサンプルを使って環境変数がシェルから引き継がれる...
#pre{{
// ex4.c : env example
#include <stdio.h>
int main(int argc, char* argv[], char *env[])
{
int i;
for (i=0 ; env[i] != NULL ; ++i) {
printf("%s ", env[i]);
}
printf("\n");
}
}}
コンパイルし、実行します。((makeコマンドですべてのサンプ...
#pre{{
$ cc -o ex4 ex4.c
$ ex4
MANPATH=/opt/local/share/man:/usr/share/man:/usr/local/sh...
TERM_PROGRAM=Apple_Terminal TERM=xterm-color SHELL=/bin/b...
CATALINA_HOME=/Users/take/local/tomcat
TMPDIR=/var/folders/Xo/XoxQsiGT2RWIck+BYuxhKU+++TI/-Tmp-/
Apple_PubSub_Socket_Render=/tmp/launch-wSems0/Render TERM...
以下省略
}}
この結果をenvコマンドと比べると
#pre{{
$ echo `env`
MANPATH=/opt/local/share/man:/usr/share/man:/usr/local/sh...
TERM_PROGRAM=Apple_Terminal TERM=xterm-color SHELL=/bin/b...
CATALINA_HOME=/Users/take/local/tomcat
TMPDIR=/var/folders/Xo/XoxQsiGT2RWIck+BYuxhKU+++TI/-Tmp-/
Apple_PubSub_Socket_Render=/tmp/launch-wSems0/Render TERM...
以下省略
}}
同じ結果となり、環境変数がサンプルプログラムに引き渡され...
*** 特殊変数 [#n0f58ed5]
シェルには、シェル変数、環境変数の他に、
- 位置変数
- 特殊変数
- 特殊置換
の変数操作があります。
:位置変数 | シェルやシェル関数への引数を$0, $1, $2, ...$n...
:特殊変数 | プロセスID、引数の個数などの特殊な情報を参...
-- $* : $1から始まるすべての位置変数を展開します
-- $0 : 実行コマンドの名前を返します
-- $# : 位置変数の数を返します
-- $$ : 実行中のプロセスIDを返します
-- $? : 直前のコマンドの終了ステータスを返します
: 特殊置換 | ${変数:=省略値}の形式で指定し、変数が定義さ...
*** クォーティング [#w5202abd]
シェルで、空白で区切られた文字列を1つの引数にしたり、置換...
このようなときには、バックスラッシュ\やシングルクォート'...
#pre{{
$ echo \$
$
# echo '* ?'
* ?
}}
シェルのクォーティングルールをきちんと説明した書物は少な...
|>|>|>|>|>|>|CENTER:メタ文字|h
||'|"|`|\|$|*|
|'|t|n|n|n|n|n|
|"|n|t|y|y|y|n|
|`|n|n|t|y|n|n|
ここで、
- t : 終端文字
- y : シェルによって解釈される
- n : シェルによって解釈されず、そのまま渡される
ことを意味します。
ただし、`で囲まれた$は、シェル変数が置換されるので、yの間...
#pre{{
$ HELLO="echo hello"
$ echo `$HELLO`
hello
}}
以上のことから、シェルのクォーティングルールを簡単に説明...
- ' は、すべての特殊文字を無効にします
- "は、変数置換、\を有効にし、`によって返された結果を1つ...
- `は、変数置換、\を有効にします
となります。
** おきまりのパターン [#a20dec8e]
シェルは、コマンドを組み合わせるだけではなく、その使い方...
ここでは、単純な検索するプログラムtgrepを複数ファイルの検...
- 引数のチェックの追加
- 各ファイルに対する繰り返し処理の追加
tgrepは、
- 標準入力から1行文の文字列を読み込み
- 第一引数に指定されたパターンを含んでいたら、その行を標...
をファイルの終わりまで繰り返す、簡単なプログラムです。
#pre{{
#include <stdio.h>
#include <string.h>
#define BUFSIZE (512)
main(int argc, char *argv[])
{
char* pattern = *(++argv);
char line[BUFSIZE];
while(fgets(line, BUFSIZE, stdin) != NULL) {
if (strstr(line, pattern) != NULL)
printf(line);
}
}
}}
これから作成するシェルスクリプトの名称をTgrep.shとします。
主な機能は、
- 引数が1個の場合には、標準入力から検索し引数で指定され...
- 引数が2個以上の場合には、第2引数以降の各ファイルに対し...
*** 引数チェック [#j066a3a2]
引数のチェックでよく使用されるシェルの文法がcase文です。
シェルのcase文には正規表現を使うことができるので、C言語...
まず、引数の数を調べるところは、特殊シェル変数の$#で引数...
case文で処理を振り分けます。各条件で実行する処理は、2個...
#pre{{
#! /bin/sh
case $# in
1)
echo pattern: $1 ;;
[2-9]) echo pattern: $1; shift; echo files: $* ;;
esac
}}
ここで、shiftコマンドは、位置引数を1個左にシフトコマンド...
まずは、このようにシェルスクリプトが正しい動作をするかech...
#pre{{
$ Tgrep.sh 1
pattern: 1
$ Tgrep.sh 1 2 3 4
pattern: 1
files: 2 3 4
}}
うまく動作しているように見えますが、これでは10個以上の引...
そこで、引数のパターンを0個、1個、それ以外に変えてみま...
#pre{{
#! /bin/sh
case $# in
0) echo invalid argument.; exit 1;;
1) echo pattern: $1 ;;
*) echo pattern: $1; shift; echo files: $* ;;
esac
}}
引数が10個以上でも正常に動作するか、引数の個数を変えて確...
#pre{{
$ Tgrep.sh 1 2 3 4 5 6 7 8 9 10 11
pattern: 1
files: 2 3 4 5 6 7 8 9 10 11
$ Tgrep.sh 1
pattern: 1
$ Tgrep.sh
invalid argument.
}}
すべてうまくいきました。これで、引数チェックは完成です。
*** 各ファイルに対する繰り返し [#scf2ecb0]
次は、検索するファイル数が複数になったときの処理を追加し...
同じ処理を何回か繰り返す場合には、for文を使用します。
引数のテストではechoを使っていましたが、これからはtgrepに...
2個以上のファイルに対する処理にfor文を入れてみましょう。
#pre{{
#! /bin/sh
case $# in
0) echo invalid argument.; exit 1;;
1) tgrep $1 ;;
*) PATTERN=$1; shift;
for i in $*
do
tgrep $PATTERN < $i;
done ;;
esac
}}
できたスクリプトを動かしてみます。
#pre{{
$ Tgrep.sh BUFSIZE *.c
#define BUFSIZE (128)
char buf[BUFSIZE];
len = read(fd[0], buf, BUFSIZE);
len = read(fd[0], buf, BUFSIZE);
#define BUFSIZE (128)
#define BUFSIZE (512)
char line[BUFSIZE];
while(fgets(line, BUFSIZE, stdin) != NULL) {
}}
うまく検索しているみたいですが、これではどのファイルにパ...
そこで、Tgrep.shの出力行の先頭にファイル名を追加すること...
出力行の処理には、while文とread文を使用します。出力行をre...
それをファイル名と一緒にechoコマンドで出力します。
#pre{{
#! /bin/sh
case $# in
0) echo invalid argument.; exit 1;;
1) tgrep $1 ;;
*) PATTERN=$1; shift;
for i in $*
do
tgrep $PATTERN < $i | while read LINE
do
echo $i: $LINE
done
done ;;
esac
}}
出力結果は、以下のようになります。
#pre{{
$ Tgrep.sh BUFSIZE *.c
ex2.c: #define BUFSIZE (128)
ex2.c: char buf[BUFSIZE];
ex2.c: len = read(fd[0], buf, BUFSIZE);
ex2.c: len = read(fd[0], buf, BUFSIZE);
ex3.c: #define BUFSIZE (128)
tgrep.c: #define BUFSIZE (512)
tgrep.c: char line[BUFSIZE];
tgrep.c: while(fgets(line, BUFSIZE, stdin) != NULL) {
}}
少しずつ動作を確認しながら、機能を拡張することで難なく目...
このようにわずか13行のシェルスクリプトで単純な検索プロ...
** 制御コマンド [#zd2f10a8]
詳しい説明もせずにfor文、case文、while文を使ってしまった...
シェル制御コマンドのおもしろいところは、ifならfi、caseはe...
*** for文 [#ua1c3ffc]
for 文は、次の形式で使用します。
#pre{{
for name in word_list
do
block_stmt;
done
}}
- word_listには空白で区切った単語の列を指定し、指定された...
- doとdoneの間のblock_stmtが実行されます
- in word_listが省略された場合には、すべての引数($*と同...
簡単な例で動作をみてましょう。
#pre{{
# 10回ループを回す場合の簡単な例
for i in 1 2 3 4 5 6 7 8 9 10
do
echo $i; # ループカウンタの値を出力する
done
}}
端末からこれを実行すると、
#pre{{
$ for i in 1 2 3 4 5 6 7 8 9 10
> do
> echo $i;
> done
1
2
3
4
5
6
7
8
9
10
}}
doneが入力されるまでは、第2プロンプトの> が出力され、その...
シェルスクリプトでは、Cのfor文のような指定回数だけループ...
そこで、pythonのrangeと同様のプログラムを作成し、for文で...
rangeの仕様は、以下の通りとします
- 引数が1個の場合、0以上、指定された値より小さい整数を出...
- 引数が2個の場合、第1引数を初期値とし、初期から第2引数よ...
- 引数が3個の場合、第1引数を初期値、第2引数を最大値、第3...
range.cは、以下のようになります。
#pre{{
// range.c : range program.
#include <stdio.h>
main(int argc, char* argv[])
{
int init = 0;
int max = 0;
int step = 1;
int i;
if (argc == 2) {
max = atoi(argv[1]);
}
else if (argc == 3) {
init = atoi(argv[1]);
max = atoi(argv[2]);
}
else if (argc == 4) {
init = atoi(argv[1]);
max = atoi(argv[2]);
step = atoi(argv[3]);
}
if (step > 0)
for (i = init; i < max; i += step)
printf("%d ", i);
else
for (i = init; i > max; i += step)
printf("%d ", i);
printf("\n");
}
}}
動作を確認してみましょう。
#pre{{
$ range 1 10
1 2 3 4 5 6 7 8 9
$ range -1 1
-1 0
$ range 10 2 -1
10 9 8 7 6 5 4 3
$ range 3
0 1 2
}}
となります。
それでは、rangeを使って与えられた数をカウントダウンするス...
((^Gは、Ctrl-vのあとにCtrl-gを入力してください))
#pre{{
# /bin/sh
for i in `range $1 0 -1`
do
echo $i ^G; sleep 1;
done
}}
では、実行権限を与えて、5からカウントダウンしてみましょ...
#pre{{
$ chmod +x Ex5.sh
$ Ex5.sh 5
5
4
3
2
1
}}
1秒ごとにブザーが鳴って、5から1までカウントダウンしまし...
*** case文 [#ta6d2577]
case文は、パターンに合致した実行文を処理します。
#pre{{
case word in
pattern1) block_stmt ;; # pattern1に合致した場合、
....
pattern2) block_stmt ;; # pattern2に合致した場合、
*) block_stmt ;; # 上記のいずれでもない場合、
esac
}}
処理の終わりには、;ではなく;;を指定します。また、*)とする...
pattern1, pattern2のパターン指定には、shの正規表現を使う...
通常、case文は引数のチェックに使用され、その場合には位置...
UNIXには、オプションを処理する便利なコマンドgetoptがあり...
#pre{{
$ getopt cf: -cf 1 2 3
-c -f 1 -- 2 3
}}
となり、
- -c -f 1のようにオプションまたオプションとその値のペアに...
- -- でオプションの終わりを表し
- それ以降がコマンドへの引数
に変換してくれるので、シェルスクリプトの引数処理がとても...
getoptを使った例として、
コマンドEx6.shのオプションがvfのいずれかで、fオプションに...
#pre{{
#! /bin/sh
set -- `getopt cf: $*`; # オプションのチェックと展開して...
C_FLAG=false
FILE=""
for i in $*
do
case $i in
-c) C_FLAG=true ;;
-f) FILE=$2; shift ;;
--) break ;;
-?) echo invalid option; exit 1;;
esac
shift;
done
echo C_FLAG=$C_FLAG FILE=$FILE ARGS=$*
}}
となります。
これを実行すると
#pre{{
$ chmod +x Ex6.sh
$ Ex6.sh -cf 1 2 3
C_FLAG=true FILE=1 ARGS=2 3
}}
となります。
*** if文 [#i8ed2aa3]
次にif文です。if文は、testコマンドと一緒に使うことが多い...
if文の形式は、
#pre{{
if expr_list then ; block_stmt [else block_stmt] fi
}}
の形式を取ります。
expr_listの最後にセミコロンを付けない場合には、
#pre{{
if expr_list
then
block_stmt
else
block_stmt
fi
}}
のように改行してthenを記述します。((私はこちらの形式を使...
expr_listで最後に実行したコマンドの終了コードが0の場合に...
if分がネストする場合には、elif文を使用します。
#pre{{
if expr_list
then
block_stmt
elif expr_list
then
block_stmt
else
block_stmt
fi
}}
if文の例を以下に示します。
#pre{{
if [ $? -ne 0 ] # 直前のコマンドが正常に終了しなかった場合
then
if [ -e $TMP_FILE ] # 一時ファイルが存在すれば、
then
rm -f $TMP_FILE; # 一時ファイルを削除し、処理を終了...
exit 1;
fi
fi
}}
*** testコマンド [#q9f5e194]
testは、引数で指定された条件を確認するコマンドです。
shでは、慣例として"["((testのシンボリックリンク))も利用す...
よく使われるtestコマンドオプションを以下に示します。
: = | 文字列が等しい場合に、真を返します
: != |文字列が等しくない場合、真を返します
: -eq | 数値が等しい場合に、真を返します
: -ne | 数値が等しくない場合に、真を返します
: -d | ディレクトリの場合に、真を返します
: -e | ファイルが存在する場合に、真を返します
: -z | 文字列の長さが0の場合に、真を返します。シェル変数...
: -n | 文字列の長さが0でない場合に、真を返します
*** while文 [#t4b48c96]
while文は、条件が真の間、ループを回します。
while文の仕様は、
#pre{{
while expr_list
do
block_stmt;
done
}}
でexpr_listで最後に実行したコマンドの終了条件が0(真)の...
以下のEx7.shコマンドではread文と組み合わせて使用します。
#pre{{
#! /bin/sh
i=0
while read line
do
IN_LINE[$i]=$line;
i=`expr $i + 1`;
done
echo IN_LINE=${IN_LINE[@]};
}}
Ex7.shを実行すると
#pre{{
$ Ex7.sh <<EOF
> abc efg
> 12345
> EOF
IN_LINE=abc efg 12345
}}
のようにシェル変数IN_LINEの値が出力されます。
** シェルスクリプトTIP集 [#x2bd3c3c]
*** 縦をよこにする [#zd916bb7]
ディレクトリの特定のファイルのみを削除する場合、削除する...
+ 削除するファイルの候補をlsコマンドで/tmp/rm.lstに出力する
+ /tmp/rm.lsの内容をエディタで編集する
+ rmコマンドを起動する
では実際にやってみましょう。
これまでのCのサンプルファイルがあるディレクトリでオブジェ...
#pre{{
$ cc -c *.c
$ ls *.o >/tmp/rm.lst
$ cat /tmp/rm.lst
ex1.o
ex2.o
ex3.o
ex4.o
range.o
tgrep.o
$ rm `cat /tmp/rm.lst`
$ ls *.o
ls: *.o: No such file or directory
}}
rm `cat /tmp/rm.lst`のように一覧ファイルをバッククォート`
で実行すると縦に並んでいたファイルのリストが横一覧のコマ...
*** 一意なファイル名の生成 [#w23470d6]
シェルスクリプト中で一時ファイルを作成する場合、一意なフ...
このような場合特殊変数$$を使ってシェルのプロセスIDを取得...
特にヒアドキュメントのテンプレート機能を使ってawk等のスク...
以下のシェルスクリプト(Ex8.sh)のようなパターンになりま...
#pre{{
#! /bin/sh
TMP_FILE=/tmp/temp.$$
NAME='Hiroshi TAKEMOTO'
cat << EOF > $TMP_FILE
My name is $NAME.
This is a simple shell script example.
EOF
echo `cat $TMP_FILE`
rm $TMP_FILE
}}
Ex8.shを実行すると
#pre{{
$ Ex8.sh
My name is Hiroshi TAKEMOTO. This is a simple shell scrip...
$ ls /tmp/temp.*
ls: /tmp/temp.*: No such file or directory
}}
一時ファイルは、実行後に削除されています。
*** 出力の結合 [#ze550214]
shでは、標準エラー出力や標準出力の指定を2, 1のように記述...
標準出力と標準エラー出力を一緒にファイルに出力する場合に...
例)test.shの標準出力と標準エラー出力を/tmp/stdou_and_std...
#pre{{
$ test.sh 2>&1 >/tmp/stdou_and_stderr.out
}}
*** 特殊ファイル [#mdaa9e59]
出力内容を捨てたい時やファイルを空にしたい時に特殊ファイ...
標準出力をリダイレクトで/dev/nullに送れば、そのメッセージ...
大量のメッセージを出力するプログラムでそのメッセージが必...
また、ファイルを空にするときには、
#pre{{
$ cp /dev/null file_name
}}
のようにするとfile_nameで指定されたファイルのサイズが0に...
* まとめ [#h15f4f0e]
パイプでつなぐ(パイプライン処理)ことによって、短時間に...
更にシェルスクリプトを使うことによって
- データを加工するプログラムの一部をシェルスクリプト内部...
- 共通な処理は、シェルスクリプトにまとめ、さらにそれを使...
といったことを組み合わせることができます。
ここで紹介したデータ加工のパイプライン処理とシェルスクリ...
日常の業務で皆様のお役に立てば幸いです。
** 謝辞 [#idba5a02]
竹中章子さん、戸張一夫さんには原稿に対し貴重なコメントを...
* 参考文献 [#k43cbbaa]
- The Unix System
((私は、この本でUNIXを勉強しました。これ1冊でshの使い方、...
http://www.amazon.com/exec/obidos/ASIN/0201137917/
- Lions’ Commentary on UNIX
((初期のUNIXのソースコードを使ってUNIXカーネルの仕組みを...
http://www.amazon.co.jp/exec/obidos/ASIN/4756118445/
皆様のご意見、ご希望をお待ちしております。
- この記事は、「コマンドを組み合わせるのりとしてのシェル...
#comment_kcaptcha
終了行:
[[FrontPage]]
2008/08/12からのアクセス回数 &counter;
#contents
* はじめに [#a4aced3f]
Amazonの協調フィルタリングで話題になった「集合知(collecti...
そこにはこれが正解というものはなく、仮説と検証のサイクル...
日々研究開発されています。
仮説と検証のサイクルでは、生のデータから分析可能なデータ...
その処理内容は様々です。このため、データ加工では短期間に...
業務です。
そんな時に役にたつのが、これからご紹介します
- UNIXのコマンドをパイプでつないで、データを加工する方法...
- シェルスクリプトを使って、パイプライン処理によりきめ細...
です。
シェルには、sh, ksh, bash, csh, tcsh等いろいろな種類があ...
((bashでも以下に紹介する機能は使用できます))
* パイプでつなぐ [#g6c751b8]
1968年、UNIXの産みの親であるKen ThompsonとDennis Ritchie...
と呼ばれる大型のオペレーティングシステムを開発していた頃、
「インタラクティブで便利なコンピュータサービス」が欲しい...
彼らは、「ベル研の文書処理システムを作る」と言って予算を...
(システムメモリ16Kバイト、ユーザメモリ8Kバイト、ハードデ...
という現在のマイコンチップ以下のハードウェアを購入し、
その上に現在のUNIXシステムのコマンド群とroffと呼ばれる文...
今では、Linuxの普及により誰でもUNIXの環境を持つことができ...
この機会に、UNIXの「パイプでつなぐ」を試してみてください。
** UNIXの3大発明 [#i61b3a24]
ここでは、「パイプでつなく」をテーマにUNIXの偉大な発明の...
- fork : 子プロセスの生成
- pipe : パイプ
- dup : リダイレクト
について、その仕組みを例題を使いながら、わかりやすく説明...
*** パイプを使った処理の例 [#a948992b]
- 単語処理の例
パイプを使った処理の例として、「tr」コマンドのmanページに...
#pre{{
$ man tr | tr -cs 'A-Za-z' '\n' | sort -u | wc -l
492
}}
このようにUNIXのコマンドをパイプでつなぐことによって簡単...
- リアルタイムの例
「単語処理の例」は、別にパイプを使わなくてもファイルを使...
パイプがファイルと決定的に異なる点は、そのリアルタイム性...
図は、マウスイベントを別Windowにプロットする例です。
&ref(demo_pipeline.jpg);
このデモでは、xevコマンドとEvent2Plot, Graphのシェルスク...
#pre{{
$ xev | Event2Plot | Graph
}}
** パイプのつなぎ方 [#pfaae892]
pipeシステムコールは、本当に不思議な関数です。自分が出力...
マニュアルの説明を読んだだけでpipeシステムコールの使い方...
- 最初にパイプを生成します。
&ref(pipe.jpg);
- 次にforkシステムコールを使って、子プロセスを生成します
&ref(fork.jpg);
- 親のin、子のoutをクローズします
&ref(close.jpg);
- 親の1番のファイル記述子(stdout)をクローズし、dupします...
&ref(dup.jpg);
** パイプを実現する3つのシステムコール [#wece4043]
パイプをつなぐときに必要なシステムコール
- fork
- pipe
- dup
について、おさらいも含めて説明します。
*** fork システムコールのおさらい [#mcf73ea6]
forkシステムコールの仕様を簡単に書くと、
#pre{{
呼び出し形式
#include <unistd.h>
pid_t
fork(void);
機能
呼び出し元のプロセスをコピーして、新しいプロセスを生成...
戻り値
成功すると、子プロセスには0が返され、
親プロセスには、子プロセスのプロセスIDが返されます。fork...
}}
です。
forkでは、
- 子プロセスは、変数、ファイルなどのリソースを親プロセス...
- 親と子プロセスは、forkのリターン値で別の処理に切り分け...
ます。
簡単なプログラムで、上記の仕様を確認してみましょう。
#pre{{
#include <stdio.h>
#include <unistd.h>
main()
{
int pid;
if ((pid = fork()) == -1) {
fprintf(stderr, "can't fork\n");
}
else if (pid == 0) {
// child
fprintf(stdout, "this is a child process\n");
fprintf(stderr, "pid(child)=%d\n", pid);
}
else {
// parent
fprintf(stdout, "this is a parent process\n");
fprintf(stderr, "pid(parent)=%d\n", pid);
}
}
}}
以下のようにコンパイルして、実行すると
#pre{{
$ cc -o ex1 ex1.c
$ ex1
this is a parent process
pid(parent)=6556
this is a child process
pid(child)=0
}}
と出力され、親と子プロセスの分岐とpidの値が正しくセットさ...
次に、標準出力(1)とエラー出力(2)をリダイレクトでフ...
shでは n> のようにリダイレクトするファイル記述番号を指定...
- 1>1.out の指定により、標準出力をファイル(1.out)にリダイ...
- 2>2.out の指定により、標準エラー出力をファイル(2.out)に...
しています。
#pre{{
$ ex1 1>1.out 2>2.out
$ more *.out
<< 1.outの内容 >>
this is a parent process
this is a child process
<< 2.outの内容 >>
pid(parent)=6570
pid(child)=0
}}
と、親と子プロセスが同じファイルに書き込み、ファイルの共...
*** pipe システムコールの使い方 [#ka7a7042]
pipeシステムコールの仕様を簡単に書くと、
#pre{{
呼び出し形式
#include <unistd.h>
int
pipe(int fildes[2]);
機能
パイプを生成し、ペアのファイル記述子を割り当てます。1...
2番目(fildes[1])が書き込み用となります。
fildes[1]に書き込まれたデータは、fildes[0]から読み込ま...
これにより、あるプログラムの出力を他のプログラムの入力に...
読み込みまたは書き込みのファイル記述子のいずれかがクロ...
widowedとなったパイプに書き込むと書き込みプロセスはSIGPIP...
widowedにすることで、読み込みプロセスにEnd-Of-Fileを送る...
読み手がパイプのデータをすべて読み込んだ後やwidowedになっ...
0が返されます。
戻り値
成功すると0を返し、そうでない場合には-1を返します。
}}
パイプは、
- 単一方向のデータフローを持つバッファです
- バッファが満杯になった場合には、書き込みプロセスはスリ...
- 読み込みプロセスは、バッファに書き込まれるまでスリープ...
- 書き込み側がクローズするとパイプからの読み込みでは0(E...
- 読み手側でクローズされたパイプに書き込もうとするとSIGPI...
のように機能します。
簡単なプログラムで、上記の仕様を確認します。
#pre{{
// ex2.c : pipe example
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define BUFSIZE (128)
main()
{
int pid;
int fd[2];
// create a pipe.
if (pipe(fd) == -1) {
fprintf(stderr, "Can't create pipe\n");
exit(1);
}
printf("fd[0]=%d, fd[1]=%d\n", fd[0], fd[1]);
if ((pid = fork()) == 0) {
// child
char buf[BUFSIZE];
int len;
// close pipe out.
close(fd[1]);
// read message.
len = read(fd[0], buf, BUFSIZE);
printf("len=%d, buf=%s", len, buf);
len = read(fd[0], buf, BUFSIZE);
close(fd[0]);
fprintf(stderr, "child finishied. len=%d\n", len);
}
else {
// parent
char buf[] = "hello world\n";
// close pipe in.
close(fd[0]);
// write message.
write(fd[1], buf, sizeof(buf));
close(fd[1]);
fprintf(stderr, "parent finishied\n");
}
}
}}
プログラムを実行すると
#pre{{
$ ex2
fd[0]=3, fd[1]=4
parent finishied
len=13, buf=hello world
child finishied. len=0
}}
と出力され、
- pipeシステムコールによってfdに3, 4のファイル記述子が割...
- 親プロセスがhello world\nを書き込み、書き込み用ファイル...
- 子プロセスが、hello world\nを読み込み、次の読み込みをす...
ます。((最初の読み込みで、len=13となっているのは、文字列...
*** dup システムコールの使い方 [#ic4ba39d]
同様にdupシステムコールの仕様を簡単に書くと、
#pre{{
呼び出し形式
#include <unistd.h>
int
dup(int fildes);
機能
既存のファイル記述子を複製し、新しく生成されたファイル...
プロセスには、getdtablesize()で返される大きさのファイル記...
新しいファイル記述子には、未使用の内もっとも小さい値が返...
戻り値
正常終了の場合、0以上の値が返され、そうでない場合には-1...
}}
となります。
UNIXのファイル記述子の割り当てルールは、
- 新しいファイル記述子には、未使用の内もっとも小さい値が...
ので、特定の値のファイル記述子を割り当てたい時には、
- dupの直前にそのファイル記述子をクローズする
ことで実現できます。
リダイレクトやパイプは、この単純なルールを使って実現され...
dupの動作を確認するために、以下のサンプルプログラムを作成...
#pre{{
// ex3.c : dup example
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define BUFSIZE (128)
main()
{
int pid;
int fd[2];
// create a pipe.
if (pipe(fd) == -1) {
fprintf(stderr, "can't create pipe\n");
exit(1);
}
if ((pid = fork()) == 0) {
// child
int in;
// close pipe out.
close(fd[1]);
// close stdin.
close(0);
in = dup(fd[0]);
close(fd[0]);
fprintf(stderr, "in=%d\n", in);
fprintf(stderr, "child : exec wc\n");
execl("/usr/bin/wc", "wc", 0);
}
else {
// parent
int out;
// close pipe in.
close(fd[0]);
// close stdout.
close(1);
out = dup(fd[1]);
close(fd[1]);
fprintf(stderr, "out=%d\n", out);
fprintf(stderr, "parent: exec echo 'hello world'\n");
execl("/bin/echo", "echo", "hello world", 0);
}
}
}}
プログラムを実行すると、
#pre{{
$ ex3
out=1
parent: exec echo 'hello world'
in=0
child : exec wc
1 2 12
}}
と出力されます。
((execlは、引数で指定されたコマンドを実行します。実行プロ...
- 親プロセスのパイプの出力用ファイル記述子は、1(標準出...
- echo 'hello world'を実行します
- 子プロセスのパイプの入力用ファイル記述子は、0(標準入...
- echoの結果'hello world'を標準入力から読み込みwcを実行し...
wcの出力結果は、
#pre{{
$ echo 'hello world' | wc
1 2 12
}}
と同じであり、親プロセスと子プロセスの間でパイプが正常に...
** パイプをつなぐサンプル [#hbf674b0]
パイプのつなぎ方が分かったところで、最初に紹介したパイプ...
パイプライン処理の特徴として、以下のことが挙げられます。
- 確認しながら、パイプをつなぐ
- 複雑な処理は、単純な処理をパイプでつなぐ
- 途中経過を確認しながら、パイプでつなぐ
*** 確認しながら、パイプをつなぐ [#mb5f3fb6]
いくつかの処理を確認しながら、パイプを増やしていくのがパ...
最初のサンプルで、man trの結果から単語を切り出す部分をみ...
trの処理は、
- -cオプションは、指定されたパターン以外の文字列を置換の...
- -sオプションは、同じ文字に置換された場合、それを1個にま...
この結果、英字以外の文字列は、改行(\n)1個に置き換わり、...
実際にその処理をみてみましょう。
#pre{{
$ man tr | tr -cs 'A-Za-z' '\n'
TR
BSD
General
Commands
Manual
TR
途中省略
Std
POSIX
standard
BSD
July
BSD
}}
と出力されます。
単語が抽出できたことを確認し、同じ単語を1つにまとめます。
sortコマンドの
- -uオプションは、入力をソートするとき重複する行を1つにま...
先ほどのパイプラインに、sort -uを追加して重複を取り除きま...
#pre{{
$ man tr | tr -cs 'A-Za-z' '\n' | sort -u
A
ALL
AM
AN
AR
ASCII
AT
Additionally
Any
As
B
BI
BSD
以下省略
}}
と出力され、英単語とは思えない1または2文字の単語が含まれ...
これは、manページが、
&ref(man.jpg);
のように文書整形コマンドによって、強調文字、アンダーライ...
これをターミナルに出力するために、制御コードが付加されて...
#pre{{
$ man tr | od -c | more
0000000 T R ( 1 ) ...
0000020 B S ...
0000040 n e r a l C o m m a n ...
0000060 a n u a l ...
0000100 T R ( ...
0000120 N \b N A \b A M \b M E \b E ...
0000140 t \b t r \b r - - ...
0000160 s l a t e c h a r a c ...
0000200 \n \n S \b S Y \b Y N \b N O ...
0000220 P S \b S I \b I S \b S \n ...
0000240 t \b t r \b r [ - \b - C ...
0000260 c s \b s u \b u ] _ \b s ...
0000300 \b r _ \b i _ \b n _ \b g _ ...
0000320 \b s _ \b t _ \b r _ \b i _ ...
0000340 g _ \b 2 \n t \b ...
以下省略
}}
のように強調文字では、
- N \b Nのように文字を表示し、バックスペースで戻し、再度...
アンダラインでは、
- _ \b s のように先に_を表示し、バックスペースで戻し...
しているのです。
正しい結果がでるようにするには、バックスペースとその前の...
そこで、sedコマンドを追加します。(以下のコマンドで^Hは、...
#pre{{
$ man tr | sed -e 's/.^H//g' | tr -cs 'A-Za-z' '\n' | sor...
A
ALL
ASCII
Additionally
Any
As
BSD
C
COLLATE
COMPATIBILITY
CTYPE
}}
と正しく単語が抽出できました。
確認が終わった後で、moreをwc -lに変えると
#pre{{
$ man tr | sed -e 's/.^H//g' | tr -cs 'A-Za-z' '\n' | sor...
436
}}
正しい単語の数を得ることができました。
このように一つずつ確認しながらパイプをつなぐことによって...
*** 複雑な処理は、単純な処理をパイプでつなぐ [#t78619d9]
例えば、WebのアクセスログからGoogleの検索でブログにアクセ...
- Googleの検索からアクセスされたログには、http://www.goog...
- PukiWikiを使ったブログには、index.phpという文字が含まれ...
この条件を使って
#pre{{
$ grep http://www.google.co.jp/search /var/log/apache2/ac...
218.228.8.55 - - [09/Aug/2009:06:43:41 +0900] "GET /~take...
%2F%E6%9C%80%E5%88%9D%E3%81%AE%E4%B8%80%E6%AD%A9 HTTP/1.1...
.google.co.jp/search?q=avr+isp&hl=ja&lr=lang_ja&client=fi...
:ja:official&start=50&sa=N" "Mozilla/5.0 (Windows; U; Win...
.1.2) Gecko/20090729 Firefox/3.5.2"
以下省略
}}
のように簡単に目的のログだけを抽出することができます。
もちろん、grepの正規表現を使って1つのgrepで検索することも...
AかつBのような時、
$ grep A | grep B
のようにパイプでつなぐのが簡単かつ確実です。特に急いでい...
*** 途中経過を確認しながら、パイプでつなぐ [#i6e9f386]
2つ目のサンプル、
#pre{{
xev | Event2Plot | Graph
}}
では、Event2PlotとGraphというシェルスクリプトを使いました。
引数やオプションが多い場合には、それをシェルスクリプトに...
Graphは、graphコマンド((GNU plotutils パッケージに含まれ...
#pre{{
#! /bin/sh
graph -T X -x 0 100 -y 0 100 -m 0 -S 5
}}
- 1行目の#! は、使用するシェル(コマンド・インタープリタ...
- graphのコマンドとオプションをセットしています
エディタでGraphを入力した後に、シェルで実行できるように実...
#pre{{
$ chmod +x Graph
}}
Graphを起動して、X,Yの座標をブランク区切りで入力すると、...
#pre{{
$ Graph
10 30
50 50
70 20
}}
これでGraphの完成です。((Macの場合MacPortのgraphはXウィン...
次にxevコマンドを実行し、太い線の正方形の中で、マウスを動...
#pre{{
MotionNotify event, serial 23, synthetic NO, window 0xa00...
root 0x1fd, subw 0x0, time 236035801, (68,77), root:(...
state 0x0, is_hint 0, same_screen YES
MotionNotify event, serial 23, synthetic NO, window 0xa00...
root 0x1fd, subw 0x0, time 236035818, (66,106), root:...
state 0x0, is_hint 0, same_screen YES
}}
のようにMotionNotify eventの次の行に(68,77)のようにマウス...
これをawkを使ってX Y座標に変換しているのが、Event2Plotで...
先ほどと同様に
#pre{{
#! /bin/sh
awk -F, '
/MotionNotify/ {
getline;
gsub(/\(/, "", $4);
gsub(/\)/, "", $5);
printf("%d %d\n", $4, 100 - $5);
}'
}}
をEvent2Plotに入力して、
chmod +x Event2Plot
を実行してください。
xevとEvent2Plotをパイプでつないで、
#pre{{
$ xev | Event2Plot
6 59
27 85
37 82
42 81
43 80
42 80
41 79
40 79
39 79
38 79
以下省略
}}
最初、ある程度マウスを動かすと画面に座標が表示されます。(...
ここまで、確認できたらGraphと連結してみましょう。
#pre{{
$ xev | Event2Plot | Graph
}}
無事、最初の図と同じように出力されます。
グラフにプロットされるデータの途中経過がどのようになって...
このような場合には、teeコマンドで途中のデータをファイルに...
((tailコマンドの-fオプションによって、ファイルに追加され...
#pre{{
$ xev | Event2Plot | tee /tmp/1 | Graph &
$ tail -f /tmp/1
58 50
65 54
73 65
72 71
62 80
57 81
途中省略
終了するには、Ctrl-Cを入力してください
}}
リアルタイムとまではいきませんが、途中経過が表示されます。
** その他のパイプ(pipe)の使われ方 [#j46d5541]
*** グルーピング [#r0f82b15]
初期のUNIXでは、ファイルシステムを跨ぐファイルのコピーに...
そこで、tarコマンドとパイプを使って以下のようにコピーしま...
#pre{{
$ tar cf - . | (cd /mnt/bak; tar -xf -)
}}
これまでは、()で括ってパイプを使ったことはありませんで...
処理で、()で括ったコマンドは入出力が共通になります。
((実装上は別のシェルプロセスを起動し、コマンドを実行する...
*** バッククォート(`) [#p3d27cbb]
バッククォートで括られたコマンドの出力をパイプでつないで...
例えば、変数NOWに今の時刻をセットしたい場合に、
#pre{{
$ NOW=`date`
$ echo $NOW
Wed Aug 12 20:15:11 JST 2009
}}
とすると、変数NOWにdateコマンドの実行結果がセットされ、ec...
また、あるコマンドの実行結果を別のコマンドの引数にしたい...
例)変数iの値に1を足した値を表示する場合(シェル内の四則...
#pre{{
$ i=1
$ echo `expr $i + 1`
2
}}
これを使えば、変数の値を変える場合には、
#pre{{
$ i=`expr $i + 1`
$ echo $i
2
}}
とすればよいことが分かります。
*** ヒアドキュメント(<<EOF) [#u89a4f77]
シェルスクリプト内に記述したドキュメントにシェル変数の置...
ヒアドキュメントは、テンプレート(ひな形)として使用され...
sh, awk等のプログラムをシェルスクリプト内で生成し、実行す...
簡単な例を使ってヒアドキュメントの変数置換を確かめてみま...
#pre{{
$ NOW=`date`
$ cat <<EOF
> What time is it now?
> It's $NOW.
> EOF
What time is it now?
It's Thu Aug 13 17:56:49 JST 2009.
}}
- 変数NOWに現在時刻がセットされ、
- <<EOFがヒアドキュメントの開始です。<<の後には任意の文字...
- スクリプトではなく、端末などで実行すると第2プロンプトの...
- 出力は、期待通りNOWが現在時刻に置換されています
awkではshと同様に変数を$を使って参照するため、ヒアドキュ...
そのような場合には、<<\EOFのように<<の後に\を付けます。
#pre{{
$ cat <<\EOF
> What time is it now?
> It's $NOW.
> EOF
What time is it now?
It's $NOW.
}}
* 簡単シェルスクリプト [#vf5f7efd]
パイプをつないでデータを加工する方法がわかったところで、...
まずは、シェルのおさらいからはじめましょう。ここではsh, b...
** おさらい [#b051ca79]
ここでは、シェルのおさらいも兼ねて、
- リダイレクト
- ファイル名展開
- シェル変数と環境変数
- 特殊変数
- クォーティング
について、例題を交えながら説明します。
*** リダイレクト [#l97cf876]
UNIXのCプログラムでは、main関数がプログラムの開始部分(...
シェルがプログラムを起動する前に、標準入力、標準出力、標...
リダイレクトの仕組みは、dupシステムコールの例題でも示した...
- 新しい接続先のオープン
- 切り替える入出力のクローズ
- dupシステムコールを使った入出力の付け替え
によって実現されています。
主なリダイレクト指定方法は、
- < を使った標準入力の指定
- > を使った標準出力の指定
- >> を使ったファイルへの追加出力指定
があります。
では、実際にリダイレクトを使って簡単なファイルを作成しま...
#pre{{
$ echo test > /tmp/test.out
$ cat /tmp/test.out
test
$ echo more test >> /tmp/test.out
$ cat /tmp/test.out
test
more test
$ wc </tmp/test.out
2 3 15
}}
存在しないファイルに>>を使った場合、新たにファイルを作成...
また、すでにファイルが存在する場合、>を使ったリダイレクト...
((ファイルのrw属性は変更されません))
*** ファイル展開 [#k8dc7d65]
シェルでは、メタ記号を使って複数のファイル名に展開するこ...
メタ記号の一覧を以下に示します。
|メタ記号|説明|
|?|任意の一文字にマッチする|
|[abef]|[]で囲まれた文字のいずれかの一文字にマッチする|
|[a-z]|a-zの間に含まれる一文字にマッチする|
|[^abc]|[の直後に^が指定されるとそれ以降に指定した文字列...
|*|任意の文字列にマッチする|
ファイル展開はディレクトリ内のファイルに対して行われます。
例えば、ファイルが.bakで終わるすべてのファイルを削除する...
#pre{{
$ rm *.bak
}}
のように指定します。
サンプルソースのディレクトリには、以下のファイルがありま...
#pre{{
$ ls -a
. 2.out Graph ex2 images
.. Event2Plot Makefile ex2.c range
.cproject Ex5.sh Pipeline.txt ex3 range.c
.project Ex6.sh Tgrep.sh ex3.c tgrep
.settings Ex7.sh ex1 ex4 tgrep.c
1.out Ex8.sh ex1.c ex4.c
}}
しかし、echo *では
#pre{{
$ echo *
1.out 2.out Event2Plot Ex5.sh Ex6.sh Ex7.sh Ex8.sh Graph ...
Tgrep.sh ex1 ex1.c ex2 ex2.c ex3 ex3.c ex4 ex4.c images r...
tgrep.c
}}
となり、.で始まるファイルが含まれていません。
UNIXでは、.で始まるファイル、ディレクトリはメタ記号の展開...
それでは、.*とするとどうなるでしょう。
#pre{{
$ echo .*
. .. .cproject .project .settings
}}
.で始まるファイルの他に、.(カレントディレクトリ)と..(...
メタ記号の展開で間違ってカレントディレクトリや親ディレク...
ファイル展開では.で始まるファイルはメタ記号では展開されま...
この性質を利用して.profile等各種コマンドのユーザ設定ファ...
それでは、.ではじまるファイル名を指定するには、以下のよう...
#pre{{
$ echo .[^.]*
.cproject .project .settings
}}
*** シェル変数 [#hf55402a]
シェルには、同一シェル内だけで有効なシェル変数と子プロセ...
シェル変数に値をセットするには、変数名と値の間に空白を入...
代入する値の中に空白が含まれる場合シングルクォート(')また...
シェル変数名にはアルファベット、数字、アンダースコア(_)が...
慣例としてループの添え字を除いて、変数名には大文字が多く...
簡単なシェル変数の例を以下に示します。
#pre{{
$ HOST_NAME=IES00
$ IN_FILE=""
$ PS="ps -ef"
}}
順番に見ていきましょう。
- シェル変数HOST_NAMEに"IES00"をセットします
- シェル変数IN_FILEに空文字をセットします
- シェル変数PSに"ps -ef"をセットします
変数を参照するには、変数名の前に$を付けます。
変数は、空白や.などの変数名に使えない文字を区切りとします...
ダブルクォート"や${}で括って参照します。
#pre{{
$ $PS
UID PID PPID C STIME TTY TIME CMD
0 1 0 0 0:17.04 ?? 0:24.09 /sbin/...
0 15 1 0 0:01.72 ?? 0:01.83 /usr/l...
0 16 1 0 0:26.06 ?? 0:41.26 /usr/s...
以下省略
$ VAR=hello
$ echo $VAR
hello
$ echo ${VAR}_again
hello_again
$ echo using"$VAR"
usinghello
}}
また、シェル変数には配列指定も可能であり、変数名[添え字]...
すべての配列要素を参照する場合には、${配列名[@]}とし、要...
#pre{{
$ LINE[0]=foo
$ LINE[1]=bar
$ echo ${LINE[@]}
foo bar
$ echo ${#LINE[@]}
2
}}
変数をクリアするには、unset 変数名を使います
#pre{{
$ unset LINE
$ echo ${LINE[@]}
}}
*** 環境変数 [#t79199e5]
つぎに環境変数について説明します。環境変数を定義するには...
すでに定義されているシェル変数を環境変数に切り替えるには...
#pre{{
$ export VAR
}}
新規に環境変数を定義する場合には、exportの後に変数名=値を...
#pre{{
$ export VAR=hello
}}
どうやってシェル変数と環境変数が使い分けられているのか、...
Cのmain関数の宣言形式に、
#pre{{
#include <stdio.h>
int main(int argc, char * argv[], char * env[])
}}
というのがありますが、最後のenvが環境変数の配列をシェルか...
簡単なCのサンプルを使って環境変数がシェルから引き継がれる...
#pre{{
// ex4.c : env example
#include <stdio.h>
int main(int argc, char* argv[], char *env[])
{
int i;
for (i=0 ; env[i] != NULL ; ++i) {
printf("%s ", env[i]);
}
printf("\n");
}
}}
コンパイルし、実行します。((makeコマンドですべてのサンプ...
#pre{{
$ cc -o ex4 ex4.c
$ ex4
MANPATH=/opt/local/share/man:/usr/share/man:/usr/local/sh...
TERM_PROGRAM=Apple_Terminal TERM=xterm-color SHELL=/bin/b...
CATALINA_HOME=/Users/take/local/tomcat
TMPDIR=/var/folders/Xo/XoxQsiGT2RWIck+BYuxhKU+++TI/-Tmp-/
Apple_PubSub_Socket_Render=/tmp/launch-wSems0/Render TERM...
以下省略
}}
この結果をenvコマンドと比べると
#pre{{
$ echo `env`
MANPATH=/opt/local/share/man:/usr/share/man:/usr/local/sh...
TERM_PROGRAM=Apple_Terminal TERM=xterm-color SHELL=/bin/b...
CATALINA_HOME=/Users/take/local/tomcat
TMPDIR=/var/folders/Xo/XoxQsiGT2RWIck+BYuxhKU+++TI/-Tmp-/
Apple_PubSub_Socket_Render=/tmp/launch-wSems0/Render TERM...
以下省略
}}
同じ結果となり、環境変数がサンプルプログラムに引き渡され...
*** 特殊変数 [#n0f58ed5]
シェルには、シェル変数、環境変数の他に、
- 位置変数
- 特殊変数
- 特殊置換
の変数操作があります。
:位置変数 | シェルやシェル関数への引数を$0, $1, $2, ...$n...
:特殊変数 | プロセスID、引数の個数などの特殊な情報を参...
-- $* : $1から始まるすべての位置変数を展開します
-- $0 : 実行コマンドの名前を返します
-- $# : 位置変数の数を返します
-- $$ : 実行中のプロセスIDを返します
-- $? : 直前のコマンドの終了ステータスを返します
: 特殊置換 | ${変数:=省略値}の形式で指定し、変数が定義さ...
*** クォーティング [#w5202abd]
シェルで、空白で区切られた文字列を1つの引数にしたり、置換...
このようなときには、バックスラッシュ\やシングルクォート'...
#pre{{
$ echo \$
$
# echo '* ?'
* ?
}}
シェルのクォーティングルールをきちんと説明した書物は少な...
|>|>|>|>|>|>|CENTER:メタ文字|h
||'|"|`|\|$|*|
|'|t|n|n|n|n|n|
|"|n|t|y|y|y|n|
|`|n|n|t|y|n|n|
ここで、
- t : 終端文字
- y : シェルによって解釈される
- n : シェルによって解釈されず、そのまま渡される
ことを意味します。
ただし、`で囲まれた$は、シェル変数が置換されるので、yの間...
#pre{{
$ HELLO="echo hello"
$ echo `$HELLO`
hello
}}
以上のことから、シェルのクォーティングルールを簡単に説明...
- ' は、すべての特殊文字を無効にします
- "は、変数置換、\を有効にし、`によって返された結果を1つ...
- `は、変数置換、\を有効にします
となります。
** おきまりのパターン [#a20dec8e]
シェルは、コマンドを組み合わせるだけではなく、その使い方...
ここでは、単純な検索するプログラムtgrepを複数ファイルの検...
- 引数のチェックの追加
- 各ファイルに対する繰り返し処理の追加
tgrepは、
- 標準入力から1行文の文字列を読み込み
- 第一引数に指定されたパターンを含んでいたら、その行を標...
をファイルの終わりまで繰り返す、簡単なプログラムです。
#pre{{
#include <stdio.h>
#include <string.h>
#define BUFSIZE (512)
main(int argc, char *argv[])
{
char* pattern = *(++argv);
char line[BUFSIZE];
while(fgets(line, BUFSIZE, stdin) != NULL) {
if (strstr(line, pattern) != NULL)
printf(line);
}
}
}}
これから作成するシェルスクリプトの名称をTgrep.shとします。
主な機能は、
- 引数が1個の場合には、標準入力から検索し引数で指定され...
- 引数が2個以上の場合には、第2引数以降の各ファイルに対し...
*** 引数チェック [#j066a3a2]
引数のチェックでよく使用されるシェルの文法がcase文です。
シェルのcase文には正規表現を使うことができるので、C言語...
まず、引数の数を調べるところは、特殊シェル変数の$#で引数...
case文で処理を振り分けます。各条件で実行する処理は、2個...
#pre{{
#! /bin/sh
case $# in
1)
echo pattern: $1 ;;
[2-9]) echo pattern: $1; shift; echo files: $* ;;
esac
}}
ここで、shiftコマンドは、位置引数を1個左にシフトコマンド...
まずは、このようにシェルスクリプトが正しい動作をするかech...
#pre{{
$ Tgrep.sh 1
pattern: 1
$ Tgrep.sh 1 2 3 4
pattern: 1
files: 2 3 4
}}
うまく動作しているように見えますが、これでは10個以上の引...
そこで、引数のパターンを0個、1個、それ以外に変えてみま...
#pre{{
#! /bin/sh
case $# in
0) echo invalid argument.; exit 1;;
1) echo pattern: $1 ;;
*) echo pattern: $1; shift; echo files: $* ;;
esac
}}
引数が10個以上でも正常に動作するか、引数の個数を変えて確...
#pre{{
$ Tgrep.sh 1 2 3 4 5 6 7 8 9 10 11
pattern: 1
files: 2 3 4 5 6 7 8 9 10 11
$ Tgrep.sh 1
pattern: 1
$ Tgrep.sh
invalid argument.
}}
すべてうまくいきました。これで、引数チェックは完成です。
*** 各ファイルに対する繰り返し [#scf2ecb0]
次は、検索するファイル数が複数になったときの処理を追加し...
同じ処理を何回か繰り返す場合には、for文を使用します。
引数のテストではechoを使っていましたが、これからはtgrepに...
2個以上のファイルに対する処理にfor文を入れてみましょう。
#pre{{
#! /bin/sh
case $# in
0) echo invalid argument.; exit 1;;
1) tgrep $1 ;;
*) PATTERN=$1; shift;
for i in $*
do
tgrep $PATTERN < $i;
done ;;
esac
}}
できたスクリプトを動かしてみます。
#pre{{
$ Tgrep.sh BUFSIZE *.c
#define BUFSIZE (128)
char buf[BUFSIZE];
len = read(fd[0], buf, BUFSIZE);
len = read(fd[0], buf, BUFSIZE);
#define BUFSIZE (128)
#define BUFSIZE (512)
char line[BUFSIZE];
while(fgets(line, BUFSIZE, stdin) != NULL) {
}}
うまく検索しているみたいですが、これではどのファイルにパ...
そこで、Tgrep.shの出力行の先頭にファイル名を追加すること...
出力行の処理には、while文とread文を使用します。出力行をre...
それをファイル名と一緒にechoコマンドで出力します。
#pre{{
#! /bin/sh
case $# in
0) echo invalid argument.; exit 1;;
1) tgrep $1 ;;
*) PATTERN=$1; shift;
for i in $*
do
tgrep $PATTERN < $i | while read LINE
do
echo $i: $LINE
done
done ;;
esac
}}
出力結果は、以下のようになります。
#pre{{
$ Tgrep.sh BUFSIZE *.c
ex2.c: #define BUFSIZE (128)
ex2.c: char buf[BUFSIZE];
ex2.c: len = read(fd[0], buf, BUFSIZE);
ex2.c: len = read(fd[0], buf, BUFSIZE);
ex3.c: #define BUFSIZE (128)
tgrep.c: #define BUFSIZE (512)
tgrep.c: char line[BUFSIZE];
tgrep.c: while(fgets(line, BUFSIZE, stdin) != NULL) {
}}
少しずつ動作を確認しながら、機能を拡張することで難なく目...
このようにわずか13行のシェルスクリプトで単純な検索プロ...
** 制御コマンド [#zd2f10a8]
詳しい説明もせずにfor文、case文、while文を使ってしまった...
シェル制御コマンドのおもしろいところは、ifならfi、caseはe...
*** for文 [#ua1c3ffc]
for 文は、次の形式で使用します。
#pre{{
for name in word_list
do
block_stmt;
done
}}
- word_listには空白で区切った単語の列を指定し、指定された...
- doとdoneの間のblock_stmtが実行されます
- in word_listが省略された場合には、すべての引数($*と同...
簡単な例で動作をみてましょう。
#pre{{
# 10回ループを回す場合の簡単な例
for i in 1 2 3 4 5 6 7 8 9 10
do
echo $i; # ループカウンタの値を出力する
done
}}
端末からこれを実行すると、
#pre{{
$ for i in 1 2 3 4 5 6 7 8 9 10
> do
> echo $i;
> done
1
2
3
4
5
6
7
8
9
10
}}
doneが入力されるまでは、第2プロンプトの> が出力され、その...
シェルスクリプトでは、Cのfor文のような指定回数だけループ...
そこで、pythonのrangeと同様のプログラムを作成し、for文で...
rangeの仕様は、以下の通りとします
- 引数が1個の場合、0以上、指定された値より小さい整数を出...
- 引数が2個の場合、第1引数を初期値とし、初期から第2引数よ...
- 引数が3個の場合、第1引数を初期値、第2引数を最大値、第3...
range.cは、以下のようになります。
#pre{{
// range.c : range program.
#include <stdio.h>
main(int argc, char* argv[])
{
int init = 0;
int max = 0;
int step = 1;
int i;
if (argc == 2) {
max = atoi(argv[1]);
}
else if (argc == 3) {
init = atoi(argv[1]);
max = atoi(argv[2]);
}
else if (argc == 4) {
init = atoi(argv[1]);
max = atoi(argv[2]);
step = atoi(argv[3]);
}
if (step > 0)
for (i = init; i < max; i += step)
printf("%d ", i);
else
for (i = init; i > max; i += step)
printf("%d ", i);
printf("\n");
}
}}
動作を確認してみましょう。
#pre{{
$ range 1 10
1 2 3 4 5 6 7 8 9
$ range -1 1
-1 0
$ range 10 2 -1
10 9 8 7 6 5 4 3
$ range 3
0 1 2
}}
となります。
それでは、rangeを使って与えられた数をカウントダウンするス...
((^Gは、Ctrl-vのあとにCtrl-gを入力してください))
#pre{{
# /bin/sh
for i in `range $1 0 -1`
do
echo $i ^G; sleep 1;
done
}}
では、実行権限を与えて、5からカウントダウンしてみましょ...
#pre{{
$ chmod +x Ex5.sh
$ Ex5.sh 5
5
4
3
2
1
}}
1秒ごとにブザーが鳴って、5から1までカウントダウンしまし...
*** case文 [#ta6d2577]
case文は、パターンに合致した実行文を処理します。
#pre{{
case word in
pattern1) block_stmt ;; # pattern1に合致した場合、
....
pattern2) block_stmt ;; # pattern2に合致した場合、
*) block_stmt ;; # 上記のいずれでもない場合、
esac
}}
処理の終わりには、;ではなく;;を指定します。また、*)とする...
pattern1, pattern2のパターン指定には、shの正規表現を使う...
通常、case文は引数のチェックに使用され、その場合には位置...
UNIXには、オプションを処理する便利なコマンドgetoptがあり...
#pre{{
$ getopt cf: -cf 1 2 3
-c -f 1 -- 2 3
}}
となり、
- -c -f 1のようにオプションまたオプションとその値のペアに...
- -- でオプションの終わりを表し
- それ以降がコマンドへの引数
に変換してくれるので、シェルスクリプトの引数処理がとても...
getoptを使った例として、
コマンドEx6.shのオプションがvfのいずれかで、fオプションに...
#pre{{
#! /bin/sh
set -- `getopt cf: $*`; # オプションのチェックと展開して...
C_FLAG=false
FILE=""
for i in $*
do
case $i in
-c) C_FLAG=true ;;
-f) FILE=$2; shift ;;
--) break ;;
-?) echo invalid option; exit 1;;
esac
shift;
done
echo C_FLAG=$C_FLAG FILE=$FILE ARGS=$*
}}
となります。
これを実行すると
#pre{{
$ chmod +x Ex6.sh
$ Ex6.sh -cf 1 2 3
C_FLAG=true FILE=1 ARGS=2 3
}}
となります。
*** if文 [#i8ed2aa3]
次にif文です。if文は、testコマンドと一緒に使うことが多い...
if文の形式は、
#pre{{
if expr_list then ; block_stmt [else block_stmt] fi
}}
の形式を取ります。
expr_listの最後にセミコロンを付けない場合には、
#pre{{
if expr_list
then
block_stmt
else
block_stmt
fi
}}
のように改行してthenを記述します。((私はこちらの形式を使...
expr_listで最後に実行したコマンドの終了コードが0の場合に...
if分がネストする場合には、elif文を使用します。
#pre{{
if expr_list
then
block_stmt
elif expr_list
then
block_stmt
else
block_stmt
fi
}}
if文の例を以下に示します。
#pre{{
if [ $? -ne 0 ] # 直前のコマンドが正常に終了しなかった場合
then
if [ -e $TMP_FILE ] # 一時ファイルが存在すれば、
then
rm -f $TMP_FILE; # 一時ファイルを削除し、処理を終了...
exit 1;
fi
fi
}}
*** testコマンド [#q9f5e194]
testは、引数で指定された条件を確認するコマンドです。
shでは、慣例として"["((testのシンボリックリンク))も利用す...
よく使われるtestコマンドオプションを以下に示します。
: = | 文字列が等しい場合に、真を返します
: != |文字列が等しくない場合、真を返します
: -eq | 数値が等しい場合に、真を返します
: -ne | 数値が等しくない場合に、真を返します
: -d | ディレクトリの場合に、真を返します
: -e | ファイルが存在する場合に、真を返します
: -z | 文字列の長さが0の場合に、真を返します。シェル変数...
: -n | 文字列の長さが0でない場合に、真を返します
*** while文 [#t4b48c96]
while文は、条件が真の間、ループを回します。
while文の仕様は、
#pre{{
while expr_list
do
block_stmt;
done
}}
でexpr_listで最後に実行したコマンドの終了条件が0(真)の...
以下のEx7.shコマンドではread文と組み合わせて使用します。
#pre{{
#! /bin/sh
i=0
while read line
do
IN_LINE[$i]=$line;
i=`expr $i + 1`;
done
echo IN_LINE=${IN_LINE[@]};
}}
Ex7.shを実行すると
#pre{{
$ Ex7.sh <<EOF
> abc efg
> 12345
> EOF
IN_LINE=abc efg 12345
}}
のようにシェル変数IN_LINEの値が出力されます。
** シェルスクリプトTIP集 [#x2bd3c3c]
*** 縦をよこにする [#zd916bb7]
ディレクトリの特定のファイルのみを削除する場合、削除する...
+ 削除するファイルの候補をlsコマンドで/tmp/rm.lstに出力する
+ /tmp/rm.lsの内容をエディタで編集する
+ rmコマンドを起動する
では実際にやってみましょう。
これまでのCのサンプルファイルがあるディレクトリでオブジェ...
#pre{{
$ cc -c *.c
$ ls *.o >/tmp/rm.lst
$ cat /tmp/rm.lst
ex1.o
ex2.o
ex3.o
ex4.o
range.o
tgrep.o
$ rm `cat /tmp/rm.lst`
$ ls *.o
ls: *.o: No such file or directory
}}
rm `cat /tmp/rm.lst`のように一覧ファイルをバッククォート`
で実行すると縦に並んでいたファイルのリストが横一覧のコマ...
*** 一意なファイル名の生成 [#w23470d6]
シェルスクリプト中で一時ファイルを作成する場合、一意なフ...
このような場合特殊変数$$を使ってシェルのプロセスIDを取得...
特にヒアドキュメントのテンプレート機能を使ってawk等のスク...
以下のシェルスクリプト(Ex8.sh)のようなパターンになりま...
#pre{{
#! /bin/sh
TMP_FILE=/tmp/temp.$$
NAME='Hiroshi TAKEMOTO'
cat << EOF > $TMP_FILE
My name is $NAME.
This is a simple shell script example.
EOF
echo `cat $TMP_FILE`
rm $TMP_FILE
}}
Ex8.shを実行すると
#pre{{
$ Ex8.sh
My name is Hiroshi TAKEMOTO. This is a simple shell scrip...
$ ls /tmp/temp.*
ls: /tmp/temp.*: No such file or directory
}}
一時ファイルは、実行後に削除されています。
*** 出力の結合 [#ze550214]
shでは、標準エラー出力や標準出力の指定を2, 1のように記述...
標準出力と標準エラー出力を一緒にファイルに出力する場合に...
例)test.shの標準出力と標準エラー出力を/tmp/stdou_and_std...
#pre{{
$ test.sh 2>&1 >/tmp/stdou_and_stderr.out
}}
*** 特殊ファイル [#mdaa9e59]
出力内容を捨てたい時やファイルを空にしたい時に特殊ファイ...
標準出力をリダイレクトで/dev/nullに送れば、そのメッセージ...
大量のメッセージを出力するプログラムでそのメッセージが必...
また、ファイルを空にするときには、
#pre{{
$ cp /dev/null file_name
}}
のようにするとfile_nameで指定されたファイルのサイズが0に...
* まとめ [#h15f4f0e]
パイプでつなぐ(パイプライン処理)ことによって、短時間に...
更にシェルスクリプトを使うことによって
- データを加工するプログラムの一部をシェルスクリプト内部...
- 共通な処理は、シェルスクリプトにまとめ、さらにそれを使...
といったことを組み合わせることができます。
ここで紹介したデータ加工のパイプライン処理とシェルスクリ...
日常の業務で皆様のお役に立てば幸いです。
** 謝辞 [#idba5a02]
竹中章子さん、戸張一夫さんには原稿に対し貴重なコメントを...
* 参考文献 [#k43cbbaa]
- The Unix System
((私は、この本でUNIXを勉強しました。これ1冊でshの使い方、...
http://www.amazon.com/exec/obidos/ASIN/0201137917/
- Lions’ Commentary on UNIX
((初期のUNIXのソースコードを使ってUNIXカーネルの仕組みを...
http://www.amazon.co.jp/exec/obidos/ASIN/4756118445/
皆様のご意見、ご希望をお待ちしております。
- この記事は、「コマンドを組み合わせるのりとしてのシェル...
#comment_kcaptcha
ページ名:
SmartDoc