(2017年度) 学生実験1 : スクリプトプログラミング






更新情報
  • [2016-03-30] 昨年度の資料を参考に、コンテンツ検討中。
  • [2016-03-31] 仮アップ。texテンプレート修正。課題調整中。
  • [2016-04-10] 実験1全体のシラバス掲載。







実験の進め方

内容と達成目標

今回のテーマ「スクリプトプログラミング」では、「そもそもそんなコマンドがあること自体知らなかった」「作業を自動化するための論点が分からなかった」等の理由でこれまで機械的な作業を手作業でやっていた仕事(の一部)を自動化する事が目標です。具体的には以下の通り。

  • (達成目標0:LaTeXの環境やコマンドを適切に利用してレポートを作成できる。)
  • 達成目標1:シェル(sh/bash)スクリプトを読め、動作を理解する。
  • 達成目標2:シェル上での正規表現を理解する。
  • 達成目標3:シェルで繰り返し作業を使う。
  • 達成目標4:(シェルで簡単なログ解析をスクリプトで組み)、その結果を gnuplot 等でグラフ作成する。

コメント

今回の内容に限らず、講義/実験でやった内容は何らかの形でサマリや例題を残す等して、忘れたときに「ここを見れば分かる!」というノートを作成するようにしましょう(今回の実験では、作成したスクリプト内に適切にコメントを書いておけば、そのときとある動作をどのように実現したのかが分かり、作業記録としても使えます)。

また、適宜参考文献や google を駆使し、様々な利用例を読み、実行しなくては自由自在に操れるようにはなれません。 シェルスクリプトに限らず、Perl/PHP/Ruby/Pythonなど、様々なスクリプト言語がありますので、時間のあるときにでもそれらについても調べてみてください。

実験1,2のレポートは、基本的には1週間でレポートを仕上げ続ける形になるため、貯めることなく毎週仕上げるようにしよう。今回の実験では作成日を含めての1週間になるが、途中でタイムオーバーになることが想定できるならば「できる範囲」で期限内に仕上げ、努力したことが読み取れるように報告書を作成してください。特に、独自課題に関しては完成までしなくても(途中であっても)構わないので、取り組んだことが分かるように報告書に記述すること(報告書に記述の無いことは評価できません)

進め方
  • テキスト・補助教材:本ページや参考文献参照(サンプルソースもあります)
  • 作業スタイル:個人作業(1人1レポート)
課題と提出方法
  • 1人1レポート提出。Level 1 に取り組むか、独自課題に取り組め。課題設定例はオプション課題例に用意していますが、これ以外でも構いません。独自課題に関しては完成までしなくても(途中であっても)構わないので、取り組んだことが分かるように報告書に記述すること(報告書に記述の無いことは評価できません)。
  • 前述したいずれかの取り組み方で課題をやり、レポートとしてまとめよ。Level毎に以下の項目を報告書としてまとめること。ただし、全てを個別に報告する必要はなく、例えば「実験結果と考察」という節を設けても構わない。
    • 課題説明
    • 作成したスクリプト本体の解説
    • 実験結果
    • 考察
  • レポートの1枚目には以下の項目を記述すること。
    • 実験テーマ名、担当教員名
    • 氏名、学籍番号
    • 実験日、提出期限日、提出した日
    • オプション課題に取り組んだ場合には、課題毎にLevel X-1、Level X-2、、のようにインデックスを付け、そのインデックスを1枚目にも明記すること(1枚目を見ただけでオプション課題をやったかどうかが分かるように書くこと)。
  • 提出物
    • [提出物1] レポートは LaTeX で作成し、PDFファイルとして作成する事。提出物には LaTeX ファイル一式(tex, 図ファイル等全て)を含む事。ファイル名の例:e165700.pdf
    • [提出物2] 作成したスクリプト一式
  • 上記2点を shell サーバにアップロードし、tnal@ie.u-ryukyu.ac.jp 宛に以下のタイトルで報告メールを送信する事。報告メールが無い場合には減点となります(ホウレンソウ)

    火曜日のクラス
     Subject: (info1/shell/tue) e1657xx
    
    金曜日のクラス
     Subject: (info1/shell/fri) e1657xx
    


    (アップロード例) 以下は ~/jikken1/shell/ に提出物を保存している場合の例です。単にコピペするだけでは動きません!アカウント名(e1657xx)を間違えないように注意する事!,br> 修正レポートを再提出する場合も同じコマンドで上書き提出できます。 rsync 自体の説明は例えばここを参照してください。

    注意点1: 両方(送信元&送信先)ともディレクトリ名の後ろにスラッシュを付けるのを忘れずに!

    % cd   (ホームディレクトリに移動)
    % rsync  -auve  ssh  ~/jikken1/shell/  e1657xx@shell.ie.u-ryukyu.ac.jp:/home/teacher/tnal/jikken1-tue/e1657xx/
    % rsync  -auve  ssh  ~/jikken1/shell/  e1657xx@shell.ie.u-ryukyu.ac.jp:/home/teacher/tnal/jikken1-fri/e1657xx/
    
    # 注意:
    # ・コピペだけでは動きません!アカウント名、ローカルディレクトリ、提出先ディレクトリを適宜修正ください。
    # ・スラッシュの有無で引数の意味が大きく異なります。
    # ・timed out する場合には「ssh」を「'ssh -4'」にしてみよう。
    
    
  • 提出期限:実験日の7日後まで(4/11のクラスの期限は4/18、4/14のクラスは4/21)。
