Linux演習の第2弾として、リダイレクトとパイプについて説明し、LOG解析の演習を行う。
標準入出力とリダイレクト
出力リダイレクト
C言語のプログラミングで、プログラムの実行結果をレポートに張り付ける時はどのように行っているだろうか?多くの人は、実行画面を PrintScreen でキャプチャした画像を張り付けているかもしれない。しかし、数十行にわたる結果であれば何度もキャプチャが必要となる。
そこで、今日の最初はリダイレクト機能について説明する。
“gcc ファイル.c” は、C言語のプログラムをコンパイルし、a.out という実行ファイルを生成する。”./a.out” にてプログラムを実行する。実行する命令に、“> ファイル名” と書くと、通常の出力画面(標準出力) をファイル名に記録してくれる。これを出力リダイレクトと呼ぶ。また、“>> ファイル名” と書くと、既存ファイルの後ろに追記してくれる。
s53599xx@nitfcei:~$ cat helloworld.c
#include <stdio.h>
int main() {
printf( "Hello World\n" ) ;
return 0 ;
}
s53599xx@nitfcei:~$ gcc helloworld.c
s53599xx@nitfcei:~$ ./a.out
Hello World
s53599xx@nitfcei:~$ ./a.out > helloworld.txt
s53599xx@nitfcei:~$ cat helloworld.txt
Hello World
s53599xx@nitfcei:~$ ./a.out >> helloworld.txt
s53599xx@nitfcei:~$ cat helloworld.txt
Hello World
Hello World
入力リダイレクト
次に、1行に名前と3教科の点数が書いてある複数行に渡るデータの各人の平均点を求めるプログラムを考える。
s53599xx@nitfcei:~$ cp /home0/Challenge/2.1-RedirectPipe.d/avg-each-low.c .
s53599xx@nitfcei:~$ cat avg-each-low.c
#include <stdio.h>
// ((input)) ((output))
// saitoh 43 54 82 saitoh 59.67
// tomoko 89 100 32 tomoko 73.67
// mitsuki 79 68 93 mitsuki 80.00
int main() {
char name[ 100 ] ;
int point[ 3 ] ;
while( scanf( "%s%d%d%d" ,
name , &point[0] , &point[1] , &point[2] ) == 4 ) {
double sum = 0.0 ;
for( int i = 0 ; i < 3 ; i++ )
sum += point[i] ;
printf( "%s %6.2f\n" , name , sum / 3.0 ) ;
}
return 0 ;
}
s53599xx@nitfcei:~$ gcc avg-each-low.c
s53599xx@nitfcei:~$ ./a.out
saitoh 43 54 82 入力
saitoh 59.67 出力
tomoko 89 100 32 入力
tomoko 73.67 出力
^D ← Ctrl-D を押すとファイル入力を終了
しかし、プログラムの書き方を間違えてプログラムを修正していると、動作確認のたびに何度も同じデータを入力するかもしれないが、面倒ではないだろうか?
プログラムを実行する時に、“< ファイル名” をつけると、通常はキーボードから入力する所を、ファイルからの入力に切り替えて実行することができる。このようなscanf()を使う時のようなプログラムの入力を標準入力といい、それをファイルに切り替えることを入力リダイレクトと呼ぶ。
s53599xx@nitfcei:~$ cp /home0/Challenge/2.1-RedirectPipe.d/name-point3.txt . s53599xx@nitfcei:~$ cat name-point3.txt saitoh 43 54 82 tomoko 89 100 32 mitsuki 79 68 93 s53599xx@nitfcei:~$ ./a.out < name-point3.txt saitoh 59.67 tomoko 73.67 mitsuki 80.00
この入力リダイレクトと出力リダイレクトを合わせて使うこともできる。
s53599xx@nitfcei:~$ ./a.out < name-point3.txt > name-avg.txt s53599xx@nitfcei:~$ cat name-avg.txt saitoh 59.67 tomoko 73.67 mitsuki 80.00
パイプ
先の名前と3教科のプログラムの結果から、全員の平均点をも計算したい場合、どのようなプログラムを作るだろうか?C言語だけの知識なら、各人の行のデータを計算するループの中に、全員の合計と人数を求めるプログラムを書いて、最後に平均点を出力するだろう。
一方で、複数人の名前と平均点のデータから平均点を求めるプログラムを書いて、前述のプログラムの実行結果を使う人もいるだろう。
以下の例では、“gcc -o avg-each-row avg-each-row.c” で、avg-each-row という実行ファイル、“gcc -o avg-all avg-all.c” で、avg-all という実行ファイルを生成し、avg-each-row で入力リダイレクト・出力リダイレクトを使って、name-avg.txt を作り、avg-all を入力リダイレクトで、最終結果を表示している。
s53599xx@nitfcei:~$ cp /home0/Challenge/2.1-RedirectPipe.d/avg-all.c .
s53599xx@nitfcei:~$ cat avg-all.c
#include <stdio.h>
// ((input)) ((output))
// saitoh 59.67 73.11
// tomoko 73.67
// mitsuki 80.00
int main() {
char name[ 100 ] ;
double point ;
double sum = 0 ;
int count = 0 ;
while( scanf( "%s%lf" , name , &point ) == 2 ) {
sum += point ;
count++ ;
}
printf( "%6.2f\n" , sum / (double)count ) ;
return 0 ;
}
s53599xx@nitfcei:~$ gcc -o avg-each-low avg-each-low.c
s53599xx@nitfcei:~$ gcc -o avg-all avg-all.c
s53599xx@nitfcei:~$ ./avg-each-low < name-point3.txt > name-avg.txt
s53599xx@nitfcei:~$ ./avg-all < name-avg.txt
71.11
しかし、いちいち入出力の結果を name-avg.txt を作るのは面倒である。であれば、以下の様なイメージで処理をすれば答えが求まる。
name-point3.txt→(avg-each-row)→name-avg.txt→(avg-all)→結果
これは、パイプ機能を使って以下の様に動かすことができる。
s53599xx@nitfcei:~$ ./avg-each-low < name-point3.txt | ./avg-all 71.11 s53599xx@nitfcei:~$ cat name-point3.txt | ./avg-each-low | ./avg-all 71.11
プログラムを実行する時に、“A | B” ように書くと、プログラムA の標準出力結果を、プログラムB の標準入力に接続させて、2つのプログラムを実行できる。このような機能を、パイプと呼ぶ。上記例の2つめ “cat… | ./avg-each-low | ./avg-all” では3つのプログラムをパイプでつないでいる。
リダイレクトのまとめ
| 入力リダイレクト(標準入力) | 実行コマンド < 入力ファイル |
| 出力リダイレクト(標準出力) | 実行コマンド > 出力ファイル |
| 出力リダイレクト(標準出力の追記) | 実行コマンド >> 出力ファイル |
| 標準エラー出力のリダイレクト | 実行コマンド 2> 出力ファイル |
| パイプ コマンドAの標準出力をコマンドBの標準入力に接続 |
コマンドA | コマンドB |
C言語のコンパイルまとめ
| C言語のコンパイル(実行ファイルはa.out) | gcc ソースファイル |
| 実行ファイル名を指定してコンパイル | gcc -o 実行ファイル ソースファイル |
フィルタプログラム
パイプを使うと、標準入力からデータをもらい・標準出力に結果を出力するような簡単なプログラムを組み合わせて、様々な処理が簡単にできる。こういったプログラムは、フィルタと呼ぶ。
簡単な例として、入力をすべて大文字に変換するプログラム(toupper)、入力文字をすべて小文字に変換するプログラム(tolower)が、下記の例のように保存してあるので動作を確かめよ。
s53599xx@nitfcei:~$ cp /home0/Challenge/2.1-RedirectPipe.d/toupper.c .
s53599xx@nitfcei:~$ gcc -o toupper toupper.c .
s53599xx@nitfcei:~$ cat toupper.c | ./toupper
#INCLUDE <STDIO.H>
#INCLUDE <CTYPE.H>
INT MAIN() {
INT C ;
WHILE( (C = GETCHAR()) != EOF )
PUTCHAR( TOUPPER( C ) ) ;
RETURN 0 ;
}
s53599xx@nitfcei:~$ cp /home0/Challenge/2.1-RedirectPipe.d/tolower.c .
s53599xx@nitfcei:~$ gcc -o tolower tolower.c
s53599xx@nitfcei:~$ cat tolower.c | ./tolower
(((何が出力されるか答えよ)))
よく使われるフィルタのまとめ
| 文字パターンを含む行だけ出力 | grep 文字パターン |
| 文字パターンを含まない行を出力 文字パターンを正規表現でマッチングし該当行を出力 大文字小文字を区別しない |
grep -v 文字パターン grep -e 正規表現 grep -i 文字パターン |
| 入力文字数・単語数・行数をカウント(word counter) | wc |
| 入力行数をカウント | wc -l |
| データを昇順に並べる | sort |
| データを降順に並べる 先頭を数字と見なしてソート |
sort -r sort -g |
| 同じ行データが連続したら1つにまとめる | uniq |
| 同じ行が連続したら1つにまとめ、連続した数を出力 | uniq -c |
| 空白区切りで指定した場所(1番目)を抽出 | awk ‘{print$1;}’ |
| 入力の先頭複数行を表示(10行) | head |
| 入力の末尾複数行を表示(10行) | tail |
| 指定した行数だけ、先頭/末尾を表示 | head -行数 tail -行数 |