スクリプト言語sed、awk

システム・プログラム

                                       電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/syspro-2001/2001-06-25
あるいは、次のページから手繰っていくこともできます。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/
http://www.is.tsukuba.ac.jp/~yas/index-j.html

■今日の重要な話

■スクリプト言語

■インタプリタとスクリプト

「#!」の意味の説明。

■ラインエディタed

テレタイプ時代のラインエディタ。

■ストリーム・エディタ sed

データを、標準入力、またはファイルから読み込み、結果を標準出力に出す。
% sed -e '命令1' -e '命令2' -e '命令3' input > output [←]
例:appleをorangeに変える。
% sed -e 's/apple/orange/' input > output [←]

◆sedのコマンドの形式

<範囲><コマンド><引数>

<範囲>の指定方法

行番号
その行番号の行
行番号1,行番号2
行番号1から行番号2の間
/regexp/
そのregexp(regular expression,正規表現)がある行
$
最後の行
1,$
全て
1,/reg1/
最初からそのregexp(regular expression,正規表現)がある行まで
!を付けると、否定の意味。

<コマンド><引数>

s/文字列1/文字列2/
文字列の置き換え(substitute)。
s/文字列1/文字列2/g
全ての"文字列1"を"文字列2"に置き換える。gがなければ1度しか置き換えない。
s/文字列1/文字列2/p
s した後に print する。
s/文字列1/文字列2/gp
p
print
d
delete。その行を削除する。
q
quit。終了。以後の入力は無視される。
文字列の区切りは、「/」以外も使える。「s:文字列1:文字列 2:」でも、「s@文字列1@文字列2@」でもよい。

◆置き換え先での置き換え元の正規表現の参照

文字列1(置き換えの元の文字列)で、「\(正規表現\)」は、文字列2(置き換え 先の文字列)では、「\1」、「\2」、「\3」で参照可能である。 数は、何番目の正規表現かを示す。 何度でも使ってもよい。

例:


----------------------------------------------------------------------
% ls c*.c | cat [←]
cont-0.c
cont-1.c
cont-2.c
% ls c*.c | sed 's/cont-\([0-9]\)/\1-continue/' [←]
0-continue.c
1-continue.c
2-continue.c
% []
----------------------------------------------------------------------

◆sedのオプション

-e 'command'
コマンドの実行
-n
明示的に p としない限り表示しない。
-f filename
filename のファイルに含まれているコマンドを実行する。

◆sedスクリプトからの利用

sed のスクリプトは、csh や sh の中で書くことが多い。
----------------------------------------------------------------------
#!/bin/csh -f

sed -e 'sed-com1' \
    -e 'sed-com2' \
    -e 'sed-com3' ... | \
shell-com1 | \
shell-com2
----------------------------------------------------------------------
csh では、行末キャンセルが必要になることが多い。

sh では、「''」の中に改行を含められる。 パイプの後の行末キャンセルも不要である。

----------------------------------------------------------------------
#!/bin/sh

sed -e '
sed-com1
sed-com2
sed-com3
' ... |
shell-com1 |
shell-com2
----------------------------------------------------------------------
コマンドラインやシェル・スクリプトの中で使う時には、 sed の命令を引用符「''」で括るとよい。 引用符「''」は、シェルによりはがされ、 sed コマンド本体には渡されない。

引用符「''」で括らなかった時には、「$」や「*」など が、シェルによって解釈され、sed には渡されない。

シェル・スクリプトで、引数やシェル変数を参照したい時には、 sed のコマンド全体を引用符「''」で 括り、その中で変数の部分だけ引用符を解除する。

----------------------------------------------------------------------
#!/bin/csh -f

sed -e 's/aaa/'$1'/' ...
----------------------------------------------------------------------
変数に空白が含まれている可能性がある時には、さらに変数を「""」で括る。
----------------------------------------------------------------------
#!/bin/csh -f