評価基準
  • レポートでは指定した課題をこなしている(提出している)事を前提に基準点を設け、以下の項目に沿って加点・減点を行う。
  • オプション課題等をやらない場合の基準点は60〜80点程度である。
  • テーマに関連する事柄を自主的にやってきた場合には積極的に加点対象対称として採点しますので、各自創意工夫して加点となるよう取り組むことを期待します。なお、独自課題に取り組む場合には Level 1 は省略OK。さらに基準点を加点(+3〜+10点程度)します。ただし、課題の目的や達成方法を必ず書くこと。結果だけ掲載したものは「レポート(報告書)」としては不十分です。
  • 独自課題については友人らと一緒にやっても構いませんが、その際にはレポートに分担等を示すこと。
  • 採点後、優秀なレポートを期間限定で公表します。

加点
  • 独自に行った関連課題に関する報告(積極性)。
  • 実験中に示した手法ではなく、自力で探し出した異なる手法で問題を解決した場合(積極性・実践性・創造性)。
  • 手法の利点・欠点や限界等に関して自主的にトライした結果・考察が示されている場合(積極性・専門性)。
  • レポートの見せ方に関する工夫など(コミュニケーション能力)。
  • その他、本テーマに関連する実験的側面上「積極性・実践性・創造性・コミュニケーション能力」等の観点から評価できるもの。
減点
  • 先行研究や事例、レポート、web等を参照しているにも関わらず、それらを参考文献として参照していない場合(一般倫理)。
  • 論理的矛盾が見られる場合(論理性)。
  • 結果を示さずに考察のみを記載してたり、結果に対する考察に整合性が見られない場合(コミュニケーション能力)。
  • タイプミスや可読性の欠如、数式等記号の印字ミス、提出期限の遅れ等、多方面に関して大きな誤りではない場合。



0. 事前準備

