ようへいの日々精進XP

よかろうもん

アルゴリズムとプログラミング 「第 5 章 関数」の予習とまとめ

これは

放送大学教養学部の「アルゴリズムとプログラミング」という授業で使われる「アルゴリズムとプログラミング」という教材書籍を自分なりにまとめたものです.

1. 関数

序説

  • 頻繁に使うコードは関数 (function) として 1 つのコードにまとめると再利用が可能
  • 構造化言語において関数は構造化を実現する上で重要な役割を果たす
  • プログラミング言語によっては, 関数はサブルーチン, プロシージャと呼ばれることもある

1.1 プログラムコードの関数

以下, 冗長なコードの例.

/* code: ex5-1.c */
#include <stdio.h>

int main()
{
  int i;

  for (i = 0; i < 10; i++)
    printf ("%d", i);
  printf ("\n");
  
  for (i = 0; i < 10; i++)
    printf ("%d", i);
  printf ("\n");

  for (i = 0; i < 10; i++)
    printf ("%d", i);
  printf ("\n");

  return 0;
}

これをコンパイルして実行してみる.

root@0be431eebb77:/work# gcc ex5-1.c -o ex5-1 -g3
root@0be431eebb77:/work# ./ex5-1
0123456789
0123456789
0123456789

以下は, このコードから 0 〜 9 の数値を表示する print_numbers を関数に切り出した場合のコード.

/* code: ex5-2.c */
#include <stdio.h>

void print_numbers (void)
{
  int i;

  for (i = 0; i < 10; i++)
    printf ("%d", i);
  printf ("\n");    
}

int main ()
{
  print_numbers ();  
  print_numbers ();  
  print_numbers ();  
  
  return 0;
}

これをコンパイルして実行してみる.

root@0be431eebb77:/work# gcc ex5-2.c -o ex5-2 -g3
root@0be431eebb77:/work# ./ex5-2
0123456789
0123456789
0123456789
  • 結果はどちらのコードも同じになるがプログラムコードの見通しは関数化したプログラムコードの方が俄然良い
  • main も関数の 1 つであり, これはコードの初めに呼ばれる特殊な関数である
  • main 関数よりも後に関数を記述する場合, 関数プロトタイプ (function prototype = 関数の宣言) が必要になる
  • 関数の宣言には, 関数数の戻り値の型, 関数の引数の型, 関数の引数の数等の情報を記述する

以下は, main 関数より前に print_numbers 関数の関数プロトタイプが記述されている.

/* code: ex5-3.c */
#include <stdio.h>

void print_numbers (void);

int main ()
{
  print_numbers ();  
  print_numbers ();  
  print_numbers ();  
  
  return 0;
}

void print_numbers (void)
{
  int i;

  for (i = 0; i < 10; i++)
    printf ("%d", i);
  printf ("\n");    
}

これをコンパイルして実行してみる.

root@0be431eebb77:/work# gcc ex5-3.c -o ex5-3 -g3
root@0be431eebb77:/work# ./ex5-3
0123456789
0123456789
0123456789
  • main 関数よりも前に関数を記述する場合, 関数プロトタイプの宣言は省略可能...だが, バグ防止等の観点から関数プロトタイプの世間を強く推奨している書籍が多い
  • ライブラリに含まれる様々な関数にも関数プロトタイプに関する情報があり, 通常, これらはヘッダファイルに記述されている

例えば, sqrt 関数 (平方根を求める関数) の関数プロトタイプ (double sqrt (double);) であり, math.h というヘッダファイルに関数プロトタイプの記述があるので, sqrt 関数を利用する場合には, 以下のように math.h をインクルードして利用する.

...
#include <stdio.h>
#include <math.h>
...

1.2 関数とスコープ

  • 変数にはスコープ (scope) と呼ばれる属性がある
  • 変数のスコープとは, 変数が有効であるコードの範囲のことである
  • スコープの範囲の違いによって, グローバル変数 (global variable) とローカル変数 (local variable) がある
  • グローバル変数では, コードの全ての範囲で変数が有効である
  • ローカル変数では, ローカル変数が宣言されたブロック内の範囲だけで変数が有効である