sed -e 's/aaa/'"$1"'/' ...
----------------------------------------------------------------------
「#!」を使う時には、-f を付ける。
----------------------------------------------------------------------
#!/bin/sed -f
sed-com1
sed-com2
sed-com3
----------------------------------------------------------------------
コマンドの場所は、which コマンドで調べる。

■パタン検索・処理言語awk

C言語に似た構文が使える。
% awk 'program' input > output [←]
例:ls -l の結果のうち、先頭が「-」で始まるものについて、ファイル名と 大きさだけを表示する。
% ls -l | awk '/^-/{printf("%d %s\n",$5,$9);}' [←]

◆awkのコマンドの形式

<パタン> { <コマンド> }
<パタン> { <コマンド> }
<パタン> { <コマンド> }

<パタン>の指定方法

/regexp/
そのregexp(regular expression,正規表現)がある行
$1 == 10
第1フィールドの値が 10
$2 ~ /regexp/
第1フィールドにそのregexp(regular expression,正規表現)がある行
length($2) == 0
第2フィールドの長さが0
BEGIN
最初
END
最後
<パタン>を省略すると、全ての行の意味になる。

<コマンド>で書けること。

整数と数字だけの文字列は、型変換が行われる。

----------------------------------------------------------------------
% awk 'BEGIN { print 1 + "2" ; }' /dev/null [←]
3
% []
----------------------------------------------------------------------

◆フィールド

標準では、タブや空白で区切られた文字列をフィールドとして $1, $2, $3 ... でアクセスできる。 $0 は、行全体がアクセスできる。

例:合計


----------------------------------------------------------------------
% awk '{s+=$1 } END {print s}' [←]
1[←]
2[←]
3[←]
^D
6
% [←]
----------------------------------------------------------------------

フィールドの区切り文字は、-F: で変更できる。 パスワード・ファイルをアクセスする時には、-F: を使う。


----------------------------------------------------------------------
% ypcat passwd | awk -F: '$1=="yas" {print}' [←]
yas:EfFxLk3yV3Ovx:1231:40:Yasushi SHINJO,[os],5163,?:/home/lab2/OS/yas:/usr/bin/tcsh
% []
----------------------------------------------------------------------

◆組込み変数

NR
Number of records。現在の行数。
NF
Number of fields。フィールドの数
他にもある。くわしくは、man awkを参照。

◆演算子

いくつかのC言語の演算子がつかえる。優先度順。

= += -= *= /= %=
||
&&
!
> >= < <= == !=
+ -
 / %
++ --

C言語にはない演算子
"string" ~ /regexp/
"string" が /regexp/ にマッチする
"string" !~ /regexp/
"string" が /regexp/ にマッチしない

◆制御構造

if( cond )
{
   statement1;
}
else
{
   statement1;
}

for( expression1;  condition; expression2 )
{
    ... break ;
    ... continue ;
}

for( variable in array )
{

}

while( condition )
{
    ... break ;
    ... continue ;
}

next;	次の行へ
exit;	END パタンへ

◆配列(連想配列)

添字には、数の他に文字列も使える。

----------------------------------------------------------------------
% awk 'BEGIN { a["apple"] = 10; a["orange"] = 20; } { print $1,a[$1];}' [←]
apple[←]
apple 10
^D
% []
----------------------------------------------------------------------

◆awkの組込み関数

index(s1,s2)
文字列 s1 の中で s2 の位置。先頭は、1。存在しなければ 0 が返る。
substr(s,m,n)
文字列 s の m番目から n 文字切り出す。
length(s)
文字列 s の長さ。
sprintf(fmt,...)
指定されたfmtに従って...を文字列にする。
split(s,array,sep)
文字列 s を、sep で分解し、array[1]..array[n] に入れる。nを返す。
他にもある。詳しくは、man awk を参照しなさい。

◆awkのオプション

-Fc
フィールドの区切りを c にする。
-f filename
filename のファイルに含まれているコマンドを実行する。

◆awkスクリプトからの利用

awk のスクリプトは、csh や sh の中で書くことが多い。
----------------------------------------------------------------------
#!/bin/csh -f