自分のノートPC(Mac OS X)を利用する場合は1を、 FedoraやCentOSといったLinux系OSを利用する場合は2を準備してください。

  1. Mac OS X での準備
    1. Xcode, Command line toolsをインストール。

      OSを再インストール等していなければ、インストール大会でインストール済みです。インストールされていれば、/Developer/Applications/Xcode.app があります。無ければ先程のリンク先を参照してインストールしてください。

    2. gnuplotをインストール。
    3. Graphvizをインストール。
    4. 各種アプリの動作確認。
  2. Linuxでの準備
    1. gnuplot をインストール(参考:yumとは?)。
      % which gnuplot      #gnuplotがインストールされているかを確認
      % sudo yum install gnuplot #yum でパッケージインストール
      
    2. Graphviz をインストール(参考: CentOS
    3. 各種アプリの動作確認。

各アプリケーションの説明は参考文献・サイトを参照。


1. shell script の基礎

C/Java等のコンパイラ言語で書くほどではないが

  • 日頃良く使う一連のコマンド群をより簡単に実行したい。
  • ファイルやディレクトリを手軽に扱いたい。
  • 複数のファイル/ディレクトリに対して同一の処理を実行したい。
  • 自動化したい。定期的に実行したい。
等、コンピュータにちょっとした仕事をやらせる方法の一つとして「スクリプト言語 / 軽量プログラミング言語(Lightweight Language, LL)」を利用したプログラミングがある。具体的な例としては、Perl や PHP を使った WWW 上の掲示板のような目に見えやすいものから、OSの起動処理など、探してみると実に様々な箇所でスクリプト言語が利用されている。 今回は、LL と比べると非力ではあるが、スクリプト言語の基本的な考え方を学ぶために多くの Linux 系 OS のデフォルトシェルとして利用されている sh (bash) シェルを利用したシェルスクリプトの作成方法について学ぶ。



1.0. 諸注意

スクリプトプログラミングに限った話ではありませんが、ファイルやディレクトリを(上書き)作成・編集・削除といった処理の自動化を行いますので、誤ってファイル等を削除してしまう可能性があります。スクリプトを実行する際には十分に気をつけること。

基本的にはバックアップを取りながら作業をすることが望ましいですが、それ以外にも以下のような対策が考えられます。

  • echo(C言語のprintf()相当)でコマンド列や引数を出力させて確認し、出力結果が想定した通りであれば echo を削除して実行する。
  • rm や mv コマンドを実行する際には -i オプションを付けて確認し、動作が想定した通りであればそれらを削除する。
  • 仮想OS環境を構築し、そこでやる。(ただし、外部リソースをwritableでmountしたままだとそこに影響が出ることもある)



1.1. bash シェルの実行

今回使用するシェルは「/bin/sh (Borne Shell)」です。
正しくパスが設定されているならば、「sh」と入力しただけでも /bin/sh を起動することが出来ますが、念のために以下のようにして実行ファイルの場所とバージョンを出力確認してください。
ここで、/bin/sh 以外のパスが出力されたり、tcsh/zsh 等異なるシェル名が出力されたら、alias や .tcshrc/.bashrc 等の設定を確認してみてください。

% which sh     # sh のある場所を確認
% sh --version # sh のバージョン確認

GNU bash であることが確認できたら、sh を実行しましょう。

% sh
sh-3.2$      # <--プロンプトが変わっているのを確認
sh-3.2$ exit # exit でシェルを終了できます
% 

1.2. シェル変数への値の代入と参照

C言語の場合、1+2 の結果を計算し、結果を確認するためには例えば以下のようなソースを書くことになります。(ここでは include や main() は省いています)

# C言語での 1+2 の例
int a, b, c;
a = 1; b = 2;
c = a + b;
printf("c=%d\n",c);
同様のことをシェルで実行するには以下のように書きます。
# (例1.2) シェルスクリプトでの 1+2 の例
# sh を起動した状態で入力してください
a=1
b=2
c=`expr $a + $b`
echo c=$c
ポイント
  • 変数に値を代入するにはname=valueという形式で書く(=の前後にスペースがあるとNG!)。
  • 変数を宣言する必要は無い。
  • int や char といった値の型を宣言する必要も無い。
  • 参照時には$nameまたは${name}として$を頭に付ける必要がある。
  • printf() のような出力にはechoを使う。
  • `バック・クォーテーション`で囲うと、その実行結果に置き換えられる。
  • exprコマンドで四則演算が可能(演算子の前後にスペース必須)。

1.3. 特殊文字の利用(エスケープシーケンス)

変数には任意の文字列を代入できますが、一部の特殊文字(; & ( ) | ^ < > ? * [ ] $ ` " ' { } [TAB] [SPACE])は、そのままでは代入することが出来ません。これらの特殊文字を「ただの文字」として使うにはエスケープシーケンスにより表現する必要があります。

# (例1.3) エスケープの例
$ a=This is example
sh: is: command not found
$ a=This\ is example
sh: example: command not found
$ a=This\ is\ example
$ echo $a
This is example

$ a="This is example"
$ echo $a
This is example

$ b=1
$ c="$a, b=$b"
$ echo $c
This is example, b=1

$ c='$a, b=$b'
$ echo $c
$a, b=$b

$ c="$a, b=\$b"
$ echo $c
This is example, b=$b

ポイント
  • [SPACE] は区切り文字とみなされる。
  • バックスラッシュ(\)は、次に続く1文字をただの文字とみなす。
  • ダブル・クォーテーション("hoge")によるエスケープでは、変数($value)、バック・クォーテーション(`hoge`)、バックスラッシュ(\)以外をただの文字とみなす。
  • シングル・クォーテーション('hoge')によるエスケープでは、全ての特殊文字がただの文字としてみなされる。

1.4. シェルスクリプト(スクリプトファイル)の作成と実行

例1(シェルスクリプトでの 1+2 の例)をシェルスクリプトとして利用するには、以下のように記載されたファイルを作成し、実行する。

#!/bin/sh
# (例1.4) シェルスクリプトの例: example2.sh
a=1
b=2
c=`expr $a + $b`
echo c=$c

スクリプトファイルの実行方法は2通りある。
1つ目は、ファイルに対して実行許可を与え、ls 等のコマンドと同様にファイル名を実行ファイル名として利用する。
2つ目は、シェルに対する引数としてファイルを指定して実行する。

# (例1.4.1) 1つ目の実行方法
$ ls -l example2.sh
-rw-r--r--   1 tnal  tnal  49 Jun  5 22:15 example2.sh
$ chmod u+x example2.sh
-rwxr--r--   1 tnal  tnal  49 Jun  5 22:15 example2.sh
$ ./example2.sh
c=3


# (例1.4.2) 2つ目の実行方法
$ sh example2.sh
c=3
演習課題1: 以下の機能を満たすシェルスクリプト (exl1.sh) を作成せよ。
  • a=8, b=2 とし、四則演算を計算する。
  • 実行結果は以下のように出力すること。
    prompt> ./ex1.sh
    
    # 演習課題1の実行結果
    a=8, b=2
    a+b=10
    a-b=6
    a*b=16
    a/b=4
    
    prompt>
    

1.5. 特殊な変数

先の例で見た通り、変数を参照する際にはname=valueとして値を設定後に$nameとして参照する。 表1.5 に示す変数はシェルスクリプト実行時に自動的にセットされている(手動で設定する必要が無い)特殊な変数である。

表1.5. 特殊な変数
$0スクリプトの名前
$1,$2, ..., $9スクリプトに渡されたx番目の引数
$#スクリプトに渡された引数の個数
$*スクリプトに渡された全引数のリスト
$?直前に実行したコマンドの終了ステータス

表1.5.2: 終了ステータス
0コマンドが正常終了
1以上の任意の値リダイレクトや展開に失敗して終了
1〜125コマンドが異常終了
126コマンドは存在しているが、実行可能な状態ではない
127コマンドが存在していない
129〜255シグナルを受信したためにコマンドが強制終了された

#!/bin/sh
# (例1.5) 特殊変数の利用例: example1.5.sh
echo "引数の数     : $#"
echo "スクリプト名 : $0"
echo "第1引数      : $1"
echo "第2引数      : $2"
echo "引数一覧     : $*"
echo "プロセスID   : $$"

# 実行例
sh-2.05b$ ./example1.5.sh 1 2 3 4 
引数の数     : 4
スクリプト名 : ./example1.5.sh
第1引数      : 1
第2引数      : 2
引数一覧     : 1 2 3 4
プロセスID   : 2548
sh-2.05b$ 
# (例1.5.2) 終了ステータスの確認
prompt> ls
prompt> echo $? #正常終了時の値を確認

prompt> ls hogege
prompt> echo $? #異常終了時の値を確認
演習課題2: 以下の機能を満たすシェルスクリプト (ex2.sh) を作成せよ。
  • 演習課題1の例: ex1.sh
  • ex1.sh を拡張(複製して ex2.sh と名前変更)する。
  • ex1.sh では演算対象の a,b をスクリプト内で設定していたが、この2つの値を実行時の引数から設定するようにする。
  • 実行結果は以下のように出力すること。以下では2例示しているが、重要なのは変数 a,b をスクリプトへの引数として実行時に指定できるようにする事である。
prompt> ./ex2.sh 8 2

# 参考出力結果(ex2)
a=8, b=2
a+b=10
a-b=6
a*b=16
a/b=4

prompt> ./ex2.sh 10 5

# 参考出力結果(ex2)
a=10, b=5
a+b=15
a-b=5
a*b=50
a/b=2

prompt>

1.6. 条件分岐(if, case, [])

シェルスクリプト中では if, case を使った条件分岐が可能で以下のような比較演算子(文字列)が記述可能である。標準的な書式としては以下のように記述される。

#!/bin/sh
# (例1.6.1) if文の例: example1.6.1.sh
#引数チェック例
if [ $# -eq 2 ] ; then
    str1=$1
    str2=$2
else
    echo "Usage: prompt> $0 arg1 arg2"
    exit 1
fi

#文字列比較例
echo "str1=$str1, str2=$str2"
if [ $str1 = $str2 ] ; then
    echo "str1 = str2"
else
    echo "str1 != str2"
fi

#実行例
prompt> ./example1.6.1.sh
Usage: prompt> ./example1.6.1.sh arg1 arg2
prompt> ./example1.6.1.sh hoge fuga
str1=hoge, str2=fuga
str1 != str2

#!/bin/sh
# (例1.6.2) case 文の例: example1.6.2.sh
if [ $# -eq 1 ] ; then
  str1="$1"
else
  echo "Usage: prompt> $0 arg1"
  exit 1
fi

case $str1 in
  hoge)
    echo "str1=hoge"
    ;;
  fuga)
    echo "str1=fuga"
    ;;
  *)
    echo "str1 has bad value (str1=$str1)."
    exit 1
esac

#実行例
sh-2.05b$ ./example1.6.2.sh     
Usage: prompt> ./example1.6.2.sh arg1
sh-2.05b$ ./example1.6.2.sh fuga
str1=fuga
sh-2.05b$ ./example1.6.2.sh 1   
str1 has bad value (str1=1).

ポイント
  • if [ 評価式 ] の部分は全てのコマンドをスペースで区切る必要がある。
  • スクリプトを途中で終了する場合にはexitを使う。
  • 評価演算子は評価する値の型によって記述方法が異なる(表1.6.x)。

表1.6.1: 文字列評価式
str1 = str2文字列str1とstr2は一致する
str1 != str2文字列str1とstr2は一致しない
-z str文字列strは空(null)である
-n str文字列strは空(null)ではない


表1.6.2: 数値評価式
int1 -eq int2整数int1とint2は等しい(EQual: int1==int2)
int1 -ge int2整数int1はint2以上(Greater than or Equal: int1>=int2)
int1 -gt int2整数int1はint2より大きい(Greater Than: int1>int2)
int1 -le int2整数int1はint2以下(Less than or Equal: int1<=int2))
int1 -lt int2整数int1はint2より小さい(Less Than: int1<int2)
int1 -ne int2整数int1とint2は等しくない(Not Equal: int1 != int2)


表1.6.3: ファイル評価式
-d filefile はディレクトリである
-f filefile は通常ファイルである
-r filefile は読み出し可能である
-s filefile の長さは0バイトではない
-w filefile は書き込み可能である
-x filefile は実行可能である

演習課題3: 以下の機能を満たすシェルスクリプト (ex3.sh) を作成せよ。
  • int型の引数を2個取り、それぞれ int1, int2 として設定(保存/代入)する。
  • int1 と int2 を数値比較し、以下のように出力すること。
  • (引数が2個以外の場合(0個、1個や、3個以上)には使い方を出力して終了する。)
prompt> ./ex3.sh

 Usage: prompt> ./ex3.sh int1 int2

prompt> ./ex3.sh 10 20

 int1=10, int2=20
 int1 is less than int2.

prompt> ./ex3.sh 10 10

 int1=10, int2=10
 int1 is equal to int2.

prompt> ./ex3.sh 20 10

 int1=20, int2=10
 int1 is greater than int2.


1.7. ループ制御(for, while)

for文による繰り返し処理の書き方は、C言語と比べると大きく異なります。 通常、シェルスクリプトにおける for 文は「リスト内の各要素に対して実行する繰り返し処理」に用います。

#!/bin/sh
# (例1.7.1) for文による繰り返し処理の例: example1.7.1.sh
list="1 2 3 4 5"
for value in $list
  do
  echo value=$value
done

# 実行例
sh-2.05b$ ./example1.7.1.sh 
value=1
value=2
value=3
value=4
value=5
sh-2.05b$

C言語の for に近い表現は while です。

#!/bin/sh
# (例1.7.2) while による繰り返し処理の例: example1.7.2.sh
a=0
while [ $a -lt 5 ]
  do
  echo "a=$a"
  a=`expr $a + 1`
done

# 実行例
sh-2.05b$ ./example1.7.2.sh 
a=0
a=1
a=2
a=3
a=4
sh-2.05b$
ポイント
  • for 文における「リスト」は半角スペースか改行で区切る必要がある。
  • 繰り返し処理はdo ... doneで囲う必要がある。
  • i++ のような記述が出来ないため、n回繰り返す処理を書きたい場合にはi=`expr $i + 1`のように自前で回数計算する必要がある。




1.8. ヒア・ドキュメント

コマンドに、(改行コード込みの)複数行の文字列を標準入力させることが出来ます。 これにより、インタラクティブなアプリケーション(シェルやgnuplotなど)に対して、複数のコマンドを実行させることが出来ます。

#!/bin/sh
# (例1.8) ヒア・ドキュメントの例: example1.8.sh
gnuplot <<ENDHOGE
set terminal svg
set output "test.svg"
set xlabel "Time"
set ylabel "Value"
set title "y=sin(x)"
plot sin(x)
ENDHOGE

#上記はSVG形式での出力例。
#EPS形式で出力したいなら "set terminal postscrit eps" 等とする。
#gnuplotの実装によっては最初からpdf出力できるものも。
#詳細は"help terminal"でヘルプで確認しよう。

# 実行例
prompt> ./example1.8.sh
prompt> ls
example1.8.sh  test.svg
prompt> open test.svg

ポイント
  • gnuplot 等への「入力スクリプト」そのものを作り出すスクリプトを作成することで、外部コマンドを利用した高機能なシェルスクリプトを作成できる。
  • 例えば、上記例での sin(x) を引数から代入させれば、標準関数の作図が1コマンドで実現できる(オプション課題: 任意の gnuplot スタイル数式をグラフ化するスクリプトを作成せよ)。




1.9. 正規表現

パターンを記述するには正規表現を使います。代表的な正規表現とその例を以下に示します。下記以外にもいろいろありますので、パターンマッチングする際に勉強してみよう。

記号意味
.改行文字以外の任意の1文字に一致。
*「直前の1文字(or正規表現)」の0回以上の繰り返しに一致。例えば、メールアドレスのように @ の前に必ずアカウント名が来るなら「.*@」と書ける。
\+「直前の1文字」の1回以上の繰り返しに一致。
^行の先頭に一致。例えば ^a と書くと「aから始まるパターン」を表現。
$行の末尾に一致。例えば a$ と書くと「aで終わるパターン」を表現。
[]括弧内の任意の1文字に一致。例えば [abc] なら「a or b or c を含むパターン」を表現。[0-9], [m-y], [A-D] のように範囲を指定することも可能。
|前後のパターンいずれかに一致。例えば「[0-9]|[a-z]」なら「0〜9か、もしくはa〜zのいずれかに一致するパターン」を表現。
\t or [[:space:]]タブに一致。[[:space:]]はタブとスペースに一致。\tが効かない実装もあるので注意。




1.10. sedによるテキスト処理(エディタ)

参考: sed入門(ドットインストール)

特定パターンに合致する部分を抽出・フィルタリング・編集することが可能。「パターン」はテキストそのもので書いたり、前述の正規表現を用いることができる。

例1: 学籍番号数字6文字(1657xx)をメールアドレス(e1657xx@ie.u-ryukyu.ac.jp)に変換する。
prompt> echo "165701\n165702" | sed -e "s/.*/e&/g"
e165701
e165702
prompt> echo "165701\n165702" | sed -e "s/.*/e&@ie.u-ryukyu.ac.jp/g"
e165701@ie.u-ryukyu.ac.jp
e165702@ie.u-ryukyu.ac.jp
  • -e: 正規表現を使うというオプション指定。
  • "s/1/2/g": 「1」が現れる度、「2」に置き換えるという置換指定。
  • .*: 正規表現(=全て)
  • &: マッチしたテキスト全て。ここでは .* を指定していたため、全てのテキストが & に保存されている。
例2: 必要な箇所だけを抽出し、フォーマットを変更して出力する。
prompt> date
2015年 4月 9日 木曜日 21時34分27秒 JST
prompt> date | sed -e "s/\([0-9]*\)年.*/\1/g"
2015
prompt> date | sed -e "s/\([0-9]*\)年 \([0-9]*\)月 \([0-9]*\)日.*/\2-\3-\1/g"
4-9-2015
  • s/\(pattern1\)\(pattern2\)/\1/g: \(と\)でパターンの始まりと終わりを指定。左の例ではpattern1とpattern2の二つがある。マッチしたpattern1を使いたいなら、\1として参照できる。pattern2は\2として参照できる。




1.11. gnuplotによるグラフ描画

Gnuplotは2次元/3次元座標を用いたグラフ生成ツールとして使用されている。

例: シンプルな線グラフの例。
  • 1行に「その日のツイート数」をline.dataとしてデータを保存しており、その推移を折れ線グラフで描画したいならば、以下のように実行する。
    prompt> gnuplot
    gnuplot> set terminal pdf
    gnuplot> set output "line.pdf"
    gnuplot> set xlabel "Day"
    gnuplot> set ylabel "Number of tweets"
    gnuplot> set yrange [0:100]
    gnuplot> plot "line.data" with line
    prompt> open line.pdf
    
  • 上記では gnuplot を起動した上で一行ずつコマンド入力する例だが、これらのコマンドをファイルに保存しておき「gnuplot < command-flie」として実行することも可能(シェルのリダイレクト機能)。
  • グラフ生成する時は、原則としてベクター形式(EPS, PDF, SVG等)で作図し、texに埋め込むこと。
  • 参考: gnuplot入門
  • 参考: gnuplotで棒グラフを書く




1.12. dotによるグラフ描画

Graphvizに含まれるdotコマンドはグラフ自動生成コマンドとして様々なところで利用されている。例えばプログラミングのドキュメンテーション・ツールであるDoxygenファイルやクラス毎の警鐘関係等を可視化するために使用している。

例: シンプルなグラフの例。





2. 便利なコマンド

2章はシェル上で利用出来る便利なコマンドをいくつか紹介します。 詳細な説明は man コマンドで調べる事。関連のあるコマンド等が「SEE ALSO」蘭に列挙されていますので、一通り読むだけでも勉強になります!

[ データセット・サンプル: error_log ]

wc
  • 説明: word, line, character, and byte count
  • 使用例
    prompt> wc error_log
    
grep
  • 説明: print lines matching a pattern
  • 使用例(Webサーバのエラーログ中に「error」と記述されている行のみを抽出)
    prompt> grep error error_log
    
  • unmatch (マッチしなかった行を抽出) するにはオプション -v を付けて実行。上の例だと「grep -v error error_log」と実行すると、errorを含まない行のみを抽出できる。
cut
  • 説明: select portions of each line of a file
  • 使用例(半角スペースを区切り文字としてみなした時の、8番目の要素を抽出)
    prompt> grep error error_log | cut -f8 -d" "
    
tr
  • 説明: translate characters
  • 使用例(IPアドレスの後ろに付いている]を削除)
    prompt> grep error error_log | cut -f8 -d" " | tr -d "]"
    
sort
  • 説明: sort lines of text files
  • 使用例(数値でソート)
    prompt> grep error error_log | cut -f8 -d" " | tr -d "]" | sort -n
    
uniq
  • 説明: report or filter out repeated lines in a file
  • 使用例(IPアドレス毎の個数をカウント)
    prompt> grep error error_log | cut -f8 -d" " | tr -d "]" | sort -n | uniq -c
    
sleep
  • 説明: suspend execution for an interval of time
  • 使用例
    prompt> sleep 1
    
head
  • 説明: display first lines of a file
  • 使用例
    prompt> head error_log
    
tail
  • 説明: display the last part of a file
  • 使用例
    prompt> tail error_log
    
man
  • 説明: format and display the on-line manual pages
  • 使用例
    prompt> man man
    
その他
paste, split, touch,,, (linux コマンドで調べたり、manコマンドで関連コマンドを調べてみよう)




3. 課題

Level 1: Webサーバへのアクセスログから「アクセス数/日」をカウントしてみよう。
  • データ説明: とあるWebサーバへのアクセスログ(2017/03/26〜03/30)をダウンロードせよ。このファイルは1回のアクセス情報が1行で保存されている。詳細は省くが、ここでは「1日あたりのアクセス数」をカウントしたいだけなので、各ログにどのように日付けが記録されているかを確認できれば十分である。例えば1件目のアクセス日付けは「[26/Mar/2017:04:03:13 +0900]」と記載されていることを確認しよう。
  • 目的: 1日あたりのアクセス数を求め、(a)テキスト出力、(b)棒グラフ出力せよ。
  • 手順: 様々な手順が考えられるが、ここでは以下の流れで処理するものとする。
  1. [日時抽出(不要箇所のざっくりした削除)] 各行から「[dd/mm/yyyy:hh:mm:ss」を抽出せよ。ヒント: cut。
  2. [日にち抽出(時分等の削除)] 「[dd/mm/yyyy:hh:mm:ss」から「dd/mm/yyyy」だけを抽出せよ。ヒント: sed。
  3. [ユニークな日にち抽出] この時点では同じ日付けが複数行残されている。ユニークな行だけを残すことで、1日毎に1行となるようにせよ。ヒント: uniq。

    補足: uniqのオプション(-c)でカウントすることが可能だが、ここでは演習のため使わないこととする。

  4. [日毎のアクセス数カウント] 上記結果を変数に保存し、for文とwcコマンドを用いて「1日毎のアクセス数」をカウントせよ。このときの出力結果をdaily_access.dat」として保存せよ。
  5. [棒グラフ出力] daily_access.datを用い、gnuplotでdaily_access.pdfを生成せよ。グラフタイトル、軸ラベルや説明にも注意すること。
  6. 上記までで最小限の機能は実現出来ているはずだが、追加でスクリプトの利便性を高めるための機能を以下の通り実装しよう。
  7. [引数チェック] ここでは access_log を引数として与える(e.g., ./level1.sh access_log)形で実行するものとする。指定された第1引数が読み込み可能なファイルかどうかを確認せよ。もし結果がfalseなら、実行方法を出力してスクリプトの処理を停止せよ。
  8. [テキスト出力先のファイルチェック] ここではテキスト出力ファイル名を「daily_access.dat」とする。変な記述が残ってたりすると困るため、スクリプト実行時にファイルが存在するなら削除した上で空ファイルを作成せよ。

レポートに含める内容: (1)スクリプト本体、(2)実行結果(テキスト出力+グラフ)、(3)スクリプトの解説。

(おまけ): ツイートリストから mention map を作成してみよう。
  • データ説明: とあるツイートリスト(学科アカウント認証)をダウンロードせよ。このファイルは1ツイートの関連情報が1行で保存されている。1行目は各項目の説明であり、実際のデータは2行目からTSV形式で保存されている。1番目はツイートID、2番目はツイート日時、3番目はユーザID、4番目はscreen_name、5番目はツイート本文、という順序で1ツイート関連情報が保存されている。
  • 目的: mentionツイートのみを対象とし、「AさんからBさんへメンションがあった場合にA->Bへ矢印を引く。矢印の太さはmention回数で決める」というmentionグラフを作成し、ユーザ間のmention関係を眺めてみたい。
  • 手順: 必要なデータを収集する手順はいろいろあるが、ここでは以下の手順で進める。
  1. [screen_nameとツイート本文の抽出] ツイートリストからscreen_nameとツイート本文を抽出せよ。ヒント: cut コマンド。
  2. [mentionツイートの抽出] 誰かへの mention として、ここでは「タブ@screen_name」というツイート、つまり@から始まるツイートだけを対象としたい。@から始まるツイートだけを抽出せよ。ヒント: grep コマンド。
  3. [正規表現の検討]誰かへの mention は「ツイート本文において@から始まる」ように書かれているはずだ。つまり、先ほどの出力においては「screen_name1(タブ区切り)@screen_name2 ツイート本文」というパターンで記述されている。このパターンを正規表現で示せ。ヒント: スクリーンネームの正規表現は「[0-9A-Za-z_]*」と仮定。
  4. [mention関係のみを抽出] 上記の正規表現を用いて「screen_name1 screen_name2」へと書き直せ。つまり、タブはスペースに変換し、ツイート本文を削除せよ。また、screen_name2の冒頭にある@を削除せよ。ヒント: sed。
  5. [ノイズの除去] 上記の変換により基本的には「screen_name1 screen_name2」というパターンになっているはずだが、ノイズ(ここでは@を含むツイート)が含まれている。これを除外せよ。ヒント: grep。
  6. [mention関係の集計] この時点で「screen_name1 screen_name2」のみとなっているが、これらは時系列に並んでいる。「AさんがBさんに何回mentionを送ったか」をどのように数えたら良いだろうか。ヒント: sort, uniq。
  7. [集計結果のフォーマット整形] uniqでカウントすると、不必要なスペースが入る。これを整えるために「連続したスペースは1つに変換」せよ。ヒント: sed or tr。ここまでの結果を「mention.data」として保存するものとする。
  8. [dot形式への変換] mention.dataは「 mention回数 screen_name1 screen_name2」の形式で記述されている。これをdot記述「"screen_name1" -> "screen_name2" [penwidth=mention回数];」に変換せよ。
  9. [dot形式ファイルの作成] 前述の前後を適切に記述し、dot形式ファイルとして正しくなるよう処理を加えよ。ファイル名は「mention.dot」とする。ヒント: echo
  10. [dotコマンドを使ったグラフ生成] dot コマンドを用いて pdf 形式でグラフ生成せよ。
  11. [スクリプト化] 上記の処理をスクリプトとして整理せよ。





4. オプション課題例

ここで示している課題は全て「オプション課題の例」です。 同じ課題に取り組んでも構いませんし、アレンジしても構いませんし、オリジナル課題を設定するのもOKです。 自由に調理してください!


(4.1 ゴミ箱の作成)

これを選択する学生があまりにも多く、サンプルも大量にあるので今回は加点低いです。それでも構わなければやってみてください。

通常 rm/rmdir でファイルやディレクトリを削除すると、その時点で誤って削除したファイル等を復旧する事はできない。これを Windows や Mac OS X にあるような「ゴミ箱」のように機能するコマンドを作成せよ。

  • ヒント:「ゴミ箱」に相当するディレクトリを作成し、スクリプト実行時に引数で指定されたファイル等をそのディレクトリに移動する。
  • 拡張例:「ゴミ箱を空にする」に相当する機能をどう実現する?
  • 拡張例:同じファイル・ディレクトリ名が既に退避されている場合はどうする?
  • 拡張例:退避させたファイル等を元のディレクトリに戻すにはどうする?
  • 注意:スクリプトの記述を誤り、想定外のファイルやディレクトリを削除してしまうことがあります!動作確認は注意して行ってください!!

4.2 バックアップスクリプトの作成

任意の指定されたディレクトリ(ホームディレクトリであったり、講義毎に用意してあるディレクトリだったり)のバックアップorスナップショットを作成するスクリプトを作成せよ。

  • ヒント:tar, gz, rsync, rsnapshot コマンドについて調べる。
  • 拡張例:dateコマンドを用いて「バックアップ時の日付」をスナップショット名に含める事で「いつのスナップショットか」が分かりやすくなるようにする。
  • 拡張例:指定したディレクトリのスナップショットが既にある場合、現在のディレクトリを一時的に退避(最新版としてスナップショットを作成でも可)させ、スナップショットから当時のディレクトリを復元できるようにする。
  • 拡張例:「一週間毎にバックアップを取る」等、一定の時間毎にスクリプトを実行できるようにする。

4.3 ファイルの漢字コードや改行コードを変換するスクリプトの作成

nkfコマンドを使ってファイルの漢字コードや改行コードを変更する事が可能である。しかし、オプションを覚えたり、一時的に別ファイルを作成する必要があったりしてやや不便な点もあるので、より便利なスクリプトを作る。

  • ヒント:nkfコマンドについて調べる
  • 拡張例:自分が使いやすいオプション指定ができるようにする。
  • 拡張例:指定されたファイルの文字コードや改行コードを変換した内容で、元のファイルに書き出す。

4.4 スケルトン生成用スクリプトの作成

LaTeXにしろHTMLにしろ、共通したヘッダ情報やお約束を含める必要がある。新規作成する時点である程度ほ骨組みを含めたファイル(スケルトン)を生成する。

  • ヒント:ヒア・ドキュメント
  • 拡張例:スケルトンを生成したいファイルを決め、変数になりうる部分を抽出し、コマンドラインから指定出来るようにする。
  • 関連:Rails等の web application 作成用フレームワークでも同様の機能が多々利用されている。それらについて調べてみる。

4.5 ping 確認用スクリプトの作成

(例えば)学科基幹サーバ群や、VM群に対して ping 接続を確認したい。指定されたIPアドレスのリストや、指定されたアドレス領域に対して ping を実行し、結果を出力する。

  • ヒント:複数のIPアドレスを指定させるには、予めファイルに「1行=1IPアドレス」のように記述させる事でも可能だし、「192.168.0.1 180」のように、連続したIPアドレスであれば起点と終点を指定する事で範囲を入力させる事が可能。
  • 機能例:IP毎の結果だけでなく、集計結果も出力出来るようにする。
  • 機能例:SMTPやHTTPが可能かについてもチェック出来るようにする。
  • 機能例:直接コンソール上に出力する形式だけでなく、HTML出力する等してみやすい詳細結果を出力出来るようにする。

4.6 デーモン起動スクリプトを読んでみる

多くのLinux系OSにおいて、デーモン(サービス)を起動するためのスクリプトが

/etc/init.d/
以下に用意されている。 それらのスクリプトでは、オプション指定時に start/stop/restart 等を指定する事でデーモンを起動/停止/再起動することができるようになっている事が多い。 勉強のためにいくつかそれらのスクリプトを読んで解説してみよう。

なお Mac OS X の場合は、init.d に相当するものとして SystemStarter や launchd として用意されています。bashとは無関係になりますが、「デーモン(サービス)を起動するためのスクリプト」としては同じ枠組みですので、興味があれば調べてみると良いでしょう。

注意点として、スクリプト全体を1行ずつ全て解説する必要はありません。例えば、スクリプト内で関数が書かれていれば「この関数はホゲホゲしている」という程度のことが読み取れれば十分です。取り組んで、理解できた部分やできなかった部分を明示することで努力したことを読み取れるレポートにしてください


4.7 サンプルスクリプトを読んでみる

當間の学科リポジトリの「sh-sample」に下記4つのサンプルを用意したので、これらを読んで解説してみる。

  • ヒント: postfix, sendmail コマンドについて調べる。
  • (1) ./ping_check.sh: 指定したホスト群に対してping試験し、オプションにより出力方法を切り替える。
  • (2) ./csv2htmltable.sh: csv出力を読み込み、HTMLのテーブル形式に変換する。
  • (3) ./text2mail.sh: 任意のテキストファイルをメール送信する。
  • (4) wget-yahoo/daily.sh: Yahoo!の天気ページをダウンロードし、最高気温を抽出する。cron.dailyを意識した例。

なお、上記の例(1),(2),(3)は組み合わせて使うことも可能です。

prompt> ./ping_check.sh -c naha yomitan www \\
| ./csv2htmltable.sh | ./text2mail.sh -t mail-address






5. レポート骨組み

LaTeXの使い方がまだ良くわかっていない学生のために、レポートの骨組みを用意しました。下記の「骨組み」では、section(節)、箇条書き(itemize)、スクリプトのような「記号を含む複数の行」をそのまま出力する例(verbatim)、図や表を含める例(figure,table)、参考文献の列挙(thebibliography)、図表や参考文献に付与したラベル名を用いた参照(label, ref, cite)といった、一般的なレポートに求められる使い方を一通り含んでいます。必要に応じて参考にしてください。

  • ダウンロード: reportskel.tgz
  • 骨組みをコンパイルして生成出来るPDFファイル: info1-script.pdf
  • コンパイル方法
    prompt> platex info1-script.tex
    prompt> platex info1-script.tex
    prompt> dvipdfmx info1-script.dvi
    prompt> open info1-script.pdf
     もしくは
    prompt> make
    
  • PDFファイル埋め込み寺にエラーが出る場合。
    • ebbファイルが無いと言われるなら: ebb *.pdf
    • xbbファイルが無いと言われるなら: ebb -x *.pdf


参考文献・サイト

當間用意サンプル一覧
sh(bash)スクリプト
Mac OS X のサービス起動関連
gnuplot関連
その他