以下のコードでは, 関数 f と関数 g の中で同じ変数名 i が使われているが, それぞれの関数でスコープが異なる.

/* code: ex5-4.c */
#include <stdio.h>

void g (void)
{
  int i;
  for (i = 0; i < 3; i++) {
    printf ("a");
  }
}

void f (void)
{
  int i;
  for (i = 0; i < 5; i++) {
    g ();
  }
}

int main ()
{
  f ();
  return 0;
}

これをコンパイルして実行する.

root@0be431eebb77:/work# gcc ex5-4.c -o ex5-4 -g3
root@0be431eebb77:/work# ./ex5-4
aaaaaaaaaaaaaaa

同じ変数名のグローバル変数とローカル変数を宣言することも可能であるが, ローカル変数が宣言されているブロック内では, グローバル変数よりもローカル変数の方が優先される.

2. 引数, 戻り値, 値渡し, 参照渡し

2.1 引数と戻り値

  • 引数 (parameter) は, 実引数 (argument, actual argument) と仮引数 (parameter, formal parameter) の 2 つがある
  • 実引数とは関数にわたす値のこと
  • 関数の中で引数の値を受け取る変数は仮引数という
  • 戻り値 (return value) とは関数が返す値

以下のコードは三角形の面積 ( area) を求める関数の例.

/* code: ex5-5.c */
#include <stdio.h>

float triangle (float base, float height)
{
  float c;
  c = (base * height) / 2.000F;
  return c;
}

int main ()
{
  float t;
  t = triangle (3.00, 4.00);
  printf ("triangle = %f\n", t);
  t = triangle (5.00, 6.00);
  printf ("triangle = %f\n", t);
  
  return 0;
}

尚, 三角形の面積は以下の公式で求められる.

$$ area = \frac{1}{2} \times base \times height $$

上記のコードにおいて, 底辺 ( base) と高さ ( height) の 2 つの float 型の引数を持ち, この引数に値が渡されると関数 (triangle) が三角形の面積を計算する. この関数の戻り値として float 型の面積の値が関数から返される. 3.00, 4.00 及び 5.00 と 6.00 が実引数であり, 関数で宣言されている base と height が仮引数となる.

一応, このコードをコンパイルして実行してみる.

root@0be431eebb77:/work# gcc ex5-5.c -o ex5-5 -g3
root@0be431eebb77:/work# ./ex5-5
triangle = 6.000000
triangle = 15.000000

2.2 "値渡し"と"参照渡し"

  • 引数の渡し型には, 値渡し (pass by value) と 参照渡し (pass by reference) と呼ばれるものがある
  • 値渡し は変数のコピーが作成されて値が渡される方法で, 関数内で変数の値が変更されても元の変数の値は変わらない
  • 参照渡し は, 変数そのものを渡す為, 関数内で変数の値の変更は, 呼び出した側の変数にも影響して値が変わる

以下のコードは 値渡し参照渡し の場合では, 変数 a の出力する値が異なる例.

/* code: ex5-6.c */
#include <stdio.h>

void add_pass_by_value (int i)
{
  i = i + 1;
}

void add_pass_by_reference (int *i)
{
  *i = *i + 1;
}

int main()
{
  int a;
  a = 10;
  add_pass_by_value (a);
  printf ("%d\n", a);

  a = 10;
  add_pass_by_reference (&a);
  printf ("%d\n", a);
  
  return 0;
}

このコードをコンパイルして実行してみる.

root@0be431eebb77:/work# gcc ex5-6.c -o ex5-6 -g3
root@0be431eebb77:/work# ./ex5-6
10
11

C 言語では変数ポインタを取得出来る為, 変数へのポインタを渡すことで 参照渡し と同じ効果がある アドレス渡し が可能であるとのこと...ポインタの話がサラッと出てきているので辛い...ポインタについては別で勉強する必要があるな.