awk '{ \
  ... \
} ' ... | \
shell-com1 | \
shell-com2
----------------------------------------------------------------------
csh では、行末キャンセルが必要になることが多い。

sh では、「''」の中に改行を含められる。 パイプの後の行末キャンセルも不要である。

----------------------------------------------------------------------
#!/bin/sh

awk '
{
  ...
  ...
}' ... |
shell-com1 |
shell-com2
----------------------------------------------------------------------
コマンドラインやシェル・スクリプトの中で使う時には、 awk の命令を引用符「''」で括るとよい。 引用符「''」は、シェルによりはがされ、 awk コマンド本体には渡されない。

引用符「''」で括らなかった時には、「$」が シェルによって解釈され、awk には渡されない。

シェル・スクリプトで、引数やシェル変数を参照したい時には、 awk のコマンド全体を引用符「''」で 括り、その中で変数の部分だけ引用符を解除する。

----------------------------------------------------------------------
#!/bin/csh -f

awk -e 'BEGIN{ x="'$1'"; } ... ' ...
----------------------------------------------------------------------
「#!」を使う時には、-f を付ける。
----------------------------------------------------------------------
#!/usr/bin/awk -f
BEGIN {
...
}
{
...
}
----------------------------------------------------------------------
コマンドの場所は、which コマンドで調べる。

■その他

その他に Unix でよく使われるスクリプト言語としては、次のようなものがあ る。 Ruby は、情報学類の卒業生の作品である。

■練習問題と課題

★練習問題 82 newer

指定されたファイルよりも新しいファイルを表示するシェル・スクリプトを作 りなさい。

% newer filename [←]
ヒント:ls -t で、最終更新時刻の順に表示する。 sed で、先頭から引数の現れる行まで表示する。

★練習問題 83 ファイルのn行目からm行目までの表示

引数として2つの数 n, m 、および、ファイル名を取り、そのファイルの n 行めから m 行目までを表示するシェル・スクリプトを作りなさい。 たとえば、次の例では、ファイルの 10 行目から 20 行目までを表示する。

% show-n-m 10 20 filename [←]
この課題では、sed、または、awkを使いなさい。 head コマンドと tail コマンドを使ってはいけない。

ヒント:sed で、行の範囲の指定を使うか、awk で、行番号を保持している変 数 NR を見て判定する。

★練習問題 84 ファイル名変更のスクリプトを出力するスクリプト

Unix のシェルでは、「*」は、シェルが解釈するので、mv コマンドで、パタ ンを使ったファイル名の変更はできない。たとえば、全ての C 言語のファイ ルをC++言語のファイルにしたいとする。次のよう打っても、 「*」はシェルに解釈され、そのようなファイルは存在しないというエラーに なる。

% mv *.c *.cc [←]
「''」で括っても、mv コマンドはそれを解釈すること はできない。

% mv '*.c' '*.cc' [←]
このような目的を果すようなシェル・スクリプトを生成するシェル・スクリプ ト make-mv-command を作りなさい。

% ls *.c [←]
echo-server-fork.c    echo-server-select.c
% make-mv-command *.c > run [←]
% cat run [←]
mv echo-server-fork.c echo-server-fork.cc
mv echo-server-select.c echo-server-select.cc
% sh run [←]
% ls *.c [←]
echo-server-fork.cc    echo-server-select.cc
% []
このような mv は、危険なので、一度命令をファイルに落として内容を確認し てから実行するとよい。

ヒント:sed の正規表現の置き換え sed の 置き換え先での置き換え元の正規表現の参照 の機能を使って、ファイル名を作る。

★練習問題 85 関数名の表示

C言語のファイルで使われている関数名を表示するスクリプトを 作りなさい。

% funcs file.c [←]
close
main
open
printf
read
write
% []
この課題では、完璧を目指さなくてもよい。いくつかの仮定を 入れて、スクリプトを簡単にしなさい。たとえば、 関数は、1行に1つしか現れないと仮定してよい。

ヒント:sed で、「xxx()」のようなパタンを拾って出力する。1つの sed の コマンドで完了させるのではなくて、複数の sed のコマンドを使う。

重複を外すには、sort して uniq すればよい。

★練習問題 86 名前を指定してのkill

kill コマンドは、PID (番号)を指定してプロセスを殺す。PID ではなく、プ ロセスが実行しているプログラム名を指定してプロセスを殺すようなスクリプ トを作りなさい。

% kill-name echo-server [←]
% []

なお、csh, tcsh, bash 等の内部コマンドでは、ジョブ・コントロールの機能 を使って、名前でジョブ(プロセスのグループ)を殺す機能がある。

★練習問題 87 awkによる単語のカウント

awk を使って wc コマンドと似たスクリプトを作りなさい。 すなわち、ファイルの行数、単語数、バイト数を表示しなさい。 ただし、ここで単語とは、空白で区切られた英単語とする。

ヒント:単語の数は、NFを数える。 バイト数は、length() を使う。

★練習問題 88 awkによる単語の出現回数

awk を使って単語の出現回数を表示するスクリプトを作りなさい。ただし、こ こで単語とは、空白で区切られた英単語とする。

ヒント:連想配列を使う。

★練習問題 89 awkによるfinger

awk を使って finger コマンドと似たスクリプトを作りなさい。すなわち、引 数としてログイン名を取り、passwd ファイルを検索して整形して表示しなさ い。

----------------------------------------------------------------------
% a-finger yas [←]
Login name: yas                         In real life: Yasushi SHINJO
Office: [os],  x5163                    Home phone: ?
Directory: /home/lab2/OS/yas            Shell: /usr/bin/tcsh
% []
----------------------------------------------------------------------
ヒント:パスワード・ファイルは、ypcat passwd の出力を使う。

★練習問題 90 学籍番号の表示

ログイン名から学籍番号を調べるスクリプトを作りなさい。

----------------------------------------------------------------------
% sno yas [←]
998765	yas	Yasushi Shinjo
% []
----------------------------------------------------------------------

★練習問題 91 行番号付き表示

システム・プログラムのWWWページにあるプログラムには、行番号が表示さ れている。これと同じように行番号を付けて表示するスクリプトを作りなさい。

----------------------------------------------------------------------
% cat today.c [←]
#define TODAY   "Monday"
main()
{
        printf("Today is %s.\n",TODAY );
}
% nprint today.c [←]
   1:   #define TODAY   "Monday"
   2:   main()
   3:   {
   4:           printf("Today is %s.\n",TODAY );
   5:   }
% []
----------------------------------------------------------------------
ヒント:awk の NR を使って行番号を付ける。 awk に与える前に、expand で、タブを展開するとよい。

★練習問題 92 行番号の削除

システム・プログラムのWWWページにあるプログラムには、行番号が表示さ れている。WWWブラウザでそのページを保存した結果から行番号が付いてい るC言語のプログラムだけを抜き出すようなシェル・スクリプトを作りなさい。

ヒント:「数字:」というパタンの行について処理する。 sed で行の頭から何文字か削るか、 awk の substr() で取り出す。

★練習問題 93 電子メールの解析

電子メールのヘッダのうち From:、To:、Cc:、Date:、Subject だけを表示す るようなスクリプトを作りなさい。

電子メールの本文にある From: のような文字列は無視しなさい。 電子メールのヘッダと本文の区切りは、空行 /^$/ でわかる。

継続行に対応しなさい。たとえば、次のようになっていた場合、To: としては 3行表示する。

----------------------------------------------------------------------
To: who1@domain.com,
    who2@domain.com,
    who3@domain.com
----------------------------------------------------------------------
行の先頭が空白かタブならば、継続行である。その時には、前に設定したヘッ ダを使う。

★練習問題 94 スクリプト言語自由課題

その他、上であげた課題と同程度に難しい課題を自由に設定してawk や sed を使って解きなさい。 あるいは、perl や ruby など他のスクリプト言語を使って上の課題を解きな さい。