3. 再帰関数

再帰呼び出し (recursion call) とは, 関数や手続き等が, 自分自身を呼び出して実行することで, アルゴリズムの中には, 再帰的にプログラムを記述することによって効果的な処理を出来るものがある. 以下, 再帰プログラミングの例として用いられる階乗 (factorial) の計算式で, 1 から  n までの自然数の総乗である. 階乗は  n! で表現され, 以下のような式で定義される. (尚,  0!=1 と定義されている.)

$$ n!=\prod_{k=1}^n k=n\times\left(n-1\right)\times\left(n-2\right)\cdots\times3\times2\times1 $$

実際の階乗の計算例は以下の通り. $n$ の値が大きくなるに従って急激に数が増加する.

1! = 1
2! = 2 x 1 = 2
3! = 3 x 2 x 1 = 6
4! = 4 x 3 x 2 x 1 = 24
..
..
10! = 10 x 9 x 8 x 7 x 6 x 5 x 4 x 3 x 2 x 1 = 3,628,800

この階乗を計算をコードに落とし込むと以下のような感じで factorial 関数を再帰で呼び出して計算結果を返す. factorial 関数は, factorial 関数内で  n - 1 という引数で呼び出すようになっており,  n 0 になった時点で 1 を返し, その時点で factorial 関数の呼び出しを止める.

/* code: ex5-7.c */
#include <stdio.h>

int factorial (int n)
{
  if (n == 0) {
    return 1;
  } else {
    return n * factorial (n - 1);
  }
}

int main()
{
  int i;
  i = 5;
  printf ("%d! = %d\n", i, factorial (i));
  
  return 0;
}

これをコンパイルして実行する.

root@0be431eebb77:/work# gcc ex5-7.c -o ex5-7 -g3
root@0be431eebb77:/work# ./ex5-7
5! = 120

これを gdb でステップ実行してみると以下のような感じになる.

(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /work/ex5-7

Breakpoint 1, main () at ex5-7.c:16
16        i = 5;
(gdb) s
17        printf ("%d! = %d\n", i, factorial (i));
(gdb) s
factorial (n=5) at ex5-7.c:6
6         if (n == 0) {
(gdb) s
9           return n * factorial (n -1);
(gdb) s
factorial (n=4) at ex5-7.c:6
6         if (n == 0) {
(gdb) s
9           return n * factorial (n -1);
(gdb) s
factorial (n=3) at ex5-7.c:6
6         if (n == 0) {
(gdb) s
9           return n * factorial (n -1);
(gdb) s
factorial (n=2) at ex5-7.c:6
6         if (n == 0) {
(gdb) s
9           return n * factorial (n -1);
(gdb) s
factorial (n=1) at ex5-7.c:6
6         if (n == 0) {
(gdb) s
9           return n * factorial (n -1);
(gdb) s
factorial (n=0) at ex5-7.c:6
6         if (n == 0) {
(gdb) s
7           return 1;

return n * factorial (n -1); が 5 回繰り返された後に return 1; が返っていることが解る.

いきなり, ポインタとは...

本教材ではポインタがサラッとしか触れられていないのが辛い. とりあえず, 本教材に書かれている内容を抜粋する.

  • ポインタは変数等のオブジェクトを指すもの
  • * 間接演算子 (indirection operator) は, ポンタを介して値に間接的に参照し, ポインタが指し示すアドレスに格納されている値を参照する演算子である
  • & アドレス演算子 (address-of operator) は, オペランドのアドレスを与える, 変数のアドレスを取得する演算子である

これだけではピンと来ないので...自分で調べるしかない.

演習問題

問 5.1

以下のコードは, どのような値を出力するか答えなさい.

/* code: q5-1.c */
#include <stdio.h>

float trapezoid (float a, float b, float h)
{
  float c;
  c = ((a + b) * h) / 2.000F;
  return c;
}

int main ()
{
  float t;
  t = trapezoid (3.00, 4.00, 5.00);
  printf ("trapezoid = %f\n", t);
  t = trapezoid (5.00, 6.00, 7.00);
  printf ("trapezoid = %f\n", t);
 
  return 0;
}

これをコンパイルして実行する.

root@0be431eebb77:/work# gcc q5-1.c -o q5-1 -g3
root@0be431eebb77:/work# ./q5-1
trapezoid = 17.500000
trapezoid = 38.500000

これは台形の面積を求めるコードだった. 尚, C 言語では, 関数に設定出来る引数の数の上限はコンパイラに依存している. C90 規格では 31 個, C99 規格では 127 個まで利用出来る.

問 5.2

以下のコードは, どのような値を出力するか答えなさい.

/* code: q5-2.c */
#include <stdio.h>

struct student
{
  int id;
  char grade;
  float average;
};
typedef struct student STUDENT_TYPE;

STUDENT_TYPE initialize_student_record (STUDENT_TYPE s)
{
  s. id++;
  s. grade = 'x';
  s. average = 0.0;
  return s;
}

int main ()
{
  STUDENT_TYPE student;

  student. id = 20;
  student. grade = 'a';
  student. average = 300.000;
  printf ("%d %c %f\n", student. id, student. grade, student. average);
  student = initialize_student_record (student);
  printf ("%d %c %f\n", student. id, student. grade, student. average);

  return 0;
}

これをコンパイルして実行してみる.

root@0be431eebb77:/work# gcc q5-2.c -o q5-2 -g3
root@0be431eebb77:/work# ./q5-2
20 a 300.000000
21 x 0.000000

問 5.3

以下の再帰関数のコードは, どのような値を出力するか答えなさい.

/* code: q5-3.c */
#include <stdio.h>

int fibonacci (int n)
{
  if (n == 0) {
    return 0;
  } else if (n == 1) {
    return 1;
  } else {
    return (fibonacci (n - 1) + fibonacci (n - 2));
  }
}

int main ()
{
  int i;
  i = 10;
  printf ("fibonacci (%d) = %d\n", i, fibonacci (i));
}

これをコンパイルして実行してみる.

root@0be431eebb77:/work# gcc q5-3.c -o q5-3 -g3
root@0be431eebb77:/work# ./q5-3
fibonacci (10) = 55

関数名から判るとおり, これはフィボナッチ数を計算するプログラムである. このプログラムをちょっと弄って 0 〜 11 まで数のフィボナッチ数列を計算させてみる.

/* code: q5-3a.c */
#include <stdio.h>

int fibonacci (int n)
{
  if (n == 0) {
    return 0;
  } else if (n == 1) {
    return 1;
  } else {
    return (fibonacci (n - 1) + fibonacci (n - 2));
  }
}

int main ()
{
  int i;
  for (i = 0; i < 11; i++) {
    printf ("fibonacci (%d) = %d\n", i, fibonacci (i));
  }
}

コンパイルして実行する.

root@0be431eebb77:/work# gcc q5-3a.c -o q5-3a -g3
root@0be431eebb77:/work# ./q5-3a
fibonacci (0) = 0
fibonacci (1) = 1
fibonacci (2) = 1
fibonacci (3) = 2
fibonacci (4) = 3
fibonacci (5) = 5
fibonacci (6) = 8
fibonacci (7) = 13
fibonacci (8) = 21
fibonacci (9) = 34
fibonacci (10) = 55

問 5.4

以下の再帰関数のコードは, どのような値を出力するか答えなさい.

/* code: q5-4.c */
#include <stdio.h>

void foo (int n)
{
  if (n < 15) {
    foo (n + 1);
    printf ("%d ", n);
  }
}

int main ()
{
  foo (0);
  return 0;
}

コンパイルして実行する.

root@0be431eebb77:/work# gcc q5-4.c -o q5-4 -g3
root@0be431eebb77:/work# ./q5-4
14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

上記のように 14 から 0 の数値が出力される.

問 5.5

コード ex5-7.c の階乗を計算する再帰関数のコードで  i = 5 を変更し,  i = -1 として負の整数を引数にして関数を呼び出した場合, どのような問題があるか考えなさい.

以下, ex5-7c のコード.

/* code: ex5-7.c */
#include <stdio.h>

int factorial (int n)
{
  if (n == 0) {
    return 1;
  } else {
    return n * factorial (n - 1);
  }
}

int main()
{
  int i;
  i = 5;
  printf ("%d! = %d\n", i, factorial (i));
  
  return 0;
}

とりあえず, ex5-7.c の実行結果.

root@0be431eebb77:/work# ./ex5-7
5! = 120

上記のコードを以下のように書き換えてみる.

/* code: q5-5.c */
#include <stdio.h>

int factorial (int n)
{
  if (n == 0) {
    return 1;
  } else {
    return n * factorial (n - 1);
  }
}

int main()
{
  int i;
  i = -1;
  printf ("%d! = %d\n", i, factorial (i));
  
  return 0;
}

これを実行する.

root@0be431eebb77:/work# ./q5-5
Segmentation fault

あちゃー. 一応, gdb で確認.

(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /work/q5-5

Breakpoint 1, main () at q5-5.c:16
16        i = -1;
(gdb) s
17        printf ("%d! = %d\n", i, factorial (i));
(gdb) s
factorial (n=-1) at q5-5.c:6
6         if (n == 0) {
(gdb) s
9           return n * factorial (n - 1);
(gdb) s
factorial (n=-2) at q5-5.c:6
6         if (n == 0) {
(gdb) s
9           return n * factorial (n - 1);
(gdb) s
factorial (n=-3) at q5-5.c:6
6         if (n == 0) {
(gdb) s
9           return n * factorial (n - 1);
(gdb) s
factorial (n=-4) at q5-5.c:6
6         if (n == 0) {
(gdb)

上記のように factorial (n=-1), factorial (n=-2), factorial (n=-3) となる為, n == 0 の条件に合致しなくなり, 無限ループが発生して最終的には Segmentation fault となる.

以上

  • C 言語の関数の雰囲気が判ってきた
  • いきなりポインタとか構造体とか出てきてビビった...ちゃんと勉強する必要があるな

2018 年 11 月 01 日 (木)

ジョギング

  • 山王公園往復
  • 懸垂 x 5
  • 懸垂頑張りすぎたかも...右の胸のあたりに変な痛み...汗

JAWS-UG 福岡もくもく会

  • 今日は Cognito の User Pool をイジった (管理者権限でユーザーの登録等を行う CLI ツールを Go 言語で作ることにした)
  • gRPC とか GraphQL の話が出てきたので, これはやっぱり勉強せねばと思った次第

とんとん

久しぶりに美人居酒屋とんとん. 鶏のタタキと湯豆腐を食べた.

2018 年 10 月 31 日 (水)

ジョギング

  • 自宅から天神交差点まで (40 分弱)
  • 懸垂 x 5

日課

  • おやすみ

気づいたら 10 月も終わった... 今日のトゥウィート

あっという間に福岡マラソンだ.

ちょっと忘れそうになっていたけど, 鹿児島マラソンにも当選した. 入金は 11/15 まで. 忘れずに.

2018 年 10 月 30 日 (火)

ジョギング

  • 山王公園往復
  • 懸垂 x 5

日課

  • (腕立て x 50 + 腹筋 x 50) x 3

夕飯, 今日も鍋

  • 今日も久原のあごだしで鍋

肌の乾燥

  • まだ秋口くらいなのに...
  • 乾燥がひどいのであります

新しい MacBook Air

が発表になっていた.

www.apple.com

へ〜〜〜って感じ.

試しに, メモリ 16GB にしてストレージを 512GB にしたら簡単に 20 万円を超えた...

2018 年 10 月 29 日 (月)

ジョギング

  • 山王公園往復
  • おニューの靴がいい感じ

日課

  • (腕立て x 50 + 腹筋 x 50) x 3

新しいソファが届く

  • 待ちに待ったソファが届いた
  • かなりの存在感を醸し出している

夕飯

  • 久原のあごだしで鍋
  • さすが久原, 上品な感じで〆まで美味しかった

今日のトゥウィート

2018 年 10 月 28 日 (日)

ジョギング

  • 山王公園往復
  • おニューのシューズで走ったけど, やっぱりグリップが違うね〜〜〜
  • 懸垂 5 回 , もう雰囲気でいけるようになったので, そろそろ 10 回を目指していこう

日課

  • おやすみ

正三おじさんとその仲間たちの JAZZ ライブ

  • 去年も招待頂いて, すごく良かったので楽しみにしていた
  • 平均年齢 75 歳のおじいさん達が楽しそうに奏でる姿に元気を頂いた
  • これからも頑張って欲しいけど, 今年でライブは終わりとのこと

2018 年 10 月 27 日 (土)

ジョギング

  • 山王公園往復
  • 懸垂 5 回, 最近は楽に 5 回まではいけるようになった

日課

  • (腕立て x 50 + 腹筋 x 50) x 3

香椎で買い物

  • 奥さんの通院のついでに諸々
  • ランニング用のシューズがヘタっていたので購入, 来月の福岡マラソンの準備も
  • 久しぶりのダイレックスで食材の買い込み

引っ越しその後...

  • 奥さんがいろいろと頑張って部屋が片付きつつある
  • ありがとう

夕飯

  • 久しぶりの鍋
  • 〆のおじやまでお腹いっぱい

液晶モニタ

  • 今, 利用している液晶モニタは FullHD で, もう少し解像度の高いモニタを 1 枚で運用したいと考えている (机の周りを出来るだけさっぱりさせたい為)
  • 4K までは不要で, 2560×1440 という解像度 (WQHD) の液晶モニタがあるらしいので, それを物色できればなあと考えている

気になる書籍

上司に「いいよ〜」とおすすめされた.

www.oreilly.co.jp

目次を見ると, Go で書かれたサンプルプログラムを動かしながら HTTP プロトコルや RESTful API の実装について学べるようだ. 是非, 読みたい.

あと, これも読みたい.

gihyo.jp

amiCtrl をちょっとバージョンアップした

tl;dr

どうも, かっぱです.

世界で 100 万人の方々からご愛顧頂きたい EC2 AMI を作ったりする「俺のツールズ」シリーズの甘えん坊 amiCtrl をちょっとバージョンアップしました.

github.com

amiCtrl を利用することで, AMI を作ったり, 削除したり, 自分のアカウントに登録されている AMI 一覧を取得することが可能です. 詳しい使い方は README をご一読下さい.

更新内容

アカウントに登録されている AMI 一覧を取得

今までなんで無かったんだろうと過去の自分を問い詰めたい気分ですが, アカウントに登録されている AMI 一覧を取得出来ます. 以下のような感じです.

$ amiCtrl
+--------------------------------------------+--------------+-----------+------------------------+
|                  AMI NAME                  |    AMI ID    |   STATE   |      SNAPSHOT ID       |
+--------------------------------------------+--------------+-----------+------------------------+
| tokito-ami2                                | ami-1234567a | available | snap-0000000a8de111a2a |
|                                            |              |           | snap-0000000a8de111a2b |
+--------------------------------------------+--------------+-----------+------------------------+
| tokito-ami                                 | ami-1234567b | available | snap-0000000a8de111a2c |
+--------------------------------------------+--------------+           +------------------------+
| suzuki-ami2                                | ami-1234567c |           | snap-0000000a8de111a2d |
+--------------------------------------------+--------------+-----------+------------------------+

登録の件数が多いとそれなりの出力行数となります. 現在のところ AMI のオーナー指定は self 固定となっていますが, 将来的には任意のオーナーを指定可能にし, フィルタで絞り込むような既存の AWS CLI 同等の機能を実装する予定です.

JSON 出力

AMI の一覧について, テーブル出力以外に JSON 出力が出来るようになりました. 以下のような感じです.

$ amiCtrl -json
{
  "amis": [
    {
      "ami_name": "tokito-ami2",
      "ami_id": "ami-1234567a",
      "instance_type": "available",
      "snapshot_ids": [
        "snap-0000000a8de111a2a",
        "snap-0000000a8de111a2b"
      ]
    },
    {
      "ami_name": "tokito-ami",
      "ami_id": "ami-1234567b",
      "instance_type": "available",
      "snapshot_ids": [
        "snap-0000000a8de111a2c"
      ]
    },
    {
      "ami_name": "suzuki-ami2",
      "ami_id": "ami-1234567c",
      "instance_type": "available",
      "snapshot_ids": [
        "snap-0000000a8de111a2d"
      ]
    }
  ]
}

JSON 出力は, 他のツールと組み合わせた利用を想定しています. 例えば, 不要な AMI をまるっと削除したい場合, 以下のように使えるんじゃないかなーと思っています.

for id in $(amiCtrl -json | jq -r .amis[].ami_id)
do
  amiCtrl -delete -ami=${id} -batch
done

-delete オプションに -batch オプションを追加することで, 削除確認の出力をスキップすることが可能となります.

# -batch オプションを指定しない場合
$ amiCtrl -delete -ami=ami-e4985d82
+------------+--------------+-----------+------------------------+
|  AMI NAME  |    AMI ID    |   STATE   |      SNAPSHOT ID       |
+------------+--------------+-----------+------------------------+
| suzuki-ami | ami-1234567e | available | snap-0000000a8de111a2d |
+------------+--------------+-----------+------------------------+
上記の AMI を削除しますか?(y/n): n
処理を停止します.

-batch オプションを付与する場合, 問答無用に AMI は削除されてしまいますので注意して利用しましょう.

テストの追加

ユニットテストではありませんが, コマンドの実行結果等をテストする小さなテストを追加しました. 以下のように bytes パッケージを利用してコマンドラインの出力結果をパースしています.

import (
    "bytes"
    "os/exec"
    _ "fmt"
    "strings"
    _ "time"
    "testing"
)

func TestVersionFlag(t *testing.T) {
    cmd := exec.Command("gom", "run", "amiCtrl.go", "-version")
    stdout := new(bytes.Buffer)
    cmd.Stdout = stdout

    _ = cmd.Run()

    if ! strings.Contains(stdout.String(), AppVersion) {
        t.Fatal("Failed Test")
    }
}

exec.Command にダラダラとコマンドを並べるのは見栄えが良くないと思ったので, コマンド自体はシンプルなシェルスクリプトに書いています. 以下のような感じです.

$ cat tests/test_stdout_list.sh
INSTANCE_ID=$(aws --profile=dummy_profile --region=us-east-1 --endpoint=http://192.168.0.100:5000 \
    ec2 run-instances \
      --image-id=ami-1a2b3c4d \
      --count=1 \
      --instance-type=c3.large \
      --key-name=MyKeyPair \
      --security-groups=MySecurityGroup \
      --query=Instances[].InstanceId --output=text)

aws --profile=dummy_profile --region=us-east-1 --endpoint=http://192.168.0.100:5000 \
  ec2 create-image \
    --instance-id=$(echo ${INSTANCE_ID} | tr -d \\r) \
    --name=test-image --output=text

gom run amiCtrl.go -profile=dummy_profile -region=us-east-1 -endpoint=http://192.168.0.100:5000

尚, テストは docker-compose で環境を構築, 実行し, Travis CI でテストを実行するようにしています.

$ docker-compose exec amictrl make test
=== RUN   TestVersionFlag
--- PASS: TestVersionFlag (1.11s)
=== RUN   TestStdoutList
--- PASS: TestStdoutList (2.32s)
=== RUN   TestStdoutCreate
--- PASS: TestStdoutCreate (1.69s)
=== RUN   TestStdoutCreateError
--- PASS: TestStdoutCreateError (1.67s)
=== RUN   TestStdoutDelete
--- PASS: TestStdoutDelete (2.25s)
=== RUN   TestStdoutDeleteError
--- PASS: TestStdoutDeleteError (2.25s)
=== RUN   TestStdoutDeleteNo
--- PASS: TestStdoutDeleteNo (2.33s)
=== RUN   TestStdoutState
--- PASS: TestStdoutState (2.80s)
=== RUN   TestStdoutJson
--- PASS: TestStdoutJson (2.80s)
PASS
ok      amiCtrl 19.219s

.travis.yml は以下の通りです.

sudo: required
matrix:
  include:
  - name: "Go 1.11"
    env: TEST_TARGET=amictrl
services:
  - docker
before_install:
  - docker-compose build
  - docker-compose up -d
install:
before_script:
  - docker-compose exec amictrl make depend
script:
  - docker-compose exec amictrl make test
after_script:
notifications:

こんな感じでテストが走っています.

ということで

この Infrastructure as Code な時代にこんな CLI ツールの存在価値について悩んでいますが, ツールをいじくることで AWSAPI や Go の書き方について学びになっているので, 引き続きこれらのツールを書いていこうと思いますーε≡≡ヘ( ´Д`)ノ

もし, よろしければ amiCtrl をご利用頂きましてフィードバック等を頂けると嬉しいでございます.

2018 年 10 月 26 日 (金)

ジョギング

  • 山王公園往復
  • 懸垂 5 回

日課

  • おやすみ

夕飯

  • パスタを作る
  • サニーで買ったカツオのタタキが美味しかった

最近

  • 夜になると眠気がひどい...
  • なんとか歯磨きまでは出来るけど, ストンと落ちるように寝てしまっている...

ギョームにてすぐに日本時間が欲しい時に僕がやること

tl;dr

AWS がメールで送ってくる EC2 等のメンテナンス通知. メンテナンスの日時が UTC で表記されているので, その時間を日本時間 (JST) で欲しい時に僕がやっていることをメモっておきます.

何やるか

まずは irb を起動します.

$ ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17]
$ irb -v
irb 0.9.6(09/06/30)
$ irb

そして, おもむろに time ライブラリを require します.

irb(main):001:0> require 'time'
=> true

そして, Time クラス parse メソッドの引数に UTC 時間を付与し, さらに getlocal メソッドで日本時間を得ます.

irb(main):002:0> Time.parse('2018-11-08T20:00:00.000Z').getlocal
=> 2018-11-09 05:00:00 +0900

getlocal は以下のドキュメントを参考にさせて頂きました.

docs.ruby-lang.org

上記の例だと, UTC 時間で 2018 年 11 月 8 日の 20 時は日本時間で 2018 年 11 月 9 日の 5 時ということになります.

また, Object クラスをオープンクラスして, 以下のようなメソッドを差し込むと...

class Object
  def to_local_time
    require 'time'
    Time.parse(self).getlocal
  end
end

以下のように書くことが出来る.

irb(main):001:0> class Object
irb(main):002:1>   def to_local_time
irb(main):003:2>     require 'time'
irb(main):004:2>     Time.parse(self).getlocal
irb(main):005:2>   end
irb(main):006:1> end
=> :to_local_time
irb(main):007:0> '2018-11-08T20:00:00.000Z'.to_local_time
=> 2018-11-09 05:00:00 +0900

Ruby のこういうところ (オレオレメソッドを Ruby 本体に追加出来る) が好きだな〜.

同じようなことを python でも

検算という意味で python でも同じようなことをやってみます.

$ python -V
Python 3.6.5
$ python
>>> from pytz import timezone
>>> from dateutil import parser
>>> u = '2018-11-08T20:00:00.000Z'
>>> j = parser.parse(u).astimezone(timezone('Asia/Tokyo'))
>>> print(j)
2018-11-09 05:00:00+09:00

前述の Ruby で試した場合と同じ結果を得ることが出来ました.

尚, 本検算については, 以下の記事を参考にさせて頂きました. ありがとうございます.

qiita.com

以上

メモでした.