シェルスクリプトマガジン

test

シェルスクリプトの書き方入門(Vol.65記載)

投稿日:2020.03.25 | カテゴリー: コード

筆者:大津 真

本連載ではシェルスクリプトの書き方をやさしく紹介します。対象とす
るシェルは、多くのLinuxディストリビューションが標準シェルとして
採用する「Bash」です。第1回目となる今回は、シェルスクリプトの概要
と作成、実行の方法を解説します。

シェルスクリプトマガジン Vol.65は以下のリンク先でご購入できます。

図4 ホスト名と日時を表示するシェルスクリプト「 today1.sh 」

hostname
date '+ 今日は%Y 年%m 月%d 日(%a) です'

図5 シバンを追加したシェルスクリプト「 today2.sh 」

#!/bin/bash
hostname
date '+ 今日は%Y 年%m 月%d 日(%a) です'

図6 コメントを追加したシェルスクリプト「 today3.sh 」

#!/bin/bash
# ホスト名と今日の日時を表示する
# ver.1.0 2020/2/1
hostname
date '+ 今日は%Y 年%m 月%d 日(%a) です'

図8 コマンド置換を使用したシェルスクリプト「today4.sh」

#!/bin/bash
# ホスト名と今日の日時を表示する
# ver.1.1 2020/2/1
echo " ホスト名は$(hostname) です"
date '+ 今日は%Y 年%m 月%d 日(%a) です'

Vol.65 補足情報

投稿日:2020.03.25 | カテゴリー: コード

目次

p.2の「ラズパイセンサーボードで学ぶ」は「ラズパイ入門ボードで学ぶ」の誤りです。お詫びして訂正いたします。

情報は随時更新致します。

Vol.65

投稿日:2020.03.25 | カテゴリー: バックナンバー

 「自由なソフトウエアによるOSを作り上げたい」という志を共にするボランティアの開発者の協力によって開発が続いているLinuxディストリビューションがあります。それが「Debian」(デビアン)です。特集1では、Debianの概要、最新の安定版であるDebian 10の特徴やインストール方法、導入時や使いこなしの注意点を解説します。Red Hat Enterprise Linux(RHEL)やUbuntuに引けを取らない、Debainに注目してみてください。
 特集2では、オープンソースのWebサーバーソフトウエアとして人気が高い「NGINX」の商用版「NGINX Plus」について解説します。オープンソース版にはない機能により、システムの可用性と堅牢性の向上や、運用の簡素化を実現できます。試用も可能なので、特集2を読んで実際に試してみてください。
 ソフトウエアだけでなく、ハードウエアのオープン(オープンソース)化も進んでいます。特集3では、米IBM社が開発した、POWERプロセッサに関連する技術をオープン化にするコミュニティ「OpenPOWER」と、プロセッサ拡張バス標準「CAPI」のオープンな仕様である「OpenCAPI」について分かりやすく紹介します。
 このほか、二つの連載を開始しました。一つは、オープンソースソフトウエアを基に開発されている米Red Hat製品を紹介する「レッドハットのプロダクト」。もう一つは、初めての人や初心者にもシェルスクリプトの書き方を紹介する「シェルスクリプトの書き方入門」です。
 今回も読み応え十分のシェルスクリプトマガジン Vol.65。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

シェルスクリプトマガジンvol.65 Web掲載記事まとめ

投稿日:2020.03.25 | カテゴリー: コード

004 レポート WebブラウザMicrosoft Edgeの新版
005 レポート 仮想マシン構築・運用ソフト「Multipass」
006 NEWS FLASH
008 特集1 知っておきたいDebian/やまねひでき
026 特集2 NGINX Plus徹底解説/髙田知典 コード掲載
042 特別企画 詳説 OpenPOWER/OpenCAPI/河井裕
050 ラズパイ入門ボードで学ぶ 電子回路の制御/米田聡 コード掲載
054 レッドハットのプロダクト/松田絵里奈 コード掲載
064 姐のNOGYO
066 漢のUNIX/後藤大地
072 円滑コミュニケーションが世界を救う!/濱口誠一
074 バーティカルバーの極意/飯尾淳 コード掲載
080 中小企業手作りIT化奮戦記/菅雄一
084 やっつける/桑原滝弥、イケヤシロウ
086 Webアプリの正しい作り方/しょっさん
096 法林浩之のFIGHTING TALKS/法林浩之
098 香川大学SLPからお届け!/重松亜夢 コード掲載
106 MySQL Shellを使おう/梶山隆輔
114 シェルスクリプトの書き方入門/大津真 コード掲載
120 Techパズル/gori.sh
122 コラム「社訓」/シェル魔人

特集2 NGINX Plus徹底解説(Vol.65記載)

投稿日:2020.03.25 | カテゴリー: コード

著者:髙田 知典

「NGINX」(エンジンエックス)は、人気の高いWebサーバーソフトウエ
アです。「NGINX Plus」は、オープンソース版のNGINXにさまざまな
機能拡張を施した商用版です。追加された拡張機能を利用すること
で、システムの可用性と堅牢性の向上や、運用の簡素化を実現できま
す。本特集では、NGINX Plusの機能や試用方法などを紹介します。

シェルスクリプトマガジン Vol.65は以下のリンク先でご購入できます。

Part2 オープンソース版NGINX とNGINX Plus の違い

図2 アクティブヘルスチェックの設定例

upstream my_upstream {
    zone my_upstream 64k;
    server server1.example.com slow_start=30s;
    server server2.example.com slow_start=30s;
}
server {
    location / {
        proxy_pass http://my_upstream;
        health_check interval=5s uri=/test.php match=statusok;
    }
}
match statusok {
    status 200;
    header Content-Type = text/html;
    body ~ "Server[0-9]+ is alive";
}

図3 Sticky cookie の設定例

upstream my_upstream {
    server server1.example.com;
    server server2.example.com;
    sticky cookie srv_id expires=1h;
}

図4 Sticky route の設定例

map $cookie_jsessionid $route_cookie {
    ~.+\.(?P<route>\w+)$ $route;
}
map $request_uri $route_uri {
    ~jsessionid=.+\.(?P<route>\w+)$ $route;
}
upstream backend {
    server backend1.example.com route=a;
    server backend2.example.com route=b;
    sticky route $route_cookie $route_uri;
}

図5 Sticky learnの設定例

upstream backend {
   server backend1.example.com;
   server backend2.example.com;
   sticky learn
       create=$upstream_cookie_examplecookie
       lookup=$cookie_examplecookie
       zone=client_sessions:1m
       timeout=1h;
}

図6 DNS名をAレコード情報を使って解決する設定例

resolver 10.0.0.2 valid=10s;
upstream backends {
    zone backends 64k;
    server backends.example.com:8080 resolve;
}
server {
    location / {
        proxy_pass http://backends;
    }
}

図7 DNS名をSRVレコード情報を使って解決する設定例

resolver 10.0.0.2 valid=10s;
upstream backends {
    zone backends 64k;
    server backends.example.com service=_http._tcp resolve;
}
server {
    location / {
        proxy_pass http://backends;
    }
}

図9 コンテンツキャッシュのパージ設定例

http {
(略)
    proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=mycache:10m purger=on;
    map $request_method $purge_method {
        PURGE 1;
        default 0;
    }
    server {
        listen 80;
        server_name www.example.com;
        location / {
            proxy_pass https://localhost:8002;
            proxy_cache mycache;
            proxy_cache_purge $purge_method;
        }
    }
    geo $purge_allowed {
       default 0;
       10.0.0.1 1;
       192.168.0.0/24 1;
    }
    map $request_method $purge_method {
       PURGE $purge_allowed;
       default 0;
    }
}

図15 nginx-sync.confの設定例

NODES="node2.example.com node3.example.com node4.example.com"
CONFPATHS="/etc/nginx/nginx.conf /etc/nginx/conf.d"
EXCLUDE="default.conf"

図16 NGINX Plus APIの設定例

http {
(略)
    # Configuration of the server group
    upstream appservers {
        # 共有メモリーにサーバーグループ構成を保存するようにゾーンを設定
        zone appservers 64k;
        server appserv1.example.com      weight=5;
        server appserv2.example.com:8080 fail_timeout=5s;
        server reserve1.example.com:8080 backup;
        server reserve2.example.com:8080 backup;
    }
    server {
            location / {
            proxy_pass http://appservers;
            health_check;
        }
        location /api {
        # GET以外のメソッドをBasic認証で制限
            limit_except GET {
                auth_basic "NGINX Plus API";
                auth_basic_user_file /etc/nginx/.htpasswd;
            }
        # APIを書き込みモードで動作させる
            api write=on;
        # アクセス元をローカルホストのみに制限
            allow 127.0.0.1;
            deny  all;
        }
    }
}

図18 stateファイルの設定を追加した例

http {
(略)
    # Configuration of the server group
    upstream appservers {
        zone appservers 64k;
        state /var/lib/nginx/state/appservers.conf;
        # server appserv1.example.com      weight=5;
        # server appserv2.example.com:8080 fail_timeout=5s;
        # server reserve1.example.com:8080 backup;
        # server reserve2.example.com:8080 backup;
    }
}

図19 キーバリューストアの設定例

http {
    # キーバリューストアの定義
    keyval_zone zone=blacklist:1M state=/tmp/blacklist.json;
    keyval $remote_addr $black_list zone=blacklist;
    server {
    listen 80;
        location / {
            root /usr/share/nginx/html;
            if ($black_list = 0) {
                return 403;
            }
        }
        location /api {
            # GET以外のメソッドをベーシック認証で制限
            limit_except GET {
            auth_basic "NGINX Plus API";
            auth_basic_user_file /etc/nginx/.htpasswd;
            }
            # APIを書き込みモードで動作させる
            api write=on;
            #アクセス元をローカルホストのみに制限
            allow 127.0.0.1;
            deny  all;
        }
    }
}

ラズパイ入門ボードで学ぶ電子回路の制御(Vol.65掲載)

投稿日:2020.03.25 | カテゴリー: コード

筆者:米田 聡

シェルスクリプトマガジンでは、小型コンピュータボード「Raspberry Pi」(ラズパイ)は2種類の拡張ボードを制作しています。第12回は、最初に作成した「ラズパイ入門ボード」に「ロータリエンコーダ」を接続し、オーディオのボリュームのようなコントローラを実装します。

シェルスクリプトマガジン Vol.65は以下のリンク先でご購入できます。

図5 ロータリエンコーダ用のクラスファイル(rotary.py)

import RPi.GPIO as GPIO
import time

P_A = 21
P_B = 25

class rotaryenc():

  def __init__(self, phase_a = P_A, phase_b = P_B):
    self.phase_a = phase_a
    self.phase_b = phase_b

    GPIO.setmode(GPIO.BCM)
    GPIO.setup(phase_a, GPIO.IN)
    GPIO.setup(phase_b, GPIO.IN)

    GPIO.add_event_detect(phase_a, GPIO.RISING, callback=self.__changeStatus)

    self.__callback = None
    self.prev_forward_time = 0
    self.prev_backward_time = 0

  def __changeStatus(self, gpio):
    pa = GPIO.input(self.phase_a)
    pb = GPIO.input(self.phase_b)
    if pa == GPIO.LOW:     # チャタリング等
      return

    value = 0
    current = time.time()
    if pb == GPIO.LOW: # 時計回り
      value = 1
      if self.prev_forward_time != 0:
        if (current - self.prev_forward_time) < 0.1:    # 100ms以内
          value = 10
      self.prev_forward_time = current
      self.prev_backward_time = 0
    if pb == GPIO.HIGH:
      value = -1
      if self.prev_backward_time != 0:
        if (current - self.prev_backward_time) < 0.1:    # 100ms以内
          value = -10
      self.prev_forward_time = 0
      self.prev_backward_time = current

    if value != 0 and self.__callback is not None:
      self.__callback(value)
    
  def registerCallback(self, c):
    self.__callback = c
    return
    
  def unregisterCallback(self):
    self.__callback = None

  def __del__(self):
    GPIO.cleanup()

図6 テスト用のサンプルプログラム(test.py)

from rotary import rotaryenc
from EbOled import EbOled
import time

# OLED
oled = EbOled()
oled.begin()
oled.clear()
oled.display()

value = 0

def callback(r):
  global value
  value += r
  oled.drawString('value=' + str(value))
  oled.display()

re = rotaryenc()
re.registerCallback(callback)
try:
  time.sleep(120)
except KeyboardInterrupt:
  pass

レッドハットのプロダクト(Vol.65記載)

投稿日:2020.03.25 | カテゴリー: コード

筆者:松田 絵里奈

「Red Hat Decision Manager」は、OSSの「Drools」がベースのルールエンジンです。同製品で業務ロジックを実装することで、アプリから業務ロジックを分離でき、メンテナンスが容易になります。また、複雑なロジックでも簡単で分かりやすい形式で記述できます。

シェルスクリプトマガジン Vol.65は以下のリンク先でご購入できます。

図11 pom.xml(抜粋)

(略)
    <dependencies>
        <dependency>
          <groupId>org.kie</groupId>
          <artifactId>kie-api</artifactId>
        </dependency>
        <dependency>
          <groupId>org.drools</groupId>
          <artifactId>drools-core</artifactId>
        </dependency>
        <dependency>
          <groupId>org.drools</groupId>
          <artifactId>drools-decisiontables</artifactId>
        </dependency>
    </dependencies>
(略)

図12 過去利用状況ファクト(過去利用状況.java)

package com.example.rhdm;

public class 過去利用状況 {

    private String ユーザーID;
    private int 過去利用回数;
    private int 過去利用額計;
    public String get ユーザーID() {
        return ユーザーID;
    }
    public void set ユーザーID(String ユーザーid) {
        ユーザーID = ユーザーid;
    }
    public int get 過去利用回数() {
        return 過去利用回数;
    }
    public void set 過去利用回数(int 過去利用回数) {
        this.過去利用回数 = 過去利用回数;
    }
    public int get 過去利用額計() {
        return 過去利用額計;
    }
    public void set 過去利用額計(int 過去利用額計) {
        this.過去利用額計 = 過去利用額計;
    }
}

図13 今回利用ファクト(今回利用.java)

package com.example.rhdm;

public class 今回利用 {

    private String ユーザーID;
    private int 利用額;
    private String 割引ランク;
    private int 割引率;
    
    public String get ユーザーID() {
        return ユーザーID;
    }
    public void set ユーザーID(String ユーザーid) {
        ユーザーID = ユーザーid;
    }
    public int get 利用額() {
        return 利用額;
    }
    public void set 利用額(int 利用額) {
        this.利用額 = 利用額;
    }
    public String get 割引ランク() {
        return 割引ランク;
    }
    public void set 割引ランク(String 割引ランク) {
        this.割引ランク = 割引ランク;
    }
    public int get 割引率() {
        return 割引率;
    }
    public void set 割引率(int 割引率) {
        this.割引率 = 割引率;
    }
}

図14 最初の/src/main/resources/割引決定.drlファイル

package com.example.rhdm

import com.example.rhdm.*

dialect "java"

rule "利用回数2回未満、利用額1万未満"
    when
        $過去利用:過去利用状況(過去利用回数 < 2, 過去利用額計 < 10000)
        $今回利用:今回利用(ユーザーID == $過去利用.ユーザーID)
    then        
//      modify($今回利用) {set 割引ランク("X")}
        $今回利用.set 割引ランク("X");
        System.out.println($今回利用.get ユーザーID() + "の割引ランクは" + $今回利用.get 割引ランク() + "です");
 end

図16 ルールを動かすためのJavaクラス

package com.example.rhdm;

import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;

public class DrlTest {

    public static final void main(String[] args) {
        try {
        KieServices ks = KieServices.Factory.get();
            KieContainer kContainer = ks.getKieClasspathContainer();
            // 実行対象ルールを指定
KieSession kSession = kContainer.newKieSession("ksession-rules");
            // ファクトを生成
             過去利用状況 fact1 = new 過去利用状況();
            fact1.setユーザーID("A001");
            fact1.set過去利用回数(1);
            fact1.set過去利用額計(5000);
            今回利用 fact2 = new 今回利用();
            fact2.setユーザーID("A001");
            // ルールエンジンにファクトをインサート
            kSession.insert(fact1);
            kSession.insert(fact2);
            // ルール実行
kSession.fireAllRules();
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

図17 追加するルール

rule "利用回数2回未満、利用額1万以上"
    when
        $過去利用:過去利用状況(過去利用回数 < 2, 過去利用額計 >= 10000)
        $今回利用:今回利用(ユーザーID == $過去利用.ユーザーID)
    then
        $今回利用.set割引ランク("E");
        System.out.println($今回利用.getユーザーID() + "の割引ランクは"+ $今回利用.get割引ランク() + "です");
end

rule "利用回数2回以上5回未満、利用額1万未満"
    when
        $過去利用:過去利用状況(過去利用回数 >= 2, 過去利用回数 < 5, 過去利用額計 < 10000)
        $今回利用:今回利用(ユーザーID == $過去利用.ユーザーID)
    then
        $今回利用.set割引ランク("E");
        System.out.println($今回利用.getユーザーID() + "の割引ランクは"+ $今回利用.get割引ランク() + "です");
end

rule "利用回数2回以上5回未満、利用額1万以上"
    when
        $過去利用:過去利用状況(過去利用回数 >= 2, 過去利用回数 < 5, 過去利用額計 >= 10000)
        $今回利用:今回利用(ユーザーID == $過去利用.ユーザーID)
    then
        $今回利用.set割引ランク("D");
        System.out.println($今回利用.getユーザーID() + "の割引ランクは"+ $今回利用.get割引ランク() + "です");
end

図18 さらに追加するルール

rule "割引ランクX割引率設定"
    when
        $今回利用:今回利用(割引ランク == "X")
    then
        $今回利用.set割引率(0);
        System.out.println($今回利用.getユーザーID() + "の割引率は"+ $今回利用.get割引率() + "です");
end

rule "割引ランクE割引率設定"
    when
        $今回利用:今回利用(割引ランク == "E")
    then
        $今回利用.set割引率(3);
        System.out.println($今回利用.getユーザーID() + "の割引率は"+ $今回利用.get割引率() + "です");
end

rule "割引ランクD割引率設定"
    when
        $今回利用:今回利用(割引ランク == "D")
    then
        $今回利用.set割引率(5);
        System.out.println($今回利用.getユーザーID() + "の割引率は"+ $今回利用.get割引率() + "です");
end

バーティカルバーの極意(Vol.65掲載)

投稿日:2020.03.25 | カテゴリー: コード

筆者:飯尾 淳

 今回はデータ分析から少し離れてフラクタル図形について語りましょ
う。例として、縦棒(バーティカルバー)と横棒が縦横無尽に組み合わさっ た図形であるヒルベルト曲線を考えます。ヒルベルト曲線はフラクタル図形の一つで、自己相似性という特徴を持ちます。本記事ではこれを描画するプログラムを紹介します。シンプルなプログラムでこんなにも複雑な図形を描くことができるのかと驚くはずです。

シェルスクリプトマガジン Vol.65は以下のリンク先でご購入できます。

図4 図を出力するためのHTMLコード

<!DOCTYPE html>
<html>
  <head>
    <title>Hilbert Curve</title>
    <meta charset="utf-8">
    <meta name="description" content="Drawing Hilbert curve">
    <meta name="author" content="Jun Iio">
    <meta name="viewport" content="width=device-width,initial-scale=1">
  </head>
  <body>
    <canvas id="theCanvas" width="1000" height="1000"></canvas>
    <script src="hilbert.js"></script>
  </body>
</html>

図5 1次ヒルベルト曲線を描くJavaScriptコード

// the canvas and its graphic context
var cs  = document.getElementById('theCanvas');
var ctx = cs.getContext('2d');

ctx.lineWidth   = 5;
ctx.strokeStyle = 'black';

ctx.beginPath();
[ [0.25, 0.25], [0.25, 0.75], [0.75, 0.75], [0.75, 0.25] ]
	.map(p => [p[0]*cs.width, p[1]*cs.height])
    	.forEach(p => { ctx.lineTo(p[0],p[1]); });
ctx.stroke();

図7 n次のヒルベルト曲線を描く手続き

function hilbert(n, R) {
  if (n > 1) {
    hilbert(n-1, R’=f(R,"左上"));
    hilbert(n-1, R’=f(R,"左下"));
    hilbert(n-1, R’=f(R,"右下"));
    hilbert(n-1, R’=f(R,"右上"));
  } else {
    (Rで示される座標上で1次のヒルベルト曲線を描くコード)
  }

図10 座標変換ルールを格納した 配列を定義するコード

var tm = [
  // tm[0]
    [ [   0,  1/2,   0],
      [ 1/2,    0,   0],
      [   0,    0,   1] ],
  // tm[1]
    [ [ 1/2,    0,   0], 
      [   0,  1/2, 1/2], 
      [   0,    0,   1] ],
  // tm[2]
    [ [ 1/2,    0, 1/2],
      [   0,  1/2, 1/2], 
      [   0,    0,   1] ],
  // tm[3]
    [ [   0, -1/2,   1], 
      [-1/2,    0, 1/2], 
      [   0,    0,   1] ]
]

図11 1次から8次までのヒルベルト曲線を描くコード

// the canvas and its graphic context
var cs  = document.getElementById('theCanvas');
var ctx = cs.getContext('2d');

// line style
var colors = [ 'gray', 'navy', 'purple', 'brown',
                'red', 'orange', 'yellowgreen', 'skyblue' ];
var widths = [5, 4, 3, 2, 2, 1, 1, 0.5 ];

var tm = [
  [ [  0,  1/2,   0], [ 1/2,   0,   0], [0, 0, 1] ],
  [ [1/2,    0,   0], [   0, 1/2, 1/2], [0, 0, 1] ],
  [ [1/2,    0, 1/2], [   0, 1/2, 1/2], [0, 0, 1] ],
  [ [  0, -1/2,   1], [-1/2,   0, 1/2], [0, 0, 1] ]
];
var E = [ [ 1, 0, 0], [0, 1, 0], [0, 0, 1] ];

function affine_transform(m, p) {
  return [ m[0][0] * p[0] + m[0][1] * p[1] + m[0][2],
           m[1][0] * p[0] + m[1][1] * p[1] + m[1][2] ];
}

function mat_mul(m0, m1) {
  return [ [m0[0][0]*m1[0][0]+m0[0][1]*m1[1][0]+m0[0][2]*m1[2][0],
            m0[0][0]*m1[0][1]+m0[0][1]*m1[1][1]+m0[0][2]*m1[2][1],
            m0[0][0]*m1[0][2]+m0[0][1]*m1[1][2]+m0[0][2]*m1[2][2]],
           [m0[1][0]*m1[0][0]+m0[1][1]*m1[1][0]+m0[1][2]*m1[2][0],
            m0[1][0]*m1[0][1]+m0[1][1]*m1[1][1]+m0[1][2]*m1[2][1],
            m0[1][0]*m1[0][2]+m0[1][1]*m1[1][2]+m0[1][2]*m1[2][2]],
           [m0[2][0]*m1[0][0]+m0[2][1]*m1[1][0]+m0[2][2]*m1[2][0],
            m0[2][0]*m1[0][1]+m0[2][1]*m1[1][1]+m0[2][2]*m1[2][1],
            m0[2][0]*m1[0][2]+m0[2][1]*m1[1][2]+m0[2][2]*m1[2][2]] ];
}
function hilbert(n, m) {
  if (n > 0) {
    tm.forEach(mm => { hilbert(n-1, mat_mul(m, mm)); });
  } else {
    [ [0.25, 0.25], [0.25, 0.75], [0.75, 0.75], [0.75, 0.25] ]
        .map(p => affine_transform(m, p))
        .map(p => [p[0]*cs.width, p[1]*cs.height])
        .forEach(p => { ctx.lineTo(p[0],p[1]); });
  }
}

function drawHilbert(i) {
  ctx.beginPath();
  ctx.lineWidth   = widths[i];
  ctx.strokeStyle = colors[i];
  hilbert(i, E);
  ctx.stroke();
}

for(i=0; i<colors.length; i++) { drawHilbert(i); }

香川大学SLPからお届け!(Vol.65掲載)

投稿日:2020.03.25 | カテゴリー: コード

筆者:重松 亜夢

 はじめまして!香川大学の重松亜夢です。2019年秋にSLPの所長を引き継ぎま
した。SLPの最近の主な活動はチーム開発です。2019年12月には部員が最近の活動をブログに投稿し、Advent Calendarを作成しました。また、2020年1月には餅つきで親交を深めました。
 今回は、Webアプリケーションに認証機能を実装します。具体的には、米Google社の「Google Cloud Platform」のAPIを使って「Googleでログイン」を実装します。

シェルスクリプトマガジン Vol.65は以下のリンク先でご購入できます。

図9 「.env」ファイルに記述する内容

SAMPLE_HOST="localhost:1323"
GOOGLE_CLIENT_ID="クライアントID"
GOOGLE_CLIENT_SECRET="クライアントシークレット"

図10 「main.go」ファイルに記述する内容

package main
import (
  route "example.com/user_name/sample-app/route"
)
func main() {
  route.Echo.Logger.Fatal(route.Echo.Start(":1323"))
}

図11 「router.go」ファイルに記述する内容

package route
import (
  "fmt"
  "log"
  "os"
  "example.com/user_name/sample-app/handler"
  "github.com/joho/godotenv"
  "github.com/labstack/echo/v4"
  "github.com/labstack/echo/v4/middleware"
  "github.com/stretchr/gomniauth"
  "github.com/stretchr/gomniauth/providers/google"
  "github.com/stretchr/signature"
)
var Echo *echo.Echo
func init() {
  e := echo.New()
  err := setupOAuth()
  if err != nil {
    log.Fatal("Error loading .env file")
  }
  e.Use(middleware.Logger())
  e.GET("/auth/login/:provider", handler.LoginHandler)
  e.GET("/auth/callback/:provider", handler.CallbackHandler)
  Echo = e
}

図12 「router.go」ファイルに追記する内容

func setupOAuth() error {
  err := godotenv.Load()
  if err != nil {
    return err
  }
  host := os.Getenv("SAMPLE_HOST")
  googleCallbackURL := fmt.Sprintf("http://%s/auth/callback/google", host)
  gomniauth.SetSecurityKey(signature.RandomKey(64))
  gomniauth.WithProviders(
    google.New(
      os.Getenv("GOOGLE_CLIENT_ID"),
      os.Getenv("GOOGLE_CLIENT_SECRET"),
      googleCallbackURL,
    ),
  )
  return nil
}

図13 「sesseion.go」ファイルに記述する内容

package handler
import (
  "net/http"
  "github.com/labstack/echo/v4"
  "github.com/stretchr/gomniauth"
  "github.com/stretchr/objx"
)
func LoginHandler(c echo.Context) error {
  provider, err := gomniauth.Provider(c.Param("provider"))
  if err != nil {
    return err
  }
  authURL, err := provider.GetBeginAuthURL(nil, nil)
  if err != nil {
    return err
  }
  return c.Redirect(http.StatusTemporaryRedirect, authURL)
}

図13 「sesseion.go」ファイルに記述する内容

package handler
import (
  "net/http"
  "github.com/labstack/echo/v4"
  "github.com/stretchr/gomniauth"
  "github.com/stretchr/objx"
)
func LoginHandler(c echo.Context) error {
  provider, err := gomniauth.Provider(c.Param("provider"))
  if err != nil {
    return err
  }
  authURL, err := provider.GetBeginAuthURL(nil, nil)
  if err != nil {
    return err
  }
  return c.Redirect(http.StatusTemporaryRedirect, authURL)
}

図14 「sesseion.go」ファイルに追記する内容

func CallbackHandler(c echo.Context) error {
  provider, err := gomniauth.Provider(c.Param("provider"))
  if err != nil {
    return err
  }
  omap, err := objx.FromURLQuery(c.QueryString())
  if err != nil {
    return err
  }
  _, err = provider.CompleteAuth(omap)
  if err != nil {
    return err
  }
  return c.String(http.StatusOK, "Login Success!")
}

図16 「sesseion.go」ファイルのCallbackHandler関数の変更コード

  creds, err := provider.CompleteAuth(omap)
  if err != nil {
    return err
  }
  user, err := provider.GetUser(creds)
  if err != nil {
    return err
  }
  authCookieValue := objx.New(map[string]interface{}{
    "name":      user.Name(),
    "email":     user.Email(),
    "avatarURL": user.AvatarURL(),
  }).MustBase64()
  cookie := &http.Cookie{
    Name:    "auth",
    Value:   authCookieValue,
    Path:    "/",
    Expires: time.Now().Add(24 * time.Hour),
  }
  c.SetCookie(cookie)
  return c.Redirect(http.StatusTemporaryRedirect, "/")

図17 「router.go」ファイルに追記する内容

type TemplateRenderer struct {
  templates *template.Template
}
func (t *TemplateRenderer) Render(w io.Writer, 
                                  name string, data interface{},
                                  c echo.Context) error {
  return t.templates.ExecuteTemplate(w, name, data)
}

図18 「router.go」ファイルのinit関数に挿入する内容

  renderer := &TemplateRenderer{
    templates: template.Must(template.ParseGlob("templates/*.html")),
  }
  e.Renderer = renderer
  e.GET("/", handler.MainPageHandler)

図19 「handler.go」ファイルに記述する内容

package handler
import (
  "net/http"
  "github.com/labstack/echo/v4"
  "github.com/stretchr/objx"
)
func MainPageHandler(c echo.Context) error {
  auth, err := c.Cookie("auth")
  if err != nil {
    return c.Render(http.StatusOK, 
                    "welcome", map[string]interface{}{
      "title": "Welcome",
    })
  }
  userData := objx.MustFromBase64(auth.Value)
  return c.Render(http.StatusOK, "top", map[string]interface{}{
    "name":      userData["name"],
    "email":     userData["email"],
    "avatarURL": userData["avatarURL"],
    "title":     "TopPage",
  })
}

図20 「index.html」ファイルに記述する内容

{{define "top"}}
  {{template "head" .}}
  <img src={{.avatarURL}} width="10%">
  <h2>Hello, {{.name}}</h2>
  Your Email : {{.email}}
{{end}}
{{define "welcome"}}
  {{template "head" .}}
  <h1>ようこそ</h1>
  <ul>
    <li>
      <a href="/auth/login/google">Googleでログイン</a>
    </li>
  </ul>
{{end}}

図21 「base.html」ファイルに記述する内容

{{define "head"}}
  <title>{{ .title }} / Sample-App</title>
{{end}}

図23 「router.go」ファイルのinit関数定義部分に挿入する内容

e.Pre(middleware.RemoveTrailingSlash())
e.Group("", authCheckMiddleware())

図24 「router.go」ファイルの末尾に追記する内容

func authCheckMiddleware() echo.MiddlewareFunc {
  return func(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
      _, err := c.Cookie("auth")
      if err != nil {
        return c.Redirect(http.StatusTemporaryRedirect, "/")
      }
      return next(c)
    }
  }
}

図25 認証後だけアクセスできるルートを設定する例

authCheck := e.Group("", authCheckMiddleware())
authCheck.GET("/fuga", handler.SamplePage)

最終回 不正アクセスを通知する

投稿日:2020.01.30 | カテゴリー: 記事

 ログの情報を分析して、管理者に警告メッセージを送信するのは、サーバーの運用管理ではよく使う手法です。インターネット側からアクセスできるようにしたサーバーの場合は、特にセキュリティ関連の通知ができると便利です。

 そこで、暗号化通信の「SSH」(Secure SHell)による、Linuxサーバーへのログイン認証のログを監視して、リモートログインや、不正(らしき)アクセスを検知したときにSNSに通知するシェルスクリプトを作成します(図1)。

図1 ログを解析して通知をSNSに送信するシェルスクリプト

Vol.64 補足情報

投稿日:2020.01.25 | カテゴリー: コード

姐のNOGYO

p.31にあるUSPファームのサイトの「http://www.uspeace.jp/」は「https://farm.usp-lab.com/」の誤りです。お詫びして訂正いたします。

情報は随時更新致します。

Vol.64

投稿日:2020.01.25 | カテゴリー: バックナンバー

 「C」や「C++」のような高性能や高効率なソフトウエア開発に適しており、なおかつ、安全性を重視した次世代のプログラミング言語「Rust」が最近注目されています。特集1では、Rustの特徴、使いどころ、開発環境構築方法、プログラミングのやり方などを、初心者にも分かりやすいように、紹介しています。また、CSV 形式のデータを分析するという実用的なプログラムを扱っています。
 特集2では、ビジュアル開発ツール「Viscuit」(ビスケット)を使ったプログラミングを紹介しています。「メガネ」というツールだけで、驚くようなプログラムが、子供でも作成できます。Viscuitでコンピュータサイエンスを体験してみましょう。
 このほか、連載「ユニケージ新コードレビュー」ではプログラミング言語「AWK」の使いどころを、連載「センサーボードで学ぶ電子回路の制御」ではリアルタイムクロック(RTC)を実装する方法を紹介しています。
 今回も読み応え十分のシェルスクリプトマガジン Vol.64。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

シェルスクリプトマガジンvol.64 Web掲載記事まとめ

投稿日:2020.01.25 | カテゴリー: コード

004 レポート 専修大学3年次の最終発表会
005 NEWS FLASH
008 特集1 はじめてのRust/河野達也 コード掲載
031 姐のNOGYO
032 特集2 Viscuitで学ぶコンピュータサイエンス/渡辺勇士
042 ラズパイセンサーボードで学ぶ 電子回路の制御/米田聡
045 香川大学SLPからお届け!/清水赳 コード掲載
052 錆(さび)/桑原滝弥、イケヤシロウ
054 MySQL Shellを使おう/梶山隆輔
060 法林浩之のFIGHTING TALKS/法林浩之
062 バーティカルバーの極意/飯尾淳
066 Webアプリの正しい作り方/しょっさん コード掲載
076 円滑コミュニケーションが世界を救う!/濱口誠一
078 漢のUNIX/後藤大地 コード掲載
084 中小企業手作りIT化奮戦記/菅雄一
090 ユニケージ新コードレビュー/坂東勝也
096 Techパズル/gori.sh
098 コラム「新しい風が吹いてくる」/シェル魔人

特集1 はじめてのRust(Vol.64記載)

投稿日:2020.01.25 | カテゴリー: コード

著者 :河野 達也

最近、Rustというプログラミング言語の名前をよく見かけるようになりました。米Amazon Web Services社、米Dropbox社、米Facebook社、米Mozilla財団などは、Rustを使ってミッションクリティカルなソフトウエアを開発しています。Rust とはどんな言語でしょうか。シンプルなプログラムの開発を通してRustの世界に飛び込みましょう。

シェルスクリプトマガジン Vol.64は以下のリンク先でご購入できます。

Part1

図1 Rust のプログラムの例

// src/main.rsファイルの内容
//
// reqwestクレートを使うために、Cargo.tomlファイルのdependencies
// セクションに reqwest = "0.9" と書く

// main関数。プログラムが起動すると最初に呼ばれる
// この関数は引数を取らず、Result型の値を返す
fn main() -> Result<(), Box<dyn std::error::Error>> {
  // WebサービスのURI文字列をservice_uri変数にセットする
  let service_uri =
    "http://weather.livedoor.com/forecast/webservice/json/v1?city=130010";
  // 指定したURIに対してGETリクエストを送信し、レスポンスボディを取得する
  let body = reqwest::get(service_uri)?.text()?;
  // レスポンスボディを表示する
  println!("body = {:?}", body);
  // Okを返してmain関数から戻る
  // return文は不要だがこの行だけ行末にセミコロンがないことに注意
  Ok(())
}

図12 エラーになるRustのプログラムの例

// Circle型を定義する
#[derive(Debug)]
struct Circle {
  r: f64, // 円の半径、f64型
}

fn main() {
  // Circleの値をつくる
  let a = Circle{ r: 5.8 };
  // take_circle()を呼ぶとCircleの所有権が
  // 変数aから関数の引数bにムーブ
  take_circle(a);
  // aの内容を表示
  //(所有権がないのでコンパイルエラーになる)
  println!("{:?}", a);
}

fn take_circle(b: Circle) {
  // 何らかの処理

} // ここで引数bがスコープを抜けるのでCircleは削除

図21 sqrt()関数の定義コード

/// この関数はニュートン法で平方根を求めます。
fn sqrt(a: f64) -> f64 {
    // 未実装を表す。実行するとエラーになりプログラムが終了する
  unimplemented!()
}

図23 let文とif式を追加したsqrt()関数の定義コード

fn sqrt(a: f64) -> f64 {
  // 変数x0を導入し、探索の初期値に設定する
  let x0 = if a > 1.0 {
    a
  } else {
    1.0
  };
}

図24 loop式を追加したsqrt()関数の定義コード

fn sqrt(a: f64) -> f64 {
  let x0 = if a > 1.0 {
    a
  } else {
    1.0
  };
  // loopで囲まれたブロックは
  // break式が呼ばれるまで繰り返し実行される
  loop {
    // √aのニュートン法による漸化式で次項を求める
    let x1 = (x0 + a / x0) / 2.0;
    if x1 >= x0 {
      break;   // 値が減少しなくなったらloopから抜ける
    }
    x0 = x1;
  }
}

図25 戻り値の記述を追加したsqrt()関数の定義コード

fn sqrt(a: f64) -> f64 {
  let x0 = if a > 1.0 {
    a
  } else {
    1.0
  };
  loop {
    let x1 = (x0 + a / x0) / 2.0;
      if x1 >= x0 {
        break;
      }
      x0 = x1;
  }
  x0
}

図26 mut修飾子を追加したsqrt()関数の定義コード

fn sqrt(a: f64) -> f64 {
  let mut x0 = if a > 1.0 {
    a
  } else {
    1.0
  };
  loop {
    let x1 = (x0 + a / x0) / 2.0;
    if x1 >= x0 {
      break;
    }
    x0 = x1;
  }
  x0
}

図27 main()関数のコード

fn main() {
  let a = 2.0;
  // aの値とニュートン法で求めた平方根を表示
  println!("sqrt({}) = {}", a, sqrt(a));
}

Part2

図2 Cargo.tomlファイルに追加する設定

[dependencies]
chrono = "0.4"
clap = "2.33"
csv = "1.1"
hdrhistogram = "6.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

図3 コマンドライン引数を処理するプログラム

// clapで定義されているclap::Appとclap::Argの
// 二つの型をスコープに入れる
use clap::{App, Arg};
fn main() {
  // clap::Appでコマンド名やバージョンなどを設定
  let arg_matches = App::new("trip-analyzer")
    .version("1.0")
    .about("Analyze yellow cab trip records")
    // INFILEという名前のコマンドライン引数を登録
    .arg(Arg::with_name("INFILE")
      .help("Sets the input CSV file")
      .index(1)  // 最初の引数
    )
    // get_matches()メソッドを呼ぶとユーザーが与えた
    // コマンドライン引数がパースされる
    .get_matches();
  // INFILEの文字列を表示。"{:?}"はデバッグ用文字列を表示
  println!("INFILE: {:?}", arg_matches.value_of("INFILE"));
}

図5 Option型の定義(抜粋)

enum Option<T> {
  None,    // Noneバリアント
  Some(T), // Someバリアント。T型の値を持つ
}

図6 Result型の定義(抜粋)

Result<T, E> {
  Ok(T),   // 処理成功を示すバリアント。T型の成功時の値を持つ
  Err(E),  // 処理失敗を示すバリアント。E型のエラーを示す値を持つ
}

図7 match式とif let式の使用例

■match式の使用例

match arg_matches.value_of("INFILE") {
  // 値がパターンSome(..)にマッチするなら、
  // 包んでいる&strをinfile変数にセットし、=>以降の節を実行
  Some(infile) => println!("INFILE is {}", infile),
  // 値がパターンNoneにマッチするなら、=>以降の節を実行
  None => eprintln!("Please specify INFILE"),
}

■if let式の使用例

// 値がパターンSome(..)にマッチするなら、
// 包んでいる&strをinfile変数にセットし、true節を実行
if let Some(infile) = arg_matches.value_of("INFILE") {
  println!("INFILE is {}", infile);
} else {
  // そうでなければelse節を実行
  eprintln!("Please specify INFILE");
}

図8 コマンドライン引数を必須にする変更

(略)
    .arg(Arg::with_name("INFILE")
      .help("Sets the input CSV file")
      .index(1)
      .required(true) // この行を追加
    )
    // コマンドライン引数がない場合は
    // ここでエラーメッセージを表示してプログラム終了
    .get_matches();
  // 次の行を追加
  let infile = arg_matches.value_of("INFILE").unwrap();
  // 次の行を変更
  println!("INFILE: {}", infile);
}

図9 std::fmtモジュールのDebugトレイトの定義(抜粋)

trait Debug {
  fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error>;
}

図10 analyze()関数の定義コードを追加

use clap::{App, Arg};
// Errorトレイトをスコープに入れる
use std::error::Error;
// CSVファイルのパスを引数に取り、データを分析する
fn analyze(infile: &str) -> Result<String, Box<dyn Error>> {
  // CSVリーダーを作る。失敗したときは「?」後置演算子の働きにより、
  // analyze()関数からすぐにリターンし、処理の失敗を表すResult::Errを返す
  let mut reader = csv::Reader::from_path(infile)?;
  // 処理に成功したので(とりあえず空の文字列を包んだ)Result::Okを返す
  Ok(String::default())
}
fn main() {
(略)

図11 main()関数の定義コードを変更

fn main() {
(略)
  let infile = arg_matches.value_of("INFILE").unwrap();
  match analyze(infile) {
    Ok(json) => println!("{}", json),
    Err(e) => {
      eprintln!("Error: {}", e);
      std::process::exit(1);
    }
  }
}

図12 analyze()関数の定義コードを変更

fn analyze(infile: &str) -> Result<String, Box<dyn Error>> {
  let mut reader = csv::Reader::from_path(infile)?;
  // レコード数をカウントする
  let mut rec_counts = 0;
  // records()メソッドでCSVのレコードを一つずつ取り出す
  for result in reader.records() {
    // resultはResult<StringRecord, Error>型なので?演算子で
    // StringRecordを取り出す
    let trip = result?;
    rec_counts += 1;
    // 最初の10行だけ表示する
    if rec_counts <= 10 {
      println!("{:?}", trip);
    }
  }
  // 読み込んだレコード数を表示する
  println!("Total {} records read.", rec_counts);
  Ok(String::default())
}

図15 構造体Tripの定義コードを追加

type LocId = u16;
#[derive(Debug)]  // Debugトレイトの実装を自動導出する
struct Trip {
  pickup_datetime: String,  // 乗車日時
  dropoff_datetime: String, // 降車日時
  pickup_loc: LocId,        // 乗車地ID
  dropoff_loc: LocId,       // 降車地ID
}

図16 Tripをデシリアライズするための書き換え(その1)

// SerdeのDeserializeトレイトをスコープに入れる
use serde::Deserialize;
type LocId = u16;
// serde::Deserializeを自動導出する
#[derive(Debug, Deserialize)]
struct Trip {
  // renameアトリビュートでフィールド名と
  // CSVのカラム名を結びつける
  #[serde(rename = "tpep_pickup_datetime")]
  pickup_datetime: String,
  #[serde(rename = "tpep_dropoff_datetime")]
  dropoff_datetime: String,
  #[serde(rename = "PULocationID")]
  pickup_loc: LocId,
  #[serde(rename = "DOLocationID")]
  dropoff_loc: LocId,
}

図17 analyze()関数の定義コードを変更

fn analyze(infile: &str) -> Result<String, Box<dyn Error>> {
(略)
  let mut rec_counts = 0;
  // records()メソッドをdeserialize()メソッドに変更する
  for result in reader.deserialize() {
    // どの型にデシリアライズするかをdeserialize()メソッドに
    // 教えるために、trip変数に型アノテーションを付ける
    let trip: Trip = result?;
    rec_counts += 1;
(略)
}

図19 RecordCounts構造体の定義を追加

use serde::{Deserialize, Serialize};  // Serializeを追加
// serde_jsonでJSON文字列を生成するためにSerializeを自動導出する
#[derive(Debug, Serialize)]
struct RecordCounts {
  read: u32,    // CSVファイルから読み込んだ総レコード数
  matched: u32, // 乗車地や降車地などの条件を満たしたレコードの数
  skipped: u32, // 条件は満たしたが異常値により除外したレコードの数
}

図20 RecordCountsのデフォルト値をつくる関数を定義

impl Default for RecordCounts {
  fn default() -> Self {
    Self {
      read: 0, // read: u32::default(), としてもよい
      matched: 0,
      skipped: 0,
    }
  }
}

図21 analyze()関数の定義部分のrec_counts変数が使われている行を変更

fn analyze(infile: &str) -> Result<String, Box<dyn Error>> {
(略)
  let mut rec_counts = RecordCounts::default();
(略)
  for result in reader.deserialize() {
(略)
    rec_counts.read += 1;
    if rec_counts.read <= 10 {
(略)
  }
  println!("{:?}", rec_counts); // フォーマット文字列を変更
(略)
}

図22 日時を変換するparse_datetime()関数の定義コード

// chronoの利用にほぼ必須となる型やトレイトを一括してスコープに入れる
use chrono::prelude::*;
// NaiveDateTimeは長いのでDTという別名を定義
// chrono::NaiveDateTimeはタイムゾーンなしの日時型
type DT = NaiveDateTime;  
// ついでにResult型の別名を定義する
type AppResult<T> = Result<T, Box<dyn Error>>;
// 日時を表す文字列をDT型に変換する
fn parse_datetime(s: &str) -> AppResult<DT> {
  DT::parse_from_str(s, "%Y-%m-%d %H:%M:%S").map_err(|e| e.into())
}

図23 分析レコードを絞り込むための関数定義コードを追加(その1)

// LocIdがミッドタウン内ならtrueを返す
fn is_in_midtown(loc: LocId) -> bool {
  // LocIdの配列を作る
  let locations = [90, 100, 161, 162, 163, 164, 186, 230, 234];
  // 配列に対してバイナリーサーチする。
  // locと同じ値があればOk(値のインデックス)が返る
  locations.binary_search(&loc).is_ok()
}
// ロケーションIDがJFK国際空港ならtrueを返す
fn is_jfk_airport(loc: LocId) -> bool {
  loc == 132
}

図24 分析レコードを絞り込むための関数定義コードを追加(その2)

// 月曜から金曜ならtrueを返す
fn is_weekday(datetime: DT) -> bool {
  // 月:1, 火:2, .. 金:5, 土:6, 日:7
  datetime.weekday().number_from_monday() <= 5
}

図25 分析レコードを絞り込むためにanalyze()関数の定義コードを変更

// 戻り値型をAppResultに変更
fn analyze(infile: &str) -> AppResult<String> {
(略)
  for result in reader.deserialize() {
(略)
    if is_jfk_airport(trip.dropoff_loc) && is_in_midtown(trip.pickup_loc) {
      let pickup = parse_datetime(&trip.pickup_datetime)?;
      if is_weekday(pickup) {
        rec_counts.matched += 1;
      }
    }
  }
(略)
}

図27 統計的な処理をするためのコード

use hdrhistogram::Histogram;
// DurationHistogramsをタプル構造体として定義する
// この構造体はHistogramを24個持つことで、1時間刻みの時間帯ごとに
// 所要時間のヒストグラムデータを追跡する。
// Vec<T>型は配列の一種
struct DurationHistograms(Vec<Histogram<u64>>);
// 関連関数やメソッドを実装するためにimplブロックを作る
impl DurationHistograms {
  // Histogramsを初期化する関連関数。記録する上限値を引数に取る
  fn new() -> AppResult<Self> {
    let lower_bound = 1; // 記録する下限値。1秒
    let upper_bound = 3 * 60 * 60; // 記録する上限値。3時間
    let hist = Histogram::new_with_bounds(lower_bound, upper_bound, 3)
      .map_err(|e| format!("{:?}", e))?;
    // histの値を24回複製してVec<T>配列に収集する
    let histograms = std::iter::repeat(hist).take(24).collect();
    Ok(Self(histograms))
  }
}

図28 所要時間を登録するためのメソッドを追加(その1)

impl DurationHistograms {
  fn new() -> AppResult<Self> {
(略)
  }
  fn record_duration(&mut self, pickup: DT, dropoff: DT) -> AppResult<()> {
    // 所要時間を秒で求める。結果はi64型になるがas u64でu64型に変換
    let duration = (dropoff - pickup).num_seconds() as u64;
(略)

図29 所要時間を登録するためのメソッドを追加(その2)

impl DurationHistograms {
(略)
    let duration = (dropoff - pickup).num_seconds() as u64;
    // 20分未満はエラーにする
    if duration < 20 * 60 {
      Err(format!("duration secs {} is too short.", duration).into())
    } else {
      let hour = pickup.hour() as usize;
      // タプル構造体の最初のフィールドの名前は0になるので、
      // self.0でVec<Histogram>にアクセスできる。さらに個々の
      // Histogramにアクセスするには [インデックス] で
      // その要素のインデックスを指定する
      self.0[hour]
        // Histogramのrecord()メソッドで所要時間を記録する
        .record(duration)
        // このメソッドはHistogramの作成時に設定した上限(upper_bound)
        // を超えているとErr(RecordError)を返すので、map_err()で
        // Err(String)に変換する
        .map_err(|e| {
          format!("duration secs {} is too long. {:?}", duration, e).into()
        })
    }
  }
}

図30 統計処理をするためにanalyze()関数の定義コードを変更

fn analyze(infile: &str) -> AppResult<String> {
(略)
  let mut rec_counts = RecordCounts::default();
  let mut hist = DurationHistograms::new()?;
  // for式を変更
  for (i, result) in reader.deserialize().enumerate() {
(略)
    if is_jfk_airport((略)) {
(略)
      if is_weekday(pickup) {
        rec_counts.matched += 1;
        let dropoff = parse_datetime(&trip.dropoff_datetime)?;
        hist.record_duration(pickup, dropoff)
          .unwrap_or_else(|e| {
            eprintln!("WARN: {} - {}. Skipped: {:?}", i + 2, e, trip);
            rec_counts.skipped += 1;
          });
      }
    }       
(略)
  }
(略)
}

図32 DisplayStats構造体の定義コード

#[derive(Serialize)]
struct DisplayStats {
  record_counts: RecordCounts,
  stats: Vec<StatsEntry>,
}

図33 DisplayStats構造体の定義コード

#[derive(Serialize)]
struct StatsEntry {
  hour_of_day: u8, // 0から23。時(hour)を表す
  minimum: f64,    // 最短の所要時間
  median: f64,     // 所要時間の中央値
  #[serde(rename = "95th percentile")]
  p95: f64,        // 所要時間の95パーセンタイル値
}

図34 DisplayStats型にnew()関連関数を定義するコード

impl DisplayStats {
  fn new(record_counts: RecordCounts, histograms: DurationHistograms) -> Self {
    let stats = histograms.0.iter().enumerate()
      // mapメソッドでhdrhistogram::Histogram値からStatsEntry値を作る
      .map(|(i, hist)| StatsEntry {
        hour_of_day: i as u8,
        minimum: hist.min() as f64 / 60.0,
        median: hist.value_at_quantile(0.5) as f64 / 60.0,
        p95: hist.value_at_quantile(0.95) as f64 / 60.0,
      })
      .collect();
      Self {
        record_counts,
        stats,
      }
  }
}

図35 analyze()関数の定義コードを書き換える

let display_stats = DisplayStats::new(rec_counts, hist);
let json = serde_json::to_string_pretty(&display_stats)?;
Ok(json)

香川大学SLPからお届け!(Vol.64掲載)

投稿日:2020.01.25 | カテゴリー: コード

著者:清水赳

前回に引き続き、OSSのシステム監視ツール「Prometheus」を「Itamae」というプロビジョニングツールを使って、サーバー監視システムを構築する方法を紹介します。今回は、Prometheusでノード情報を取得・計算する方法や、外形監視、データの可視化方法について解説します。

シェルスクリプトマガジン Vol.64は以下のリンク先でご購入できます。

図5 「./cookbooks/blackbox_exporter/default.yml」ファイルに記述する内容

url_head = "https://github.com/prometheus/blackbox_exporter/releases/download"
url_ver  = "v0.16.0"
origin_dir = "blackbox_exporter-0.16.0.linux-amd64"
install_dir = "/usr/local/bin"
#==== Blackbox Exporterのインストール
execute "download blackbox_exporter" do
  cwd "/tmp"
  command "wget #{File.join(url_head, url_ver, origin_dir)}.tar.gz"
end
execute "extract blackbox_exporter" do
  cwd "/tmp"
  command "tar xvfz #{origin_dir}.tar.gz"
end
execute "install blackbox_exporter" do
  cwd "/tmp"
  command "mv #{File.join(origin_dir, "blackbox_exporter")} #{install_dir}"
end
#==== Blackbox Exporterをサービスとして登録する
remote_file "/etc/systemd/system/blackbox_exporter.service" do
  owner "root"
  group "root"
  source "files/etc/systemd/system/blackbox_exporter.service"
end
remote_directory "/etc/blackbox_exporter" do
  owner "root"
  group "root"
  source "files/etc/blackbox_exporter"
end
service "blackbox_exporter" do
  action :restart
end

図6 「./cookbooks/blackbox_exporter/files/etc/systemd/system/blackbox_exporter.service」ファイルに記述する内容

[Unit]
Description=BlackboxExporter

[Service]
ExecStart=/usr/local/bin/blackbox_exporter --config.file /etc/blackbox_exporter/blackbox.yml

[Install]
WantedBy=multi-user.target

図7 「./roles/client.rb」ファイルの編集内容

include_recipe "../cookbooks/node_exporter" # 前回追加
include_recipe "../cookbooks/blackbox_exporter" # 今回追加

図8 「./cookbooks/blackbox_exporter/files/etc/blackbox_exporter/blackbox.yml」ファイルに記述する内容

modules:
  http_2xx:
    prober: http
    http:
  http_post_2xx:
    prober: http
    http:
      method: POST
  icmp:
    prober: icmp

図9 「./cookbooks/prometheus/files/etc/prometheus/prometheus.yml」ファイルに追加する内容

  - job_name: 'blackbox'
    metrics_path: /probe
    params:
      module: [http_2xx]
    static_configs:
      - targets:
        - localhost
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: localhost:9115

Webアプリケーションの正しい作り方(Vol.64記載)

投稿日:2020.01.25 | カテゴリー: コード

著者:しょっさん

ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならないでしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。第4回は、3回目のイテレーションを実施し、システムに必要な機能を実装していきます。

シェルスクリプトマガジン Vol.64は以下のリンク先でご購入できます。

図3 マイグレーションファイルのテンプレート

'use strict';

module.exports = {
  up: (queryInterface, Sequelize) => {
    /*
      Add altering commands here.
      Return a promise to correctly handle asynchronicity.

      Example:
      return queryInterface.createTable('users', { id: Sequelize.INTEGER });
    */
  },

  down: (queryInterface, Sequelize) => {
    /*
      Add reverting commands here.
      Return a promise to correctly handle asynchronicity.

      Example:
      return queryInterface.dropTable('users');
    */
  }
};

図4 経費清算のマイグレーションファイルにuser_idのカラムを追加するように修正した

'use strict';

module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.addColumn('expenses',
      'user_id', {
      type: Sequelize.UUID,
      foreignKey: true,
      references: {
        model: 'users',
        key: 'id',
      },
      onUpdate: 'RESTRICT',
      onDelete: 'RESTRICT',
    }
    );
  },

  down: (queryInterface, Sequelize) => {
    return queryInterface.removeCulumn('expenses',
      user_id);
  }
};

図5 config/database.tsファイル

import { Sequelize } from 'sequelize';

export default function (): Sequelize {
  const env: string = process.env.NODE_ENV || 'development';
  const config: any = require('./config.json')[env];

  if (config.use_env_variable) {
    const config_url: any = process.env[config.use_env_variable];
    return new Sequelize(config_url, config);
  } else {
    return new Sequelize(config.database, config.username, config.password, config);
  }
}

図6 権限テーブルのモデルファイル

import { Sequelize, Model, DataTypes } from 'sequelize';
import * as config from '../config/database';

const sequelize: Sequelize = config.default();

class Role extends Model {
  public id!: number;
  public user_id!: string;
  public name!: string;
  public readonly careated_at!: Date;
  public readonly updated_at!: Date;
}

Role.init({
  id: {
    type: DataTypes.INTEGER,
    autoIncrement: true,
    allowNull: false,
    primaryKey: true,
  },
  user_id: {
    type: DataTypes.UUID,
    allowNull: false,
    validate: {
      isUUID: 4
    }
  },
  name: {
    type: DataTypes.STRING(128),
    allowNull: false,
    defaultValue: ''
  }
}, {
  tableName: 'roles',
  underscored: true,
  sequelize: sequelize
});

export { Role };

図7 ユーザーマスターテーブルのモデルファイル

import { Sequelize, Model, DataTypes } from 'sequelize';
import { Expense } from './expense';
import { Role } from './role';
import * as config from '../config/database';

const sequelize: Sequelize = config.default();

class User extends Model {
  public id!: string;
  public boss_id?: string;
  public first_name?: string;
  public last_name!: string;
  public email!: string;
  public hash!: string;
  public deleted_at?: Date;
  public readonly careated_at!: Date;
  public readonly updated_at!: Date;
}

User.init({
  id: {
    type: DataTypes.UUID,
    allowNull: false,
    defaultValue: DataTypes.UUIDV4,
    primaryKey: true,
    validate: {
      isUUID: 4
    }
  },
  boss_id: {
    type: DataTypes.UUID
  },
  first_name: {
    type: DataTypes.STRING(32)
  },
  last_name: {
    type: DataTypes.STRING(32),
    allowNull: false
  },
  email: {
    allowNull: false,
    unique: true,
    type: DataTypes.STRING,
    validate: {
      isEmail: true
    }
  },
  hash: {
    allowNull: false,
    type: DataTypes.STRING(256)
  },
  deleted_at: {
    type: DataTypes.DATE,
    defaultValue: null
  },
}, {
  tableName: 'users',
  underscored: true,
  sequelize: sequelize
});

User.hasMany(Role, {
  sourceKey: 'id',
  foreignKey: 'user_id',
  as: 'roles'
})

User.hasMany(Expense, {
  sourceKey: 'id',
  foreignKey: 'user_id',
  as: 'expenses'
});

User.hasOne(User, {
  sourceKey: 'id',
  foreignKey: 'boss_id',
  as: 'users'
});

export { User };

図8 passportライブラリを使って、パスワード認証を行う部分(index.tsへの追加)

import bcrypt from 'bcrypt';
import passport from 'passport';
import { Strategy as LocalStrategy } from 'passport-local';
import { User } from './models/user';

// passport 初期化
app.use(passport.initialize());
app.use(passport.session());

// passport の認証定義
passport.use(new LocalStrategy({
  usernameField: 'user',
  passwordField: 'password'
}, (username, password, done) => {
  User.findOne({ where: { email: username, deleted_at: null } }).then(user => {
    if (!user || !bcrypt.compareSync(password, user.hash))
      return done(null, false);
    return done(null, user.get());
  })
}));

// passport 認証時のユーザ情報のセッションへの保存やセッションからの読み出し
passport.serializeUser((user: User, done) => {
  return done(null, user);
});
passport.deserializeUser((user: User, done) => {
  User.findByPk(user.id).then(user => {
    if (user) {
      done(null, user.get());
    } else {
      done(false, null);
    }
  })
});

図9 ログインの強制(index.ts への追加)

// ログインの強制
app.use((req, res, next) => {
  if (req.isAuthenticated())
    return next();
  if (req.url === '/' || req.url === '/login')
    return next();
  res.redirect('/login');
});

図10 src/routes/login.tsファイル(ログインスクリプト)

import Express from 'express';
const router = Express.Router();
import passport from 'passport';

// GET /login ユーザーログインフォーム
router.get('/', (req: Express.Request, res: Express.Response): void => {
  res.send('<h1>LOGIN</h1><form action="/login" method="post">ユーザーID:<input type="text" name="user" size="40"><br />パスワード<input type="password" name="password"><input type="submit" value="ログイン"><br /><a href="/login">ログイン</a><br /><a href="/expenses/submit">経費入力</a><br /><a href="/expenses/payment">支払い処理</a>');
});

// POST / ユーザーの認証処理
router.post('/',
  passport.authenticate('local', {
    successRedirect: '/',
    failureRedirect: '/login'
  })
);

export default router;

図11 src/routes/expenses/submit.tsファイルの修正

import Express from ‘express’;
const router = Express.Router();
import { Expense } from '../../models/expense’;

// GET /expenses/submit 入力フォーム
router.post('/', (req: Express.Request, res: Express.Response): void => {
  Expense.create(req.body)
    .then(result => {
      res.redirect(‘/‘);
    });
});

// POST /expenses/submit 経費の申請
router.get('/', (req: Express.Request, res: Express.Response): void => {
  const user = req!.user!.email;
  const id = req!.user!.id;
  res.send(<h2>経費入力</h2><form action="/expenses/submit" method="post"><input type="hidden" name="user_id" value="${id}">申請者名:<input type="text" name="user_name" value="${user}"><br />日付:<input type="date" name="date"><br />経費タイプ:<input type="text" name="type"><br />経費詳細:<input type="text" name="description"><br />金額:<input type="number" name="amount"><br /><input type="submit" value="経費申請"><br /><a href="/login">ログイン</a><br /><a href="/expenses/submit">経費入力</a><br /><a href="/expenses/payment">支払い処理</a>);
});

export default router;

図12 用意した型定義ファイル

interface UserModel {
  id: string;
  boss_id?: string;
  first_name: string;
  last_name: string;
  email: string;
}

declare namespace Express {
  export interface User extends UserModel { }
}

図13 認証関係をつかさどるAuthenticationクラスを記述した「src/controllers/auth/index.ts」ファイル

import bcrypt from 'bcrypt';
import { User } from "../../models/user";
import passport from 'passport';
import { Strategy as LocalStrategy } from 'passport-local';

export class Authentication {
  static initialize(app: any) {
    // passport 初期化
    app.use(passport.initialize());
    app.use(passport.session());

    passport.serializeUser(this.serializeUser);
    passport.deserializeUser(this.deserializeUser);
  }
  static serializeUser(user: any, done: any) {
    return done(null, user);
  }
  static deserializeUser(user: any, done: any) {
    User.findByPk(user.id)
      .then(user => {
        return done(null, user);
      })
      .catch(() => {
        return done(null, false);
      });
  }

  static verify(username: string, password: string, done: any) {
    User.findOne(
      {
        where: {
          email: username
        }
      }
    ).then(user => {
      if (!user || !bcrypt.compareSync(password, user.hash))
        return done(null, false);
      return done(null, user.get());
    });
  }

  static setStrategy() {
    // passport の認証定義
    const field = {
      usernameField: 'user',
      passwordField: 'password'
    };
    passport.use(new LocalStrategy(field, this.verify));
  }
}

図14 認証関係のテストケースを記述した「authentication.test.ts」ファイル

import { Authentication } from '../src/controllers/auth/index';
import { User } from '../src/models/user';

describe('authentication', () => {
  it('serialize', done => {
    const callback = (arg: any, user: User) => {
      expect(user.id).toBe('811FCB5D-7128-4AA6-BFEE-F1A8D3302CDA');
      expect(user.email).toBe('test@example.com');
      expect(arg).toBeNull();
      done();
    }
    const user_sample = {
      id: '811FCB5D-7128-4AA6-BFEE-F1A8D3302CDA',
      last_name: 'test',
      email: 'test@example.com',
    }

    Authentication.serializeUser(user_sample, callback);
  });

  it('deserialize - positive', done => {
    const callback = (arg: any, user: any) => {
      expect(arg).toBeNull();
      expect(user.id).toBe('811FCB5D-7128-4AA6-BFEE-F1A8D3302CDA');
      done();
    }
    const user_sample = {
      id: '811FCB5D-7128-4AA6-BFEE-F1A8D3302CDA',
      last_name: 'test',
      email: 'test@example.com',
    }

    Authentication.deserializeUser(user_sample, callback);
  });
  it('deserialize - negative', done => {
    const callback = (arg: any, user: any) => {
      expect(arg).toBeNull();
      expect(user).toBe(false);
      done();
    }
    const user_sample = {
      id: '',
      last_name: 'test',
      email: 'test@example.com',
    }

    Authentication.deserializeUser(user_sample, callback);
  });

  it('verify - positive', done => {
    const callback = (arg: any, user: any) => {
      expect(arg).toBeNull();
      expect(user.id).toBe('811FCB5D-7128-4AA6-BFEE-F1A8D3302CDA');
      expect(user.email).toBe('test@example.com');
      expect(user.last_name).toBe('test');
      done();
    }

    Authentication.verify('test@example.com', 'password', callback);
  })

  it('verify - negative', done => {
    const callback = (arg: any, user: any) => {
      expect(arg).toBeNull();
      expect(user).toBe(false);
      done();
    }

    Authentication.verify('test@example.com', 'incorrect', callback);
  })

  it('verify - deleted', done => {
    const callback = (arg: any, user: any) => {
      expect(arg).toBeNull();
      expect(user).toBe(false);
      done();
    }

    Authentication.verify('deleted@example.com', 'password', callback);
  })
})

図15 テストデータ作成用のシードファイル(src/seeders/*-demo-user.js)

'use strict';

module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.bulkInsert('users', [
      {
        id: '811FCB5D-7128-4AA6-BFEE-F1A8D3302CDA',
        email: 'test@example.com',
        last_name: 'test',
        hash: '$2b$10$IPwsYH8cAD9IarEGhj1/Vua2Lz4y/FD7GubAB.dNgfxgqx6i5heyy',
        created_at: new Date(),
        updated_at: new Date()
      },
      {
        id: '326260F7-2516-4C17-B8D1-DE50EF42C440',
        email: 'deleted@example.com',
        last_name: 'deleted',
        hash: '$2b$10$IPwsYH8cAD9IarEGhj1/Vua2Lz4y/FD7GubAB.dNgfxgqx6i5heyy',
        created_at: new Date(),
        updated_at: new Date(),
        deleted_at: new Date()
      }
    ]);
  },

  down: (queryInterface, Sequelize) => {
    return queryInterface.bulkDelete('users', {
      id: [
        '811FCB5D-7128-4AA6-BFEE-F1A8D3302CDA',
        '326260F7-2516-4C17-B8D1-DE50EF42C440'
      ]
    });
  }
};

漢のUNIX(Vol.64掲載)

投稿日:2020.01.25 | カテゴリー: コード

著者:後藤 大地

今回も引き続き、プログラミング言語の「Rust」について解説する。前回も取り上げたが、Rustの学習は「The Rust Programming Language」(https://doc.rust-lang.org/book/)に沿って進めるのがよいと思う。The Rust Programming Languageではまず、「数当てゲーム」(Guessing Game、英訳としては「推測ゲーム」)のプログラムを開発する。これによって、Rustのプログラミングを一通り学べる。

シェルスクリプトマガジン Vol.64は以下のリンク先でご購入できます。

図3 自動生成されるRustプログラムのソースコードファイル「src/main.rs」

fn main() {
    println!("Hello, world!");
}

図4 コンパイルに必要な構成情報や依存関係を記したファイル「Cargo.toml」

[package]
name = "guessing_game"
version = "0.1.0"
authors = ["Daichi GOTO <daichigoto@example.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

図6 ユーザーに対して入力を求め、その入力を受け付けて「あなたの予測値:」として入力値を表示するプログラム(main.rs)

use std::io;

fn main() {
    println!("数当てゲーム!");

    println!("数を入力してください");

    let mut guess = String::new();

    io::stdin().read_line(&mut guess)
        .expect("読み込みに失敗しました");

    println!("あなたの予測値: {}", guess);
}

図8 変数に値を2回代入するサンプルコード(src/main.rs)

fn main() {
    let v;

    v = 1;

    v = 2;
}

図10 mutを指定して値を変更できる変数と宣言したサンプルコード(src/main.rs)

fn main() {
    let mut v;

    v = 1;

    v = 2;
}

図12 read_line()関数の戻り値を表示させるコード

use std::io;

fn main() {
    let mut s = String::new();

    println!("戻り値: {}", 
        io::stdin().read_line(&mut s).expect("エラー"));
}

Vol.63 補足情報

投稿日:2019.11.25 | カテゴリー: コード

目次

p.3の連載「MySQL Shellを使おう」の記事タイトル「第3回 MySQL Shellのサーバー運用管理ユーティリティ」は、「第3回 MySQL X DevAPIとドキュメントストア(その1)」の誤りです。お詫びして訂正いたします。

読者プレゼント

p.9の「応募期間」にある「2019年11月25日~2019年1月20日」は「2019年11月25日~2020年1月20日」の誤りです。お詫びして訂正いたします。

MySQL Shellを使おう

p.72の記事タイトル「第3回 MySQL Shellのサーバー運用管理ユーティリティ」は、「第3回 MySQL X DevAPIとドキュメントストア(その1)」の誤りです。お詫びして訂正いたします。

情報は随時更新致します。

Vol.63

投稿日:2019.11.25 | カテゴリー: バックナンバー

 人気の小型コンピュータボードの最新版「Raspberry Pi 4 Model B」の国内販売開始が間近にせまっています。Raspberry Pi 4 Model Bは高機能、高性能で、ラズパイの用途が広がることは間違えありません。Raspberry Pi 4 Model Bを購入すると、古いラズパイが不要になるでしょう。そこで、特集1では、シェルスクリプトを用いて、不要になったラズパイを活用する方法を紹介します。メールの一斉配信や転送、エアコン制御を自動化できます。
 2020年に5G(第5世代移動通信方式)の本格サービスが開始します。この5Gによって、無線でつながるあらゆるモバイル環境が大幅に刷新されます。特集2では、5Gの概要や仕組み、5Gが実現する世界について解説します。
 特別企画では、米Amazon Web Services(AWS)社がオープンソースで公開した、データベース問い合わせ言語「SQL」互換の「PartiQL」を紹介します。PartiSQLは、RDB(リレーショナルデータベース)のみならず、JSON(JavaScript Object Notation)データやCSV(カンマ区切りテキスト)ファイルなどに対しても問い合わせが可能です。
 このほか、緊急レポートでは、小型PC-8001の「PasocomMini PC-8001 PCGセット」なども扱っています。
 今回も読み応え十分のシェルスクリプトマガジン Vol.63。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

シェルスクリプトマガジンvol.63 Web掲載記事まとめ

投稿日:2019.11.25 | カテゴリー: コード

004 CentOS 8とCentOS Stream公開
005 東京ゲームショウ2019開催
006 特別レポート ハル研究所から超小型PC-8001
008 NEWSFLASH
010 特集1 古いラズパイの活用術/麻生二郎 コード掲載
026 特集2 5Gで広がるモバイルの世界/酒井尚之、安藤高任
035 姐のNOGYO
036 特別企画 PartiQLをはじめよう/岡本秀高 コード掲載
043 ラズパイセンサーボードで学ぶ電子回路の制御/米田聡 コード掲載
046 円滑コミュニケーションが世界を救う!/濱口誠一
048 香川大学SLPからお届け!/清水赳 コード掲載
054 法林浩之のFIGHTING TALKS/法林浩之
056 バーティカルバーの極意/飯尾淳 コード掲載
062 漢のUNIX/後藤大地 コード掲載
070 virus/桑原滝弥・イケヤシロウ
072 MySQL Shellを使おう/梶山隆輔
079 中小企業手作りIT化奮戦記/菅雄一 コード掲載
084 Webアプリの正しい作り方/しょっさん コード掲載
098 ユニケージ新コードレビュー/岡田健 コード掲載
102 Techパズル/gori.sh
104 新しい風が吹いてくる/シェル魔人

特集1 古いラズパイの活用術(Vol.63記載)

投稿日:2019.11.25 | カテゴリー: コード

著者:麻生 二郎

小型コンピュータボードの最新機種「Raspberry Pi 4 Model B」が国内で発売できる状態になりました。Raspberry 4 Model Bは、高機能かつ高性能なハードウエアです。このラズパイが登場することでラズパイの適用範囲が広がりますが、同時に古いモデルが不要になります。いらなくなったラズパイを有効利用する三つの方法を紹介します。

シェルスクリプトマガジン Vol.63は以下のリンク先でご購入できます。

活用例1

図5 DMを一斉送信するシェルスクリプト(raspi_dm.sh)

#!/bin/sh

SOURCE_ADDRESS="自分のGmailアドレス"
DISTINATION_LIST="sendlist.txt"
USER_ID="Googleアカウントのユーザー名"
PASSWORD="Googleアカウントのパスワード"
MESSAGE_TEMPLATE_FILE="message.txt"

NUMBER_SEND=$(cat ${DISTINATION_LIST} | wc -l)
sed "s/%source_address%/${SOURCE_ADDRESS}/" ${MESSAGE_TEMPLATE_FILE} > /tmp/tmp_message.txt

for i in $(seq ${NUMBER_SEND})
do
  DISTINATION_ADDRESS=$(sed -n ${i}p ${DISTINATION_LIST} | cut -f 1)
  DISTINATION_NAME=$(sed -n ${i}p ${DISTINATION_LIST} | cut -f 2)
  sed "s/%name%/${DISTINATION_NAME}/g" /tmp/tmp_message.txt | sed -e "s/%distination_address%/${DISTINATION_ADDRESS}/" > /tmp/message.txt
  curl -s -k --url 'smtps://smtp.gmail.com:465' --mail-rcpt ${DISTINATION_ADDRESS} --mail-from ${SOURCE_ADDRESS} --user ${USER_ID}:${PASSWORD} --upload-file /tmp/message.txt
done

rm /tmp/tmp_message.txt /tmp/message.txt

図6 送信メッセージのテンプレートファイル(message.txt)

To: %distination_address%
From: %source_address%
Subject: 同窓会のご案内
Content-Type: text/plain; charset="UTF-8"

%name%

お元気ですか?
ご無沙汰しております。
早速ですが、下記の日程で同窓会を開催いたします。お手数をおかけしますが、出欠をメールにてご返信ください。%name%にお会いできるのを楽しみにしています。
                       記

日時:2019年11月30日(土曜日) 16時から
場所:〇〇〇高等学校 体育館
会費:5000円

                                       以上

図7 送信先のメールアドレスと、個別に書き換えたい情報を保存し
たタブ区切りテキストファイル(sendlist.txt)

taro@example.co.jp    シェルマグ太郎先生
hanako@example.com    シェルマグ花子さん
jiro@example.com    マガジン二郎君

活用例2

図11 受信メッセージをLINEに転送するシェルスクリプト (raspi_mail_line.sh)

#!/bin/sh
 
POP_SERVER="pop.gmail.com"
USER_ID="Googleアカウントのユーザー名"
PASSWORD="Googleアカウントのパスワード"
LINE_TOKEN="LINE Notifyのアクセストークン"

SUBJECT_BASE64="44CQ6YeN6KaB44CR"

expect -c "
  set timeout 30
  spawn openssl s_client -connect ${POP_SERVER}:995
  expect \"+OK Gpop ready\"
  send \"user ${USER_ID}\n\"
  expect \"+OK send PASS\"
  send \"pass ${PASSWORD}\n\"
  expect \"+OK Welcome.\"
  send \"stat\n\"
  expect \"+OK\"
  send \"quit\n\"
  expect \"+OK Farewell.\"
  exit 0
" > receive.log
 
RECEIVE_COUNT=$(grep +OK receive.log |tail -n2 |head -n 1 | cut -d " " -f 2)
 
for i in $(seq ${RECEIVE_COUNT})
do
  expect -c "
    set timeout 30
    spawn openssl s_client -connect ${POP_SERVER}:995
    expect \"+OK Gpop ready\"
    send \"user ${USER_ID}\n\"
    expect \"+OK send PASS\"
    send \"pass ${PASSWORD}\n\"
    expect \"+OK Welcome.\"
    send \"retr 1\n\"
    expect \".\"
    send \"quit\n\"
    expect \"+OK Farewell.\"
    exit 0
  " > message.log
 
  SUBJECT=$(cat message.log | grep "Subject: =" | sed "s/Subject: =?UTF-8?B?//g "| cut -c 1-16)
 
  if [ ${SUBJECT} = ${SUBJECT_BASE64} ] ; then
    cat message.log | awk '/Content-Language\: en-US/,/^\./' | head -n -1 | tail -n +2 > message.txt
    curl -s -X POST -H "Authorization: Bearer ${LINE_TOKEN}" -F "message=$(cat message.txt)" https://notify-api.line.me/api/notify
  fi
done

活用例3

図5 エアコン制御のシェルスクリプト(raspi_aircon)

#!/bin/sh

case $1 in

  "on"  ) bto_advanced_USBIR_cmd -d $(cat /var/aircon/start.txt);;
  "off" ) bto_advanced_USBIR_cmd -d $(cat /var/aircon/stop.txt);;

esac

特別企画 PartiQLをはじめよう(Vol.63記載)

投稿日:2019.11.25 | カテゴリー: コード

著者:岡本 秀高

米Amazon Web Services(AWS)社が2019年8月に発表した「PartiQL」は、RDBだけでなくKVSやJSONデータ、CSVデータに対しても問い合わせが可能な便利なクエリー言語です。文法はSQLのサブセットになっていて、SQLを知っている人であればすぐに使えます。PartiQL対応の実サービスも提供され始めた今、この新しいクエリー言語を始めてみましょう。

シェルスクリプトマガジン Vol.63は以下のリンク先でご購入できます。

図13 「sample.csv」ファイルの内容

3,Bob Smith
4,Susan Smith
6,Jane Smith

図15 項目名を追加したCSVファイル「with_header.csv」の内容

id,name
3,Bob Smith
4,Susan Smith
6,Jane Smith

図20 生成されたCSVファイル「out.csv」の内容

name,securityProjectsNum
Bob Smith,2
Susan Smith,0
Jane Smith,1

図22 生成されたAmazon Ion形式ファイル「out.ion」の内容

{
  name:"Bob Smith",
  securityProjectsNum:2
}
{
  name:"Susan Smith",
  securityProjectsNum:0
}
{
  name:"Jane Smith",
  securityProjectsNum:1
}

センサーボードで学ぶ電子回路の制御(Vol.63掲載)

投稿日:2019.11.25 | カテゴリー: コード

著者:米田 聡

シェルスクリプトマガジンでは、小型コンピュータボード「Raspberry Pi」(ラズパイ)向けのセンサー搭載拡張ボード「ラズパイセンサーボード」を制作しました。第10 回では、I2C 接続のモノクロ有機ELディスプレイをGroveコネクタにつなぎ、ラズパイセンサーボードだけでセンサーから情報をディスプレイ上に表示します。

シェルスクリプトマガジン Vol.63は以下のリンク先でご購入できます。

図6 有機ELディスプレイに日本語を表示するためのライブラリ(OLED.py)

import time
import Adafruit_SSD1306

from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont


class OLED(Adafruit_SSD1306.SSD1306_128_64):

	WIDTH = 128
	HEIGHT = 64
	
	# DEFAULT_FONT = '/usr/share/fonts/truetype/fonts-japanese-gothic.ttf'
	DEFAULT_FONT = '/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc'
	FONT_SIZE = 12
	_LINE_HEIGHT = 16
	
	def __init__(self):
		super().__init__(rst=24)
		self._image = Image.new('1', (self.WIDTH, self.HEIGHT) ,0)
		self._draw = ImageDraw.Draw(self._image)
		self._font = ImageFont.truetype(self.DEFAULT_FONT, self.FONT_SIZE, encoding='unic')
	
	def image(self, image):
		self._image = image
		super().image(self._image)
	
	def drawString(self, str, line=0):
		self._draw.rectangle((0, line*self._LINE_HEIGHT, self.WIDTH,line*self._LINE_HEIGHT+self._LINE_HEIGHT), fill=(0))
		self._draw.text((0, line*self._LINE_HEIGHT), str, font=self._font, fill=1)
		self.image(self._image)

図7 BME280のデータを表示するサンプルプログラム(sample.py)

#!/usr/bin/env python3
#
# apt install python3-pip
# sudo pip3 install RPi.BME280
#

import time
import smbus2
import bme280

from OLED import OLED

BME280_ADDR = 0x76
BUS_NO = 1

# BME280
i2c = smbus2.SMBus(BUS_NO)
bme280.load_calibration_params(i2c, BME280_ADDR)

# OLEDパネル
oled = OLED()
oled.begin()
oled.clear()
oled.display()

try:
	while True:
		data = bme280.sample(i2c, BME280_ADDR)
		oled.drawString('気温 :' + str(round(data.temperature,1)) + '℃', 0)
		oled.drawString('湿度 :' + str(round(data.humidity,1)) + '%', 1)
		oled.drawString('気圧 :' + str(round(data.pressure,1)) + 'hPa', 2)
		oled.display()
		
		time.sleep(1)
except KeyboardInterrupt:
	pass

香川大学SLPからお届け!(Vol.63掲載)

投稿日:2019.11.25 | カテゴリー: コード

著者:清水 赳

 こんにちは、香川大学修士1年の清水です。2年ぶりの登板です。
 今回から2回にわたり、OSSのシステム監視ツール「Prometheus」と「Itamae」というプロビジョニングツールを使って、サーバー監視システムを構築する方法を紹介します。今回は、Prometheusの配備とサーバー稼働状況の簡単な可視化について解説します。

シェルスクリプトマガジン Vol.63は以下のリンク先でご購入できます。

図5 「./cookbooks/prometheus/default.rb」ファイルに記述する内容

url_head = "https://github.com/prometheus/prometheus/releases/download"
url_ver  = "v2.13.1"
origin_dir = "Prometheus-2.13.1.linux-amd64"
install_dir = "/usr/local/bin"
config_dir = "/etc/prometheus"
## Prometheusをダウンロードする
execute "download prometheus" do
  cwd "/tmp"
  command "wget #{File.join(url_head, url_ver, origin_dir)}.tar.gz"
end
## Prometheusを展開する
execute "extract prometheus" do
  cwd "/tmp"
  command "tar xvfz #{origin_dir}.tar.gz"
end
## Prometheusを所定のディレクトリに配置
execute "install prometheus" do
  cwd "/tmp"
  command "mv #{File.join(origin_dir, "prometheus")} #{install_dir}"
end
## Systemd設定ファイルの転送
remote_file "/etc/systemd/system/prometheus.service" do
  owner "root"
  group "root"
  source "files/etc/systemd/system/prometheus.service"
end
## Prometheusの設定ファイルを配置する
remote_directory "/etc/prometheus" do
  owner "root"
  group "root"
  source "files/etc/prometheus"
end
## Prometheusサービスの開始
service "prometheus" do
  action :restart
end

図6 「./cookbooks/prometheus/etc/systemd/system/prometh
eus.service」ファイルに記述する内容

[Unit]
Description=Prometheus

[Service]
ExecStart=/usr/local/bin/prometheus --config.file /etc/prometheus/prometheus.yml \
    --storage.tsdb.path /var/lib/prometheus/ \
    --web.console.templates=/etc/prometheus/consoles/ \
    --web.console.libraries=/etc/prometheus/console_libraries/

[Install]
WantedBy=multi-user.target

図8 「./cookbooks/node_exporter/default.rb」ファイルに記述する内容

url_head = "https://github.com/prometheus/node_exporter/releases/download"
url_ver  = "v0.18.1"
origin_dir = "node_exporter-0.18.1.linux-amd64"
install_dir = "/usr/local/bin"
## node_exporterをダウンロードする
execute "download node_exporter" do
  cwd "/tmp"
  command "wget #{File.join(url_head, url_ver, origin_dir)}.tar.gz"
end
## node_exporterを展開する
execute "extract node_exporter" do
  cwd "/tmp"
  command "tar xvfz #{origin_dir}.tar.gz"
end
## node_exporterを所定のディレクトリに配置
execute "install node_exporter" do
  cwd "/tmp"
  command "mv #{File.join(origin_dir, "node_exporter")} #{install_dir}"
end
## Systemd設定ファイルの転送
remote_file "/etc/systemd/system/node_exporter.service" do
  owner "root"
  group "root"
  source "files/etc/systemd/system/node_exporter.service"
end
## node_exporterサービスの開始
service "node_exporter" do
  action :restart
end

図9 「./cookbooks/node_exporter/etc/systemd/system/node
_exporter.service」ファイルに記述する内容

[Unit]
Description=NodeExporter

[Service]
ExecStart=/usr/local/bin/node_exporter

[Install]
WantedBy=multi-user.target

図10 「./cookbooks/prometheus/files/etc/prometheus/prome
theus.yml」ファイルの編集内容

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
    - targets: ['localhost:9090']

# ここから下を追加
  - job_name: 'node'
    static_configs:
    - targets: ['192.168.100.12:9100']

バーティカルバーの極意(Vol.63掲載)

投稿日:2019.11.25 | カテゴリー: コード

著者:飯尾 淳

本連載は、「縦棒」(バーティカルバー)をキーワードにデータ処理や効果的なアルゴリズムについて考えるものです。ここ何回かバーティカルバーから離れて自由なテーマで論じ過ぎていたような気がするので、今回は初心に戻ってヒストグラムを用いたデータ分析の議論を紹介してみることにしましょう。
テーマは入力効率の問題です。普段、皆さんがお使いのキーボードによるタイピングとスマートフォンのフリック入力、はたしてどちらが効率的に入力できるのでしょうか?

シェルスクリプトマガジン Vol.63は以下のリンク先でご購入できます。

図3 hoge hogeタイピングにおけるデータ送信部のコード

function update_field(){
  if(count < 12 ){
    $("#answer2").html($("#answer1").val());
    typed = $("#answer1").val();
    endTime2 = new Date();
    totalTime2 = endTime2 - startTime2;
    startTime2 = new Date();
    flag = 0;

    $.ajax({
      type: "post",
      url: "./hoge.php",
      data: {
        info: info,
        flag: flag,
        uuid: uuid, seid: seid,
        num: totalTime2,
        test: question,
        typed: typed,
        userAgent: userAgent
      },
    });
  }
}

漢のUNIX(Vol.63掲載)

投稿日:2019.11.25 | カテゴリー: コード

著者:後藤 大地

Rustは、Mozilla Foundationが開発を支援している比較的新しいプログラミング言語だ。「マルチパラダイムシステムプログラミング言語」と呼ばれており、C/C++の代わりに利用できるプログラミング言語と言われている。C/C++のように見えるが、関数型プログラミングのパラダイムも織り込まれていて、かなり厳密なコーディングができるようになっている。本連載では、しばらく、このRustを取り上げていく。

シェルスクリプトマガジン Vol.63は以下のリンク先でご購入できます。

図20 Hello Worldのコード

fn main() {
    println!("Hello, world!");
}

中小企業手作りIT化奮戦記

投稿日:2019.11.25 | カテゴリー: コード

著者:菅 雄一

WordやExcel、PDFビューアなどで外字を含む文書を表示したり、外字を含む文章をメールで受信して閲覧したりすると、文字化けが起きる場合がある。今回は、私が長年、外字と向き合って格闘してきた話を書くことにする。

シェルスクリプトマガジン Vol.63は以下のリンク先でご購入できます。

図4 CSSファイルに追加する記述

@font-face {
  font-family: 'gaiji';
  src:url('./gaiji.woff') format('woff');
}
div.gaiji {
  font-family: 'gaiji';
}

図5 独自外字を表示するためのHTMLファイルの記述例

<!DOCTYPE html>
<html lang="ja">
<head>
<title>データ検索</title>
<meta charset="Shift_JIS">
<link rel="stylesheet" type="text/css" href="gaiji.css">
</head>
<body>
(略)
<div class="gaiji">
ここに独自外字を含む文字列が並ぶ
</div>
(略)
</body>
</html>

Webアプリケーションの正しい作り方(Vol.63記載)

投稿日:2019.11.25 | カテゴリー: コード

著者:しょっさん

ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならないでしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。第3回は、前回のプロジェクトの計画や方針に基づいて2回の開発サイクル(イテレーション)を回します。

シェルスクリプトマガジン Vol.63は以下のリンク先でご購入できます。

図4 TypeScriptに書き換えたメインのプログラム(index.ts)

import Express from 'express';
const app = Express();
import { Expense } from './models/expense';
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import session from 'express-session';

const users = {
  'user01': 'p@ssw0rd',
  'user02': 'ewiojfsad'
};

app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({
  secret: 'secret',
  resave: false,
  saveUninitialized: false,
  cookie: {
    maxAge: 24 * 30 * 60 * 1000
  }
}));

app.get('/login', (req: Express.Request, res: Express.Response): void => {
  res.send('<h1>LOGIN</h1><form action="/login" method="post">ユーザーID:<input type="text" name="user" size="40"><br />パスワード<input type="password" name="password"><input type="submit" value="ログイン">');
});

app.post('/login', (req: Express.Request, res: Express.Response): void => {
  if (eval("users." + req.body.user) === req.body.password) {
    if (req.session) {
      req.session.user = req.body.user;
    }
  }
  res.redirect('/');
});

app.post('/expense', (req: Express.Request, res: Express.Response): void => {
  Expense.create(req.body)
    .then(() => {
      res.redirect('/');
    });
});

app.get('/', (req: Express.Request, res: Express.Response): void => {
  const user = req!.session!.user || '名無しの権兵衛';
  res.writeHead(200, { "Content-Type": "text/html" });
  res.write(<h1>Hello ${user}</h1><table><tr><th>ID</th><th>申請者名</th><th>日付</th><th>経費タイプ</th><th>経費詳細</th><th>金額</th></tr>);
  Expense.findAll()
    .then(results => {
      for (let i in results) {
        res.write(<tr><td>${results[i].id}</td><td>${results[i].user_name}</td><td>${results[i].date}</td><td>${results[i].type}</td><td>${results[i].description}</td><td>${results[i].amount}</td></tr>);
      }
      res.write('</table><a href="/login">ログイン</a><a href="/submit">経費入力</a>');
      res.end();
    });
});

app.get('/submit', (req: Express.Request, res: Express.Response): void => {
  const user = req!.session!.user || '名無しの権兵衛';
  res.send(<h2>経費入力</h2><form action="/expense" method="post">申請者名:<input type="text" name="user_name" value="${user}"><br />日付:<input type="date" name="date"><br />経費タイプ:<input type="text" name="type"><br />経費詳細:<input type="text" name="description"><br />金額:<input type="number" name="amount"><br /><input type="submit" value="経費申請">);
});

const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(http://localhost:${port});
})

export default app;

図5 TypeScriptで作成した自作のモデルファイル(expense.ts)

import { Sequelize, Model, DataTypes } from 'sequelize';

// todo: データベース接続を定義する Typescript モジュール
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config.json')[env];
let sequelize;
if (config.use_env_variable) {
  const config_url: any = process.env[config.use_env_variable];
  sequelize = new Sequelize(config_url, config);
} else {
  sequelize = new Sequelize(config.database, config.username, config.password, config);
}

class Expense extends Model {
  public id!: number;
  public user_name!: string;
  public date!: Date;
  public type!: string;
  public description!: string | null;
  public amount!: number;
  public readonly careated_at!: Date;
  public readonly updated_at!: Date;
}

Expense.init({
  id: {
    type: DataTypes.INTEGER.UNSIGNED,
    autoIncrement: true,
    allowNull: false,
    primaryKey: true,
  },
  user_name: {
    type: DataTypes.STRING(256),
    allowNull: false,
    defaultValue: ''
  },
  date: {
    type: DataTypes.DATE,
    allowNull: false
  },
  type: {
    type: DataTypes.STRING(256),
    allowNull: false
  },
  description: {
    type: DataTypes.TEXT,
  },
  amount: {
    type: DataTypes.INTEGER.UNSIGNED,
    allowNull: false
  }
}, {
  tableName: 'expenses',
  underscored: true,
  sequelize: sequelize
});

export { Expense };

図6 トランスパイルオプションファイル(tsconfig.json)

{
  "compilerOptions": {
    "target": "es2018",
    "module": "commonjs",
    "lib": [
      "es2018"
    ],
    "sourceMap": true,
    "outDir": "./dist",
    "strict": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  },
  "include": [
    "./src/**/*.ts"
  ],
  "exclude": [
    "node_modules",
    "**/*.test.ts"
  ]
}

図7 gulpfile.jsファイルの内容

const { src, dest, parallel, series } = require('gulp');
const ts = require('gulp-typescript');
const tsconfig = require('./tsconfig.json');

// gulp固有の設定
const config = {
  output: 'dist/',
  json: {
    source: 'src/**/*.json'
  }
};

// typescript のトランスパイルオプション ← tsconfig.json を再利用する
const typescript = () => {
  return src(tsconfig.include)
    .pipe(ts(tsconfig.compilerOptions))
    .pipe(dest(config.output));
};

// json ファイルのアウトプットディレクトリへのコピーを司る指令
const json = () => {
  return src(config.json.source)
    .pipe(dest(config.output));
};

// 実行時オプション
exports.typescript = typescript;
exports.default = series(parallel(typescript, json));

図8 jest.config.jsの内容

module.exports = {
  coverageDirectory: "coverage",
  preset: 'ts-jest',
  testEnvironment: "node",
};

図9 index.test.tsの内容

test('1 adds 2 is equal 3', () => {
   expect(1 + 2).toBe(3);
 })

図11 super.test.tsファイルの内容

import app from '../src';
import request from 'supertest';

describe('Root', () => {
  it('Root index is valid', async () => {
    const response = await request(app).get("/");
    expect(response.status).toBe(200);
  });
});

describe('Login', () => {
  const userCredentials = {
    user: 'user01',
    password: 'p@ssw0rd'
  };
  it('login form is varid', async () => {
    const response = await request(app).get("/login");
    expect(response.status).toBe(200);
  });
  it('login with user credential is valid', async () => {
    const response = await request(app).post("/login")
      .send(userCredentials);
    expect(response.status).toBe(302);
  })
});

describe('submit', () => {
  it('A submit form is valid', async () => {
    const response = await request(app).get("/submit");
    expect(response.status).toBe(200);
  });
});

図13 .circleci/config.ymlファイルの内容

# Javascript Node CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
#
version: 2
jobs:
  build:
    docker:
      # specify the version you desire here
      - image: circleci/node:12.10.0

      # Specify service dependencies here if necessary
      # CircleCI maintains a library of pre-built images
      # documented at https://circleci.com/docs/2.0/circleci-images/
      # - image: circleci/mongo:3.4.4

    working_directory: ~/repo

    steps:
      - checkout

      # Download and cache dependencies
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "package.json" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-

      - run: npm install

      - save_cache:
          paths:
            - node_modules
          key: v1-dependencies-{{ checksum "package.json" }}

      # npm migrate
      - run: npm run migrate

      # run tests!
      - run: npm test

図14 書き換えたsuper.test.tsの内容

import app from '../src';
import request from 'supertest';

describe('Root', () => {
  it('Root index is valid', async () => {
    const response = await request(app).get("/");
    expect(response.status).toBe(200);
  });
});

describe('Login', () => {
  const userCredentials = {
    user: 'user01',
    password: 'p@ssw0rd'
  };
  it('login form is varid', async () => {
    const response = await request(app).get("/login");
    expect(response.status).toBe(200);
  });
  it('login with user credential is valid', async () => {
    const response = await request(app).post("/login")
      .send(userCredentials);
    expect(response.status).toBe(302);
  })
});

describe('submit', () => {
  it('A submit form is valid', async () => {
    const response = await request(app).get("/expenses/submit");
    expect(response.status).toBe(200);
  });
});

describe('payment', () => {
  it('A payment list is valid', async () => {
    const response = await request(app).get("/expenses/payment");
    expect(response.status).toBe(200);
  });
});

// エラーテスト
describe('/expense', () => {
  it('This URI is not valid', async () => {
    const response = await request(app).get("/expense");
    expect(response.status).toBe(404);
  });
});

図15 index.tsファイルの内容

import Express from 'express';
const router = Express.Router();

// GET / 最初に開く画面
router.get('/', (req: Express.Request, res: Express.Response) => {
  const user = req!.session!.user || '名無しの権兵衛';
  res.send(<h1>Hello ${user}</h1><a href="/login">ログイン</a><br /><a href="/expenses/submit">経費入力</a><br /><a href="/expenses/payment">支払い処理</a>);
})

export default router;

図16 login.tsファイルの内容

import Express from 'express';
const router = Express.Router();

// ユーザー&パスワード
const users = {
  'user01': 'p@ssw0rd',
  'user02': 'ewiojfsad'
};

// GET /login ユーザーログインフォーム
router.get('/', (req: Express.Request, res: Express.Response): void => {
  res.send('<h1>LOGIN</h1><form action="/login" method="post">ユーザーID:<input type="text" name="user" size="40"><br />パスワード<input type="password" name="password"><input type="submit" value="ログイン"><br /><a href="/login">ログイン</a><br /><a href="/expenses/submit">経費入力</a><br /><a href="/expenses/payment">支払い処理</a>');
});

// POST / ユーザーの認証処理
router.post('/', (req: Express.Request, res: Express.Response): void => {
  if (eval("users." + req.body.user) === req.body.password) {
    if (req.session) {
      req.session.user = req.body.user;
    }
  }
  res.redirect('/');
});

export default router;

図17 payment.tsファイルの内容

import Express from 'express';
const router = Express.Router();
import { Expense } from '../../models/expense';

// GET /expenses/payment もともと、最初に開かれる画面だった部分
router.get('/', (req: Express.Request, res: Express.Response): void => {
  const user = req!.session!.user || '名無しの権兵衛';
  res.writeHead(200, { "Content-Type": "text/html" });
  res.write(<h1>Hello ${user}</h1><table><tr><th>ID</th><th>申請者名</th><th>日付</th><th>経費タイプ</th><th>経費詳細</th><th>金額</th></tr>);
  Expense.findAll()
    .then(results => {
      for (let i in results) {
        res.write(<tr><td>${results[i].id}</td><td>${results[i].user_name}</td><td>${results[i].date}</td><td>${results[i].type}</td><td>${results[i].description}</td><td>${results[i].amount}</td></tr>);
      }
      res.write('</table><a href="/login">ログイン</a><br /><a href="/expenses/submit">経費入力</a><br /><a href="/expenses/payment">支払い処理</a>');
      res.end();
    });
});

export default router;

図18 submit.tsファイルの内容

import Express from 'express';
const router = Express.Router();
import { Expense } from '../../models/expense';

// GET /expenses/submit 入力フォーム
router.post('/', (req: Express.Request, res: Express.Response): void => {
  Expense.create(req.body)
    .then(() => {
      res.redirect('/');
    });
});

// POST /expenses/submit 経費の申請
router.get('/', (req: Express.Request, res: Express.Response): void => {
  const user = req!.session!.user || '名無しの権兵衛';
  res.send(<h2>経費入力</h2><form action="/expenses/submit" method="post">申請者名:<input type="text" name="user_name" value="${user}"><br />日付:<input type="date" name="date"><br />経費タイプ:<input type="text" name="type"><br />経費詳細:<input type="text" name="description"><br />金額:<input type="number" name="amount"><br /><input type="submit" value="経費申請"><br /><a href="/login">ログイン</a><br /><a href="/expenses/submit">経費入力</a><br /><a href="/expenses/payment">支払い処理</a>);
});

export default router;

図20 分割後のindex.tsファイルの内容

import Express from 'express';
const app = Express();
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import session from 'express-session';

app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({
  secret: 'secret',
  resave: false,
  saveUninitialized: false,
  cookie: {
    maxAge: 24 * 30 * 60 * 1000
  }
}));

// ルーティング
import index from './routes/index';
import login from './routes/login'; // ログイン機能
import payment from './routes/expenses/payment'; // 支払い機能
import submit from './routes/expenses/submit'; // 請求機能

app.use('/', index);
app.use('/login', login);
app.use('/expenses/payment', payment);
app.use('/expenses/submit', submit);

const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(http://localhost:${port});
})

export default app;

図21 package.jsonファイルの内容

{
  "name": "webapp",
  "version": "0.3.0",
  "description": "This application is sample code of Shell Script Magazine",
  "main": "dist/index.js",
  "scripts": {
    "dev": "ts-node ./src/index.ts",
    "test": "jest",
    "clean": "rimraf dist/*",
    "migrate": "sequelize db:migrate",
    "transpile": "gulp",
    "build": "npm run clean && npm run migrate && npm run transpile",
    "start": "node ."
  },
  "engines": {
    "node": "12.10.0"
  },
  "author": "しょっさん",
  "license": "ISC",
  "dependencies": {
    "cookie-parser": "^1.4.4",
    "express": "^4.16.4",
    "express-session": "^1.16.1",
    "gulp": "^4.0.2",
    "gulp-cli": "^2.2.0",
    "gulp-typescript": "^5.0.1",
    "pg": "^7.12.1",
    "rimraf": "^3.0.0",
    "sequelize": "^5.18.0",
    "sequelize-cli": "^5.5.1"
  },
  "devDependencies": {
    "@types/bluebird": "^3.5.27",
    "@types/cookie-parser": "^1.4.2",
    "@types/express": "^4.17.1",
    "@types/express-session": "^1.15.14",
    "@types/jest": "^24.0.18",
    "@types/node": "^12.7.3",
    "@types/pg": "^7.11.0",
    "@types/supertest": "^2.0.8",
    "@types/validator": "^10.11.3",
    "jest": "^24.9.0",
    "jest-junit": "^8.0.0",
    "sqlite3": "^4.1.0",
    "supertest": "^4.0.2",
    "ts-jest": "^24.0.2",
    "ts-loader": "^6.0.4",
    "ts-node": "^8.3.0",
    "typescript": "^3.6.2"
  }
}

ユニケージ新コードレビュー(Vol.63掲載)

投稿日:2019.11.25 | カテゴリー: コード

著者:岡田 健

ユニケージでは、小さな道具の「コマンド」をシェルスクリプトで組み合わせて、さまざまな業務システムを構築しています。本連載では、毎回あるテーマに従ってユニケージによるシェルスクリプトの記述例を分かりやすく紹介します。第10回は、グラフ可視化ソフト「Graphviz」を用いたグラフィカルなコード設計書の作成方法について解説します。

シェルスクリプトマガジン Vol.63は以下のリンク先でご購入できます。

図1 粗利を計算するシェルスクリプト(Source1.sh)

######################################
#| input:      [原価マスタ]    PRICE
#| output:     [原価マスタ修正] $tmp-price
#| outline:    原価マスタから必要な部分だけ抜き出す
######################################
cat PRICE |
self 1/3  > $tmp-price

######################################
#| input:      [部門マスタ]    CATEGORY
#| output:     [部門マスタ修正] $tmp-category
#| outline:    部門マスタから必要な部分だけ抜き出す
######################################
cat CATEGORY |
self 1/3     > $tmp-category

######################################
#| input:      [原価マスタ修正] $tmp-price
#| input:      [部門マスタ修正] $tmp-category
#| output:     [出力]        $tmp-out
#| outline:    粗利計算をする
######################################
cat SALES                       |
#  1:店舗 2:商品No 3:日付 4:売数
#  5:売上 6:割引
join1 key=2 $tmp-price          | # 原価 / 売価を連結
#  1:店舗 2:商品No 3:原価 4:売価
#  5:日付 6:売数   7:売上 8:割引
join1 key=2 $tmp-category       | # 部門を連結
#  1:店舗 2:商品No 3:部門 4:原価
#  5:売価 6:日付   7:売数 8:売上
#  9:割引
lcalc '$3,$7,$8,$8-$7*$4'       | # 売数 / 売上 / 荒利計算
#  1:部門 2:売数 3:売上 4:粗利
msort key=1                     | # 部門でソート
sm2 1 1 2 4                     | # 売数 / 売上 / 荒利集計
sm5 1 1 2 4                     | # 合計行の付加
divsen 2 3 4                    | # 千で除算
divsen 3 4                      | # 千で再除算
lcalc '$1,$2,$3,$4,100*$4/$3'   | # 荒利率を求める
#  1:部門   2:売数 3:売上 4:粗利
#  5:粗利率
marume 5.1                      | # 四捨五入
join2 key=1 CATEGORY_NAME       | # カテゴリ名の連結
#  1:部門 2:部門名 3:売数 4:売上
#  5:粗利 6:粗利率
comma 3 4 5                     | # カンマ編集
keta                            | # 桁そろえ
keisen +e                       | # 罫線を引く
cat header -                      # 出力

図3 DOT言語で書かれた中間コード(Source1.dot)

digraph sample {
  graph[ layout=dot ];
  node[fontname="IPAゴシック"];
  ID:1 原価マスタから必要な部分だけ抜き出す [ shape=box ];
  ID:2 部門マスタから必要な部分だけ抜き出す [ shape=box ];
  ID:3 粗利計算をする [ shape=box ];
  [原価マスタ] [shape = ellipse, peripheries = 2]
  [部門マスタ] [shape = ellipse, peripheries = 2]
  [出力] [ shape = ellipse, style = bold];
  [原価マスタ修正] [ shape = ellipse, style = bold];
  [部門マスタ修正] [ shape = ellipse, style = bold];
  [原価マスタ] -> ID:1 原価マスタから必要な部分だけ抜き出す [color = blue, style = bold, arrowsize = 1]
  ID:1 原価マスタから必要な部分だけ抜き出す -> [原価マスタ修正] [color = red, style = bold, arrowsize = 1]
  [部門マスタ] -> ID:2 部門マスタから必要な部分だけ抜き出す [color = blue, style = bold, arrowsize = 1]
  ID:2 部門マスタから必要な部分だけ抜き出す -> [部門マスタ修正] [color = red, style = bold, arrowsize = 1]
  [原価マスタ修正] -> ID:3 粗利計算をする [color = blue, style = bold, arrowsize = 1]
  [部門マスタ修正] -> ID:3 粗利計算をする [color = blue, style = bold, arrowsize = 1]
  ID:3 粗利計算をする -> [出力] [color = red, style = bold, arrowsize = 1]
}

第12回 コマンドを作る

投稿日:2019.11.19 | カテゴリー: 記事

 前回、「日本語の文字列をURLエンコーディング」できる適当なコマンドがなく、プログラミング言語「Python」でURLエンコーディングの処理を記述しました。シェルスクリプト内に別のプログラミング言語の記述があるのは、違和感があり、またコードも読みにくいでしょう。

 そこで、URLエンコーディングの処理をコマンド化し、シェルスクリプトと別の言語のプログラムを混在させないようにします(図1)。このように、よく使う処理をコマンド化することで、別のシェルスクリプトからも利用できます。

図1 よく使う処理をコマンド化

第11回 大切なメールだけをチャットルームに送る

投稿日:2019.10.22 | カテゴリー: 記事

 SNS(ソーシャルネットワークサービス)やチャットサービスが広がったおかげで、電子メールがあまり利用されなくなってきています。スマートフォンやインターネットサービスなどを利用するために電子メールのアドレスを持っていても、メールを使わない、メールを見ない人も多いでしょう。
 そこで、受信メッセージから重要な通知のみを取り出してSNSやチャットルームにメッセージとして転送するシェルスクリプトを作成します(図1)。

図1 重要な受信メッセージをSNSやチャットルームに転送するシェルスクリプト

機械学習のココロ(Vol.62掲載)

投稿日:2019.09.25 | カテゴリー: コード

著者:石井 一夫

最終回は、次世代データサイエンス言語として注目されている「Julia」を紹介します。高速な実行速度、並列分散処理の容易さ、数式記述の自然さなどを特徴とするJuliaは、今後急速に普及すると考えられます。

シェルスクリプトマガジン Vol.62は以下のリンク先でご購入できます。

図4 必要パッケージを読み込むコード

using Pkg
Pkg.add("Flux")
using Flux
using Statistics
using Flux: onehotbatch, onecold, crossentropy, throttle
using Base.Iterators: repeated

図5 MNIST データを読み込むコード

imgs = Flux.Data.MNIST.images()
labels = Flux.Data.MNIST.labels()

図7 訓練用データの前処理用コード

X = hcat(float.(reshape.(imgs, :))...)
Y = onehotbatch(labels, 0:9)

図8 モデルの構築用コード

m = Chain(
  Dense(28^2, 32, relu),
  Dense(32, 10),
  softmax)

図9 損失関数などの設定用コード

loss(x, y) = crossentropy(m(x), y)
opt = ADAM()
accuracy(x, y) = mean(onecold(m(x)) .== onecold(y))
dataset = repeated((X,Y),200)
evalcb = () -> @show(loss(X, Y))

図10 訓練データを用いた学習をするコード

Flux.train!(loss, params(m), dataset, opt, cb = throttle(evalcb, 10))

図13 テストデータの前処理用コード

test_X = hcat(float.(reshape.(Flux.Data.MNIST.images(:test), :))...)
test_Y = onehotbatch(Flux.Data.MNIST.labels(:test), 0:9)

図14 テスト画像の数字を推測するコード

onecold(m(test_X[:,5287])) - 1

特集1 PostgreSQL入門(Vol.62記載)

投稿日:2019.09.25 | カテゴリー: コード

著者:千田貴大

PostgreSQLは、今もなお成長を続けるオープンソースのリレーショナルデータベース管理システム(RDBMS)です。オーストリアのsolid IT社が運営するDBMSに関する情報サイト「DB-Engines」(https://db-engines.com/en/)では、2017年から2年連続で「the DBMS of the year」に選ばれており、勢いがあるRDBMSといえます。本特集では、PostgreSQLの導入方法や基本的な使い方、いくつかの機能について紹介します。

シェルスクリプトマガジン Vol.62は以下のリンク先でご購入できます。

図7 環境変数設定ファイル「/var/lib/pgsql/.bash_profile」の内容

[ -f /etc/profile ] && source /etc/profile
PGDATA=/var/lib/pgsql/11/data
export PGDATA
# If you want to customize your settings,
# Use the file below. This is not overridden
# by the RPMS.
[ -f /var/lib/pgsql/.pgsql_profile ] && source /var/lib/pgsql/.pgsql_profile

図13 「pg_hba.conf」ファイルの内容

# TYPE DATABASE USER ADDRESS METHOD
# "local" is for Unix domain socket connections only
local all all trust
# IPv4 local connections:
host all all 127.0.0.1/32 trust
# IPv6 local connections:
host all all ::1/128 trust

図16 アーカイブログ取得のための「postgresql.conf」ファイルの変更箇所

archive_mode = on
archive_command = 'cp "%p" "/var/lib/pgsql/11/archive/%f"'

図17 「recovery.conf」ファイルに記述する必要がある設定

restore_command = 'cp "/var/lib/pgsql/11/archive/%f" "%p"'

シェルスクリプトマガジンvol.62 Web掲載記事まとめ

投稿日:2019.09.25 | カテゴリー: コード

004 レポート exFATのLinuxカーネル実装
005 NEWS FLASH
008 特集1 PostgreSQL入門/千田貴大 コード掲載
024 特集2 Jetson Nanoを使ってみよう/橘幸彦
034 姐のNOGYO
035 特別企画 はじめてのLinux/長原宏治
050 ラズパイセンサーボードで学ぶ 電子回路の制御/米田聡 コード掲載
054 漢のUNIX/後藤大地
062 バーティカルバーの極意/飯尾淳 コード掲載
066 法林浩之のFIGHTING TALKS/法林浩之
068 機械学習のココロ/石井一夫 コード掲載
073 MySQL Shellを使おう/梶山隆輔
078 円滑コミュニケーションが世界を救う!/濱口誠一
080 香川大学SLPからお届け!/宇野光純 コード掲載
088 めし/桑原滝弥・イケヤシロウ
090 中小企業手作りIT化奮戦記/菅雄一
096 ユニケージ新コードレビュー/坂東勝也 コード掲載
102 Techパズル/gori.sh
104 コラム「平凡で地味な人生を幸せに送る」/シェル魔人

ユニケージ新コードレビュー(Vol.62掲載)

投稿日:2019.09.25 | カテゴリー: コード

著者:坂東 勝也

ユニケージでは、小さな道具の「コマンド」をシェルスクリプトで組み合わせて、さまざまな業務システムを構築しています。本連載では、毎回あるテーマに従ってユニケージによるシェルスクリプトの記述例を分かりやすく紹介します。第9回は、前回の続きとしてデータの扱い方について解説します。

シェルスクリプトマガジン Vol.62は以下のリンク先でご購入できます。

図2 LV3データ作成処理のコード(一部抜粋)

#######################################
# LV3 の作成

echo $lv2d/$today/PROFILE*   |  # /home/hoge/DATA/LV2/20190919/PROFILE*
tarr                         |
ugrep -v "\*"                > $tmp-list
ERROR_CHECK

# LV2 ファイルが存在した場合だけ処理
if [ -s "$tmp-list" ] ; then

        # LV2 ファイル
        # 1:管理番号        2:名前     3:ふりがな  4:アドレス    5:性別
        # 6:年齢            7:生年月日 8:婚姻      9:血液型      10:都道府県
        # 11:都道府県コード 12:携帯    13:キャリア 14:削除フラグ 15:オペレータ
        # 16:更新日付
        cat $tmp-list        |
        xargs cat            |
        msort key=1@NF       > $tmp-kousin
        ERROR_CHECK
)
        # 新規マスタの作成
        # 1:管理番号        2:名前     3:ふりがな  4:アドレス    5:性別
        # 6:年齢            7:生年月日 8:婚姻      9:血液型      10:都道府県
        # 11:都道府県コード 12:携帯    13:キャリア 14:削除フラグ 15:オペレータ
        # 16:更新日付
        up3 key=1@NF $lv3d/$yday/PROFILE $tmp-kousin | #前日マスタに本日LV2の情報をマージ
        # 最新の情報だけを残す
        getlast key=1                                | #管理番号ごとに最も新しいものだけを出力
        # 削除フラグ=1は除外
        delr 14 1                                    > $lv3d/$today/PROFILE
        ERROR_CHECK

        # マスタの置換
        cp $lv3d/$today/PROFILE $lv3d/PROFILE.new
        ERROR_CHECK
        mv $lv3d/PROFILE.new $lv3d/PROFILE
        ERROR_CHECK

fi

Vol.62 補足情報

投稿日:2019.09.25 | カテゴリー: コード

訂正・補足情報はありません
情報は随時更新致します。

Vol.62

投稿日:2019.09.25 | カテゴリー: バックナンバー

 オープンソースでは人気が高いデータベース管理システム「PostgreSQL」。特集1では、このPostgreSQLの概要、WindowsやCentOSへのインストール方法、初期設定と基本操作、知っておきたい機能を解説しました。PsotgreSQLを初めて利用する人にも分かりやすい内容になっています。
 特集2では、本格的なAI(人工知能)を安価に試せるGPU「Jetson Nano」を扱いました。米NVIDIA社の無料オンライントレーニングを基にして、開発者キットの立ち上げ方からAIによる画像分類までを日本語で分かりやすく紹介しています。
 特別企画では、無料の基本ソフト(OS)であるLinuxについて、やさしく解説しました。この企画で「Linuxとは何か」「どう使うのか」を基本からしっかり理解してください。
 このほか、ラズパイセンサーボードに接続した4桁の7セグLEDの制御、Pythonの制御構文、次世代データサイエンス言語「Julia」、MySQL Shellの運用管理ユーティリティなどを連載で紹介しています。
 今回も読み応え十分のシェルスクリプトマガジン Vol.62。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

センサーボードで学ぶ電子回路の制御(Vol.62掲載)

投稿日:2019.09.25 | カテゴリー: コード

著者:米田 聡

シェルスクリプトマガジンでは、小型コンピュータボード「Raspberry Pi」(ラズパイ)向けのセンサー搭載拡張ボード「ラズパイセンサーボード」を制作しました。第9 回では、I/Oエキスパンダに7セグメントLEDを四つ接続してセンターからの値を表示します。

シェルスクリプトマガジン Vol.62は以下のリンク先でご購入できます。

図5 LEDを表示するライブラリ(led4digits.py)

from mcpgpio import MCPGPIO
import threading
import time

class LED4DIGITS(threading.Thread):

  # 点灯させる桁のコード
  __dig = [
    [0b00000000, 0b00100000], #13
    [0b00010000, 0b00000000], #4
    [0b00001000, 0b00000000], #3
    [0b00000100, 0b00000000]  #2
  ]

  # ドットのコード
  __dot = [0b00000000, 0b00000100] # 10

  # 数値のコード
  __leds = [ [0b11100000, 0b00001011 ],  # [7, 5, 11, 9, 8, 6] 
           [0b00100000, 0b00001000 ],  # [5, 11] 
           [0b10100000, 0b00010011 ],  # [7, 5, 12, 8, 9]
           [0b10100000, 0b00011010 ],  # [7, 5, 12, 11, 9]
           [0b01100000, 0b00011000 ],  # [6, 12, 5, 11]
           [0b11000000, 0b00011010 ],  # [7, 6, 12, 11, 9]
           [0b11000000, 0b00011011 ],  # [7, 6, 12, 11, 9, 8]
           [0b10100000, 0b00001000 ],  # [7, 5, 11]
           [0b11100000, 0b00011011 ],  # [7, 5, 11, 9, 8, 6, 12]
           [0b11100000, 0b00011010 ]   # [7, 5, 11, 9, 6, 12]
    ]

  __d = 0                 # 現在の桁
  value = 0               # 表示する値
  __term = False          # 停止フラグ
  __p = -1                # ドットの位置

  def __init__(self):
    threading.Thread.__init__(self)

    self.gpio = MCPGPIO()

    for i in range(16):
      self.gpio.setup(i, self.gpio.OUTPUT)
      self.gpio.output(i, self.gpio.LOW)

  def print(self, v):
    if (v > 9999) or (v < 0):
      return

    self.__p = -1
    self.value = 0
    if isinstance(v, int):
      self.value = v

    elif isinstance(v, float):
      s = '{:.4g}'.format(v)
      if float(s) < 10:
        self.value = int(float(s) * 1000)
        self.__p = 3
      elif float(s) < 100:
        self.value = int(float(s) * 100)
        self.__p = 2
      elif float(s) < 1000:
        self.value = int(float(s) * 10)
        self.__p = 1
      else:
        self.value = int(s)

    else:
      return
    
  def stop(self):
    self.__term = True

  def run(self):
    while not self.__term:
      d = self.__d & 0b11
      co = 10 ** d
      n = int(self.value / co)
      p = int(n / 10)
      n %= 10
      # clear
      self.gpio.gpioa = 0
      self.gpio.gpiob = 0
      if (n != 0) or (d == 0) or (p > 0) or (self.__p == 3):
        # put
        a = self.__leds[n][0] | self.__dig[d][0]
        b = self.__leds[n][1] | self.__dig[d][1]
        if self.__p == d:
          a |= self.__dot[0]
          b |= self.__dot[1]
        self.gpio.gpioa = a
        self.gpio.gpiob = b

      self.__d += 1
      time.sleep(0.002)

図6 BME280からの温度を取得・表示するプログラム(sample.py)

import time
import smbus2
import bme280
from led4digits import LED4DIGITS

BME280_ADDR = 0x76
BUS_NO = 1

# BME280
i2c = smbus2.SMBus(BUS_NO)
bme280.load_calibration_params(i2c, BME280_ADDR)

# LED Start
led = LED4DIGITS()
led.start() # 点灯開始

try:
    while True:
      data = bme280.sample(i2c, BME280_ADDR)
      led.print(data.temperature)
      time.sleep(1)
except KeyboardInterrupt:
    led.stop()

香川大学SLPからお届け!(Vol.62掲載)

投稿日:2019.09.25 | カテゴリー: コード

筆者:宇野 光純

 前回に引き続き、Windowsアプリケーションとして動く簡単な2Dゲームの開発を紹介します。汎用プログラミング言語の「C++」と、オープンソースのパソコンゲーム開発用ライブラリの「DXライブラリ」を組み合わせることで、時間と労力は必要ですが、Unityなどのゲームエンジンよりも自由度の高いゲーム開発ができます。

シェルスクリプトマガジン Vol.62は以下のリンク先でご購入できます。

図2 「Shot.h」ファイルに記述するコード

#pragma once
#include "Object.h"

class Shot : public Object {
private:
  bool flag;
  bool image;
  // true なら敵機の弾、false なら自機の弾
  static int image1; // 画像ハンドル1
  static int image2; // 画像ハンドル2
  void SetImage(); // 画像関連設定用の関数

public:
  double xv, yv; // X、Y 方向の移動量
  Shot();
  void Update(); // 更新
  void Draw(); // 描画
  // 座標、速度、画像を指定し発射する
  void Shoot(double nx, double ny,
             double nxv, double nvy, bool fimg);
};

図3 「Shot.cpp」ファイルに記述するコード

#include "DxLib.h"
#include "Shot.h"
#include "Info.h"
int Shot::image1 = -1;
int Shot::image2 = -1;

Shot::Shot() {
  x = y = 0.0; xv = yv = 0.0; flag = true; image = false;
  SetImage();
}
void Shot::Update() {
  if (flag) {
    x += xv; y += yv;
    // 画面外に出た場合、無効にする
    if (x < 0 || GetWidth() < x || y < 0 || GetHeight() < y) flag = false;
  }
}
void Shot::Draw() {
  if (flag) {
    if (image) DrawGraph((int)(x - size / 2), (int)(y - size / 2), image1, TRUE);
    else DrawGraph((int)(x - size / 2), (int)(y - size / 2), image2, TRUE);
  }
}
void Shot::SetImage() {
  size = 16;
  if (image1 == -1) image1 = LoadGraph("./images/shot1.png");
  if (image2 == -1) image2 = LoadGraph("./images/shot2.png");
}
// 座標、速度、画像を指定し発射する
void Shot::Shoot(double nx, double ny, double nxv, double nyv, bool fimg) {
  x = nx; y = ny; xv = nxv; yv = nyv;
  image = fimg; flag = true;
}

図4 「Object.h」ファイルに追加するコード

class Object {
public:
  bool flag; // 有効無効を示すフラグ
};

図5 「Player.h」ファイルに追加するコード

#include "Shot.h"
class Player : public Object {
private:
  int shot_num; // 現在の弾配列の添字
  int shot_span; // 弾の発射間隔
  void SetShot(); // 弾関連の設定用関数
  void ShotFire(); // 弾発射用の関数
public:
  static const int shot_max = 20; // 弾配列の要素数
  Shot shot[shot_max]; // 弾配列
};

図6 「Player.cpp」ファイルに追加するコード

Player::Player() {
  flag = true; // 有効フラグを設定立てる
  this->SetShot(); // 自機の弾関連の設定
}
void Player::Update() {
  this->ShotFire(); // 自機の弾の発射
  for (int i = 0; i < shot_max; i++) shot[i].Update();
}
void Player::Draw() {
  for (int i = 0; i < shot_max; i++) shot[i].Draw();
}
void Player::SetShot() {
  shot_num = 0; shot_span = 0;
  for (int i = 0; i < shot_max; i++) shot[i] = Shot();
}
void Player::ShotFire() {
  if (GetKey(KEY_INPUT_Z)) {
    // 発射間隔shot_span が4 以上になったとき
    if (shot_span++ >= 4) {
      // 自機位置から弾を発射する
      shot[shot_num++].Shoot(x, y, 0, -8, false);
      // 配列の添字が要素数以上になったときは0 にする
      if (shot_num >= shot_max) { shot_num = 0; }
      // 発射間隔のリセット
      shot_span = 0;
    }
  }
}

図7 「MainScene.h」ファイルに記述するコード

#include "Shot.h"
#include <vector>

class MainScene {
private:
  int enemy_span; // 弾の発射間隔
  double enemy_shot_base; // 発射角度
  std::vector<Shot> enemy_shot; // 敵機の弾配列

public:
  void StageInitialize(); // パラメータ初期化用関数
  void StageUpdate(); // 敵機の弾発射を実装する関数
};

図8 「MainScene.cpp」ファイルに追加するコード

#define _USE_MATH_DEFINES
#include <math.h>

MainScene::MainScene() {
  StageInitialize(); // 追加したパラメータの初期化
}
void MainScene::Update() {
  for (auto itr = enemy_shot.begin(); itr != enemy_shot.end();) {
    if (!(*itr).flag) itr = enemy_shot.erase(itr);
    else { (*itr).Update(); itr++; }
  }
  StageUpdate(); // ステージの更新
}
void MainScene::Draw() {
  for (auto itr = enemy_shot.begin(); itr != enemy_shot.end(); ++itr)
    (*itr).Draw(); // 敵機の弾の描画
}
void MainScene::StageInitialize() {
  enemy_span = 0; enemy_shot_base = 0;
}
void MainScene::StageUpdate() {
  if (enemy_span++ >= 50) {
    double shot_v = 2.0; int shot_num = 36;
    for (int i = 0; i < shot_num; i++) {
      double angle = enemy_shot_base + M_PI / 18 * i; // 発射角度
      enemy_shot.push_back(Shot()); // インスタンスを末尾に追加
      enemy_shot.back().Shoot(enemy.x, enemy.y, shot_v * cos(angle),
                              shot_v * sin(angle), true); // 発射
    }
    enemy_shot_base += 0.1; // 基準の角度を更新
    enemy_span = 0; // 発射間隔を初期化
  }
}

図10 「Info.h」ファイルに追加するコード

#include "Object.h"

// 2 オブジェクトの当たり判定用関数
void Collision(Object *obj1, Object *obj2);

図11 「Info.cpp」ファイルに追加するコード

#include <math.h>
void Collision(Object *obj1, Object *obj2) {
  double dx = obj1->x - obj2->x; // X 座標の差
  double dy = obj1->y - obj2->y; // Y 座標の差
  double ds = obj1->hit_size + obj2->hit_size; // 半径の合計
  // 有効フラグが立っているかどうかの確認
  if (!obj1->flag || !obj2->flag) return;
  // 三平方の定理を使用
  if (pow(dx, 2) + pow(dy, 2) <= pow(ds, 2)) {
    // 当たり判定後の処理
    obj1->CollisionResult();
    obj2->CollisionResult();
  }
}

図12 「Object.h」ファイルに追加するコード

class Object {
public:
  int hit_size; // 当たり判定エリアの半径
  // 当たり判定後の処理用関数
  virtual void CollisionResult() {}
};

図13 「Player.h」ファイルに追加するコード

class Player : public Object {
public:
  int hp_now, hp_max; // 体力の現在値、最大値
  void CollisionResult(); // 当たり判定後の処理用関数
};

図14 「Player.cpp」ファイルに追加するコード

Player::Player() {
  hp_now = hp_max = 3; // 体力の初期化
}
void Player::SetImage() {
  hit_size = 8;
}
void Player::CollisionResult() {
  if (hp_now-- < 0) flag = false;
}

図15 「Enemy.h」ファイルに追加するコード

class Enemy : public Object {
public:
  int hp_now, hp_max;
  void CollisionResult();
};

図16 「Enemy.cpp」ファイルに追加するコード

Enemy::Enemy() {
  hp_now = hp_max = 100; // 体力
  flag = true; // 有効フラグを立てる
}
void Enemy::SetImage() {
  hit_size = 32;
}
void Enemy::CollisionResult() {
  if (hp_now-- < 0) flag = false;
}

図17 「Shot.h」ファイルに追加するコード

class Shot : public Object {
public:
  // 当たり判定後の処理用関数
  void CollisionResult();
};

図18 「Shot.cpp」ファイルに追加するコード

void Shot::SetImage() {
  hit_size = 8;
}
void Shot::CollisionResult() { flag = false; }

図19 「MainScene.cpp」ファイルに追加するコード

void MainScene::Update() {
  // 自機の弾と敵機の当たり判定処理
  for (int i = 0; i < player.shot_max; i++)
    Collision(static_cast<Object*>(&player.shot[i]), static_cast<Object*>(&enemy));
  // 自機と敵機の弾の当たり判定処理
  for (auto itr = enemy_shot.begin(); itr != enemy_shot.end(); itr++)
    Collision(static_cast<Object*>(&player), static_cast<Object*>(&(*itr)));
}
void MainScene::Draw() {
  DrawFormatString(0, 0, GetColor(255, 255, 255), "Player : %d", player.hp_now);
  DrawFormatString(GetWidth() - 120, 0, GetColor(255, 255, 255), "Enemy : %d", enemy.hp_now);
}

図20 「Info.h」ファイルに追加する コード

// ゲームシーンの取得用関数
int GetGameScene();
// ゲームシーンの設定用関数
void SetGameScene(int val);

図21 「Info.cpp」ファイルに追加するコード

int g_GameScene; // シーン管理用変数
int GetGameScene() { return g_GameScene; }
void SetGameScene(int val) { g_GameScene = val; }

図22 「TitleScene.h」ファイルに 記述するコード

#pragma once
class TitleScene {
private:
  int wait; // 待ち時間
public:
  TitleScene();
  bool Update();
  void Draw();
};

図23 「TitleScene.cpp」ファイルに記述するコード

#include "DxLib.h"
#include "TitleScene.h"
#include "Info.h"

TitleScene::TitleScene() {
  SetBackgroundColor(100, 100, 100); // 背景を灰色に
  wait = 30;
}
bool TitleScene::Update() {
  // スペースキーを押したら、true を返す
  if (wait-- < 0 && GetKey(KEY_INPUT_SPACE)) return true;
  return false;
}
void TitleScene::Draw() {
  DrawFormatString(200, 200, GetColor(255, 255, 255), " タイトルです.");
  DrawFormatString(200, 400, GetColor(255, 255, 255), " スペースキーを押してください.");
}

図25 「ResultScene.h」ファイルに記述するコード

#pragma once
class ResultScene {
public:
  bool Update();
  void Draw();
};

図26 「ResultScene.cpp」ファイルに記述するコード

#include "DxLib.h"
#include "ResultScene.h"
#include "Info.h"

bool ResultScene::Update() {
  if (GetKey(KEY_INPUT_SPACE)) return true; // メインシーンに遷移
  return false;
}
void ResultScene::Draw() {
  // ゲームクリア時
  if (GetGameScene() == 2) {
    SetBackgroundColor(255, 255, 255); // 背景を白色に
    DrawFormatString(300, 200, GetColor(0, 0, 0), " ゲームクリア !!");
  }
  // ゲームオーバー時
  else {
    SetBackgroundColor(255, 100, 100); // 背景を赤色に
    DrawFormatString(300, 200, GetColor(0, 0, 0), " ゲームオーバー...");
  }
  DrawFormatString(300, 360, GetColor(0, 0, 0), " スペースキーを押すと");
  DrawFormatString(300, 380, GetColor(0, 0, 0), " タイトルに戻ります.");
  DrawFormatString(300, 420, GetColor(0, 0, 0), " 終了には、ESC キー.");
}

図29 「Main.cpp」ファイルに追加するコード

#include "TitleScene.h"
#include "ResultScene.h"

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
         LPSTR lpCmdLine, int nCmdShow)
{
  TitleScene ts = TitleScene(); 
  ResultScene rs = ResultScene();
  while (ProcessMessage() == 0 && ScreenFlip() == 0 &&
      ClearDrawScreen() == 0) {
    // ms.Update(); ms.Draw(); は消してその部分に以下を追加
    int t = 0;
    switch (GetGameScene()) {
      case 0:
        if (ts.Update()) { ms = MainScene(); SetGameScene(1); break; }
        ts.Draw();
        break;
      case 1:
        if((t = ms.Update()) != 0) { SetGameScene(t); break; }
        ms.Draw();
        break;
      default:
        if (rs.Update()) { ts = TitleScene(); SetGameScene(0); break; }
        rs.Draw();
        break;
    }
  }
}

図30 「MainScene.h」ファイルに追加 するコード

class MainScene {
public:
  // void Update(); 行は削除
  int Update();
};

図31 「MainScene.cpp」ファイルに追加するコード

// void MainScene:Update() { 行を次の行に置き換えてから太字部分をブロック末尾に追加
int MainScene::Update() {
  if (player.hp_now <= 0) { return 3; }
  if (enemy.hp_now <= 0) { return 2; }
  return 0;
}
void MainScene::StageUpdate() {
  if (enemy_span++ >= 50) {
    // 体力が半分より大きいとき
    if (enemy.hp_now > enemy.hp_max / 2) {
        // このブロックに既存のコードを挿入
    }
    // 体力が半分以下のとき、ランダムな角度に多数の弾を発射
    else {
      double shot_v = 1.0 + GetRand(40) / 10.0;
      int shot_num = 2;
      for (int i = 0; i < shot_num; i++) {
        double angle = M_PI * (GetRand(3600) / 10.0) / 180;
        enemy_shot.push_back(Shot());
        enemy_shot.back().Shoot(
          enemy.x, enemy.y, shot_v * cos(angle), shot_v * sin(angle), true
        );
      }
    }
  }
}

バーティカルバーの極意(Vol.62掲載)

投稿日:2019.09.25 | カテゴリー: コード

著者:飯尾 淳

 しばらくデータ分析の話題から遠ざかっているような気がしますが、 ちょっとしたトレーニングをするつもりで前回、前々回と同様にプログラミングの話を続けましょう。今回は、状態遷移図に基づくシステムの動作原理を考えます。最初に簡単なケースを考え、その後で少しブラッシュアップして仕様をアップデートします。
 ところで、状態遷移図は「GraphViz1」というツールで描画します。この連載のテーマである「バーティカルバー」(垂直棒または縦棒)を用いた表現も指定できますが、今回は縦横にこだわらず柔軟に表現してみましょう。

シェルスクリプトマガジン Vol.62は以下のリンク先でご購入できます。

図2 メッセージ出力プログラム

#!/usr/bin/env python

import random

class Node:
    def __init__(self, label):
        self.label = label
        self.nodes = []

    def addNode(self, node):
        self.nodes.append(node)

    def nextNode(self):
        return random.choice(self.nodes)

    def putLabel(self):
        print(self.label, end="")
        return self

    def main():
        cur = n1 = Node(" ガ")
        n2 = Node(" ン")
        n3 = Node(" ズ")
        n4 = Node(" ダ")
        e = Node("")
        n1.addNode(n2); n1.addNode(n2)
        n2.addNode(n1); n2.addNode(n3)
        n2.addNode(n4); n2.addNode(e)
        n3.addNode(n4)
        n4.addNode(n2); n4.addNode(n2)

        while(cur != e):
            cur = cur.putLabel().nextNode()
        print()

    if __name__ == "__main__":
        main()

図4 メッセージ出力プログラムの改良版(gangan2.py)

#!/usr/bin/env python

import random

class Node:
    def __init__(self, label):
        self.label = label
        self.nodes = []

    def addNode(self, node):
        self.nodes.append(node)

    def nextNode(self):
        n = random.choice(self.nodes)
        self.nodes.remove(n)
        return n

    def putLabel(self):
        print(self.label, end="")
        return len(self.nodes)

def main():
    cur = n1 = Node("ガ")
    n2 = Node("ン")
    n3 = Node("ズ")
    n4 = Node("ダ")
    n1.addNode(n2); n1.addNode(n2)
    n2.addNode(n1); n2.addNode(n3) 
    n2.addNode(n4)
    n3.addNode(n4)
    n4.addNode(n2); n4.addNode(n2)

    while (cur.putLabel() > 0):
        cur = cur.nextNode()
    print()

if __name__ == "__main__":
    main()

第10回 写真から場所を調べる

投稿日:2019.08.20 | カテゴリー: 記事

 スマートフォンで撮影した写真が増えてくると、いつどこで撮影したものなのかが分からなくなることもあるでしょう。ただし、心配は無用です。スマートフォンの多くは「GPS」(全地球測位システム)の半導体(チップ)を内蔵しています。この半導体から取得した位置(緯度・経度)および、撮影日などの情報を、写真データと一緒に保存しています。その情報を調べれば、いつどこで撮影されたものなのかがすぐに分かります。

 そこで今回は、写真データから撮影の日時や場所の情報を取り出して表示するシェルスクリプトを作成します(図1)。

図1 GPSから取得した位置情報を取り出して表示

仕様やロジックを考える

 それでは、仕様やロジックを考えていきましょう。写真データのファイルには「Exif」(Exchangeable image file format)という形式のメタデータ(データに付属するデータ)で、写真データのサイズ、撮影の日時や場所、撮影時のカメラの設定などの情報が保存されています。このExifデータは、端末などから画像データを操作できるソフトウエア「ImageMagick」の「identify」コマンドで取り出せます。

 例えば、図2の写真(IMG_0001.JPG)の撮影場所が分からないとします。

図2 iPhoneで撮影したどこかの池の写真(IMG_0001.JPG)

第9回 グローバルIPアドレスを通知する

投稿日:2019.07.30 | カテゴリー: 記事

 自宅のネットワークにリモートからアクセスしたい人もいるでしょう。その場合、自宅ネットワークの入り口に配置しているルーターに割り振られた「グローバルIPアドレス」を知らなくてはいけません。

 グローバルIPアドレスは、自宅内から次のコマンドを実行すれば確認できます。

$ curl ifconfig.io

 表示されたIPアドレスでアクセスすればよいのですが、常に同じIPアドレスとは限りません。通常、このIPアドレスは動的に変更されます。

 「ダイナミックDNS」というサービスを利用すれば、動的にIPアドレスが変わっても自宅ネットワークへ「ドメイン名」(ドメイン付きホスト名)でアクセスできます。ダイナミックDNSに関しては、Web連載「UbuntuではじめるLinuxサーバー」の「第10回 インターネットに公開する」を参照してください。ダイナミックDNSを使えば解決しますが、作業が面倒です。また、サーバーをインターネットに公開しているわけではないので特定のドメイン名を付ける必要はありません。

 そこで、ダイナミックDNSを使わずに自宅内からグローバルIPアドレスを監視して更新されたら通知するシェルスクリプトを作成します(図1)。

図1 グローバルIPアドレスを通知する

Vol.61

投稿日:2019.07.25 | カテゴリー: バックナンバー

 約5年ぶりに企業向け有償Linuxディストリビューションのメジャーアップデート版「Red Hat Enterprise Linux 8」(RHEL8)が2019年5月リリースされました。RHELは、サーバーOSとして小規模なシステムから基幹系のような大規模なシステムまで、幅広く利用されています。
 特集1では、このRHEL8に関する概要や特徴、旧版利用者への注意点、情報入手方法をまとめました。開発元のレッドハットの技術者が自ら執筆していますので、RHELユーザーなら必ず読むべき特集記事です。
 2020年にプログラミング教育が小学校で必修となります。「何をすべきか分からない」と感じている人も多いようです。そのような人にお薦めなのが、教育向けシングルボードコンピュータ「BBC micro:bit」です。
 特集2では、このBBC micro:bitを利用したプログラミングとサンプルを紹介しています。ブロックを組み合わせるだけでプログラムが書けるので、プログラムが苦手な人、初めてのプログラミングする人にピッタリです。
 このほか、特別企画では700円くらいで買えるGPSモジュールの電子基板をパソコンから制御します。また、データベース管理システム「MySQL」の新クライアント「MySQL Shell」の入門連載を開始しました。
 今回も読み応え十分のシェルスクリプトマガジン Vol.61。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

Vol.61 補足情報

投稿日:2019.07.25 | カテゴリー: コード

漢のUNIX

p.92の1行目にある「透過」は「等価」の誤りです。お詫びして訂正いたします。

情報は随時更新致します。

Webアプリケーションの正しい作り方(Vol.61記載)

投稿日:2019.07.25 | カテゴリー: コード

著者:しょっさん

ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならない でしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。第2回は、プロジェクトを開始するまでの準備を解説します。

シェルスクリプトマガジン Vol.61は以下のリンク先でご購入できます。

図1 注釈を入れたプログラム

# Node.js(Javascript)でプログラムは書かれている
# Expressフレームワークを利用している
const express = require('express');
const app = express()

# SequelizeフレームワークでModelを利用している
const models = require('./models');
const expenses = models.expense;

# POSTデータをパーシングするためのライブラリを利用している
const bodyParser = require('body-parser');

# セッション管理にCOOKIEを利用しているが、特にステートは保管していない
const cookieParser = require('cookie-parser');
const session = require('express-session');
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({
  secret: 'secret',
  resave: false,
  saveUninitialized: false,
  cookie: {
    maxAge: 24 * 30 * 60 * 1000
  }
}));

# ユーザー情報をプログラムに埋め込んでいる
const users = {
  'user01': 'p@ssw0rd',
  'user02': 'ewiojfsad'
};

# /loginへアクセスするとログイン機能を利用できる (HTMLがべた書き)
app.get('/login', (req, res) => {
  res.send('<h1>LOGIN</h1><form action="/login" method="post">ユーザーID:<input type="text" name="user" size="40"><br />パスワード<input type="password" name="password"><input type="submit" value="ログイン">');
});
# ログインに成功しても、失敗しても、画面上は特に何も起きない
app.post('/login', (req, res) => {
  if (eval("users." + req.body.user) === req.body.password) {
    req.session.user = req.body.user;
  }
  res.redirect('/');
});

# /expenseへPOSTすると経費を登録できるが、特にエラー処理はない
app.post('/expense', (req, res) => {
  expenses.create(req.body)
    .then(() => {
      res.redirect('/');
    });
});

# /へアクセスすると誰でも経費の一覧が見られる。ログイン・経費入力フォームへのリンクを表示 (HTMLがべた書き)
app.get('/', (req, res) => {
  const user = req.session.user || '名無しの権兵衛';
  res.writeHead(200, { "Content-Type": "text/html" });
  res.write(<h1>Hello ${user}</h1><table><tr><th>ID</th><th>申請者名</th><th>日付</th><th>経費タイプ</th><th>経費詳細</th><th>金額</th></tr>);
  expenses.findAll()
    .then(results => {
      for (let i in results) {
        res.write(<tr><td>${results[i].id}</td><td>${results[i].user_name}</td><td>${results[i].date}</td><td>${results[i].type}</td><td>${results[i].description}</td><td>${results[i].amount}</td></tr>);
      }
      res.write('</table><a href="/login">ログイン</a><a href="/submit">経費入力</a>');
      res.end();
    });
});

# /submitへアクセスすると、経費入力フォームが表示される (HTMLがべた書き)
app.get('/submit', (req, res) => {
  const user = req.session.user || '名無しの権兵衛';
  res.send(<h2>経費入力</h2><form action="/expense" method="post">申請者名:<input type="text" name="user_name" value="${user}"><br />日付:<input type="date" name="date"><br />経費タイプ:<input type="text" name="type"><br />経費詳細:<input type="text" name="description"><br />金額:<input type="number" name="amount"><br /><input type="submit" value="経費申請">);
});

# Webアプリケーションサーバーとして起動する
const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(http://localhost:${port});
})

香川大学SLPからお届け!(Vol.61掲載)

投稿日:2019.07.25 | カテゴリー: コード

著者:宇野 光純

 今回は、Windowsアプリケーションとして動く簡単な2Dゲームの開発を紹介します。汎用プログラミング言語の「C++」と、オープンソースのパソコンゲーム開発用ライブラリの「DXライブラリ」を組み合わせることで、時間と労力は必要ですが、Unityなどのゲームエンジンよりも自由度の高いゲーム開発ができます。

シェルスクリプトマガジン Vol.61は以下のリンク先でご購入できます。

図1 DXライブラリを使用する際の基本コード(Main.cpp)

#include "DxLib.h"
// プログラムはWinMainから開始
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPSTR lpCmdLine, int nCmdShow )
{
  // DXライブラリ初期化処理
  if( DxLib_Init() == -1 ) {
    return -1; // エラーが起きたら直ちに終了
  }
  // ウィンドウモードで起動、拡大して表示する
  ChangeWindowMode(TRUE);
  SetWindowSizeExtendRate(1.5);
  // ちらつきを消すために、描画先を裏画面にする
  SetDrawScreen(DX_SCREEN_BACK);
  while (ProcessMessage() == 0 && ScreenFlip() == 0 &&
         ClearDrawScreen() == 0) {
    // ここにゲームの処理を書く
  }
  DxLib_End(); // DXライブラリ使用の終了処理
  return 0; // ソフトの終了
}

図2 「Info.cpp」ファイルに記述するコード

#include "DxLib.h"
#include "Info.h"
int g_Width, g_Height; // ウィンドウの横幅と縦幅
int g_StageTime; // ステージ開始からの経過時間
static const int KEY_NUM = 256; // キー配列の最大値
char g_Buf[KEY_NUM]; // キーの状態を保持する配列

void InfoInitialize() { // 各データの初期化
  GetScreenState(&g_Width, &g_Height, NULL);
  g_StageTime = 0;
}
void InfoUpdate() { // 各データの更新
  g_StageTime++;
}
int GetWidth() { // ウィンドウの横幅を返す
  return g_Width;
}
int GetHeight() { // ウィンドウの縦幅を返す
  return g_Height;
}
int GetStageTime() { // ステージ開始からの経過時間を得る
  return g_StageTime;
}
void KeyUpdate() { // 全キーの状態を更新する
  GetHitKeyStateAll(g_Buf);
}
bool GetKey(int key_code) { // 指定したキーの状態を取得する
  if (g_Buf[key_code]) { return true; }
  return false;
}

図3 「Info.h」ファイルに記述するコード

#pragma once

void InfoInitialize();
void InfoUpdate();
int GetWidth();
int GetHeight();
int GetStageTime();
void KeyUpdate();
bool GetKey(int key_input);

図4 「Object.h」ファイルに記述するコード

#pragma once
class Object {
protected:
  int size;                // 画像サイズ
public:
  double x, y;             // X座標、Y座標
  virtual void Update() {} // 更新
  virtual void Draw() {}   // 描画
};

図5 「Player.cpp」ファイルに記述するコード

#include "DxLib.h"
#include "Player.h"
#include "Info.h"
#include <math.h>

int Player::image;
Player::Player() {
  x = y = 200.0; v = 4;    // 座標と速度の初期化
  SetImage();              // 画像関連の設定
}
void Player::Update() {
  Move();                 // 移動の更新
}
void Player::Draw() {      // 描画
  DrawGraph((int)(x - size / 2), (int)(y - size / 2), image, TRUE);
}
void Player::SetImage() {  // 画像関連の設定
  size = 64; image = LoadGraph("./images/player.png");
}
void Player::Move() {      // 自機操作
  double nxv = 0, nyv = 0; // 移動量保持用
  // X方向の移動量の調整
  if (GetKey(KEY_INPUT_LEFT)) { nxv -= v; }
  if (GetKey(KEY_INPUT_RIGHT)) { nxv += v; }
  // Y方向の移動量の調整
  if (GetKey(KEY_INPUT_UP)) { nyv -= v; }
  if (GetKey(KEY_INPUT_DOWN)) { nyv += v; }
  if (GetKey(KEY_INPUT_LSHIFT)) { nxv /= 2; nyv /= 2; }
  if (nxv != 0 && nyv != 0) { nxv /= sqrt(2); nyv /= sqrt(2); }
  x += nxv; y += nyv; // 移動量の加算
 // 移動範囲外に出たときは範囲内に戻す
  if (y < 0) { y = 0; }
  if (GetHeight() < y) { y = GetHeight(); }
  if (GetWidth() < x) { x = GetWidth(); }
  if (x < 0) { x = 0; }
}

図6 「Player.h」ファイルに記述するコード

#pragma once
#include "Object.h"

class Player : public Object {
private:
  static int image; // 画像ハンドル
  void SetImage();  // 画像関連の設定
  void Move();      // 自機の操作
public:
  double v;         // 移動速度
  Player();
  void Update();    // 更新
  void Draw();      // 描画
};

図7 「Enemy.cpp」ファイルに記述するコード

#include "DxLib.h"
#include "Enemy.h"
#include "Info.h"

int Enemy::image;
Enemy::Enemy() {
  x = GetWidth() / 2; y = 50.0; // X、Y座標の初期化
  xv = yv = 0.0;                // 増加量の初期化
  this->SetImage();             // 画像関連の設定
}
void Enemy::Update() {
  x += xv; y += yv;
}
void Enemy::Draw() {
  DrawGraph((int)(x - size / 2),
            (int)(y - size / 2), image, TRUE);
}
void Enemy::SetImage() {
  size = 64;
  image = LoadGraph("./images/enemy.png");
}

図8 「Enemy.h」ファイルに記述するコード

#pragma once
#include "Object.h"

class Enemy : public Object {
private:
  static int image; // 画像ハンドル
  void SetImage();  // 画像関連の設定
public:
  double xv, yv;    // X、Y 方向の増加量
  Enemy();
  void Update();    // 更新
  void Draw();      // 描画
};

図9 「MainScene.cpp」ファイルに記述するコード

#include "DxLib.h"
#include "MainScene.h"
#include "Info.h"

MainScene::MainScene() {
  // 背景を灰色に
  SetBackgroundColor(100, 100, 100);
  InfoInitialize(); // ウィンドウサイズなどの初期化
  player = Player(); // 自機の初期化
  enemy = Enemy(); // 敵機の初期化
}
void MainScene::Update() {
  InfoUpdate(); // 各データの更新
  player.Update(); // 自機の操作
  enemy.Update(); // 敵機の更新
}
void MainScene::Draw() {
  player.Draw(); enemy.Draw(); // 自機と敵機の描画
  // 経過時間の描画
  DrawFormatString(GetWidth() / 2 - 40, 0,
                   GetColor(255, 255, 255),
                   "Time : %d", GetStageTime());
}

図10 「MainScene.h」ファイルに 記述するコード

#pragma once
#include "Player.h"
#include "Enemy.h"

class MainScene {
private:
  Player player;
  Enemy enemy;
public:
  MainScene();
  void Update(); // 更新
  void Draw();   // 描画
};

図11 コードを追加した「Main.cpp」ファイルの内容

#include "DxLib.h"
#include "MainScene.h"
#include "Info.h"

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPSTR lpCmdLine, int nCmdShow )
{
  if( DxLib_Init() == -1 ) {
    return -1;
  }
  ChangeWindowMode(TRUE);
  SetWindowSizeExtendRate(1.5);
  SetDrawScreen(DX_SCREEN_BACK);
  // シーンの初期化
  MainScene ms = MainScene();
  while (ProcessMessage() == 0 && ScreenFlip() == 0 &&
         ClearDrawScreen() == 0) {
    KeyUpdate();
    // ゲーム終了
    if (GetKey(KEY_INPUT_ESCAPE)) { break;}
    ms.Update(); // 更新
    ms.Draw();   // 描画
  }
  DxLib_End();
  return 0;
}

機械学習のココロ(Vol.61掲載)

投稿日:2019.07.25 | カテゴリー: コード

著者:石井 一夫

今回はディープラーニングのバリエーションとして、画像認識によく用いられる「CNN」(Convolutional Neural Network)と、自然言語処理によく用いられる「RNN」(Recurrent Neural Network)について紹介します。

シェルスクリプトマガジン Vol.61は以下のリンク先でご購入できます。

図3 サンプルコードのCNN 定義部分(抜粋)

def cnn_model_fn(features, labels, mode):
    """Model function for CNN."""
    # Input Layer
    input_layer = tf.reshape(features["x"], [-1, 28, 28, 1])

    # Convolutional Layer #1
    conv1 = tf.layers.conv2d(
        inputs=input_layer,
        filters=32,
        kernel_size=[5, 5],
        padding="same",
        activation=tf.nn.relu)

    # Pooling Layer #1
    pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2)

    # Convolutional Layer #2 and Pooling Layer #2
    conv2 = tf.layers.conv2d(
        inputs=pool1,
        filters=64,
        kernel_size=[5, 5],
        padding="same",
        activation=tf.nn.relu)
    pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2)

    # Dense Layer
    pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64])
    dense = tf.layers.dense(inputs=pool2_flat, units=1024, 
    activation=tf.nn.relu)
    dropout = tf.layers.dropout(
    inputs=dense, rate=0.4, training=mode == tf.estimator.ModeKeys.TRAIN)

    # Logits Layer
    logits = tf.layers.dense(inputs=dropout, units=10)
(略)

図7 サンプルコードのRNN 定義部分

def build_model(vocab_size, embedding_dim, rnn_units, batch_size):
    model = tf.keras.Sequential([
         tf.keras.layers.Embedding(vocab_size, embedding_dim,
                                   batch_input_shape=[batch_size, None]),
         rnn(rnn_units,
                  return_sequences=True,
                  recurrent_initializer='glorot_uniform',
                  stateful=True),
         tf.keras.layers.Dense(vocab_size)
     ])
     return model

シェルスクリプトマガジンvol.61 Web掲載記事まとめ

投稿日:2019.07.25 | カテゴリー: コード

004 レポート 新版の「Debian 10」を公開
005 レポート 新開発の汎用メモリーアロケータ
006 NEWS FLASH
008 特集1 Red Hat Enterprise Linux 8/森若和雄
018 特集2 micro:bitを動かそう/中田和宏
030 特別企画 GPSモジュールで遊ぼう/麻生二郎 コード掲載
037 姐のNOGYO
038 ラズパイセンサーボードで学ぶ 電子回路の制御/米田聡 コード掲載
041 Webアプリケーションの正しい作り方/しょっさん コード掲載
052 仮想現実/桑原滝弥・イケヤシロウ
054 MySQL Shellを使おう/梶山隆輔
062 中小企業手作りIT化奮戦記/菅雄一
068 バーティカルバーの極意/飯尾淳 コード掲載
074 香川大学SLPからお届け!/宇野光純 コード掲載
080 円滑コミュニケーションが世界を救う!/濱口誠一
082 機械学習のココロ/石井一夫 コード掲載
086 法林浩之のFIGHTING TALKS/法林浩之
088 漢のUNIX/後藤大地
094 ユニケージ新コードレビュー/坂東勝也
102 Techパズル/gori.sh
104 コラム「近未来に起こってほしいこと」/シェル魔人

特別企画 GPSモジュールで遊ぼう(Vol.61掲載)

投稿日:2019.07.25 | カテゴリー: コード

著者:麻生 二郎

「NEO-6M」というGPS(地理情報システム)モジュールを搭載したアンテナ付き基板が数百円に 購入できます。この基板にUART-USB変換ケーブルを接続するだけで、パソコンから位置の情報を取得できます。最新のLinuxディストリビューションとシェルスクリプトで遊んでみましょう。

シェルスクリプトマガジン Vol.61は以下のリンク先でご購入できます。

図13 GPSモジュールを利用するシェルスクリプト(gps_data.sh)

#!/bin/sh

## Yahoo! JAPANのアプリケーションID
yahoo_appid="アプリケーションID"

## 地図の大きさ、縮尺
width="640"
height="480"
scale="17"

## GPSのデータ取得
timeout 3 cat /dev/ttyUSB0 > /tmp/gps.log

##緯度計算
latitude_raw=$(cat /tmp/gps.log | grep GPGLL | head -1 | cut -f 2 -d ",")
latitude_ns=$(cat /tmp/gps.log | grep GPGLL | head -1 | cut -f 3 -d ",")
ns=1;test "${latitude_ns}" = "S" && ns=-1
latitude_do=$(echo "scale=0;${latitude_raw} / 100" | bc)
latitude=$(echo "scale=5;${ns} * ((${latitude_raw} - ${latitude_do} * 100) / 60 + ${latitude_do})" | bc)

##経度計算
longitude_raw=$(cat /tmp/gps.log | grep GPGLL | head -1 | cut -f 4 -d ",")
longitude_ew=$(cat /tmp/gps.log | grep GPGLL | head -1 | cut -f 5 -d ",")
ew=1;test "${longitude_ew}" = "W" && ew=-1
longitude_do=$(echo "scale=0;${longitude_raw} / 100" | bc)
longitude=$(echo "scale=5;${ew} * ((${longitude_raw} - ${longitude_do} * 100) / 60 + ${longitude_do})" | bc)

##HTMLファイル生成
echo "<!DOCTYPE html>" > gps.html
echo "<html lang='ja'>" >> gps.html
echo "<head>" >> gps.html
echo "<meta charset='UTF-8'>" >> gps.html
echo "<title>場所</title>" >> gps.html
echo "</head>" >> gps.html
echo "<body>" >> gps.html
echo "<p>現在地<br>" >> gps.html
echo "<img width=${width} height=${height} src='https://map.yahooapis.jp/map/V1/static?appid=${yahoo_appid}&lat=${latitude}&lon=${longitude}&z=${scale}&width=${width}&height=${height}&pointer=on'>" >> gps.html
echo "</p>" >> gps.html
echo "</body>" >> gps.html
echo "</html>" >> gps.html

バーティカルバーの極意(Vol.61掲載)

投稿日:2019.07.25 | カテゴリー: コード

著者:飯尾 淳

 前回から、「GDHP」(Gniibe Distributed HanoiProtocol)というプロトコルでハノイの塔パズルを解き、それを可視化して確認しようという試みに挑戦しています。プログラムはJavaScript で記述し、「p5.js」というグラフィックスライブラリを利用します。
 前回は、初期状態の塔を積み上げるところまで完成させました。今回はアニメーションで実際に動作させ、GDHPでパズルが解けることを確認しましょう。

シェルスクリプトマガジン Vol.61は以下のリンク先でご購入できます。

図1 向かい合う2本の塔を点滅させるコード

(略)
const POSITIONS = { 'Source'      : [0.268, 0.714],
                    'Auxiliary'   : [0.500, 0.286],
                    'Destination' : [0.732, 0.714] };
const FLASH_CTR = 20;

class Position {
(略)
}

class Tower {
  constructor(name, disks, direction=null) {
(略)
    this.pos = new Position(rx*C_WIDTH, ry*C_HEIGHT);

    this.exchanging = false;
    this.flash_ctr = 0;
  }

  draw() {
(略)
    // 支柱を描く
    stroke('brown');
    fill(this.exchanging & (this.flash_ctr < FLASH_CTR/2) 
      ? 'gold' : 'white');
    ellipse(pos.x, pos.y, 2*POLE_R);

(略)
    line(sx, sy, dx, dy);
  }

  flash_pole() {
    this.exchanging = (this.direction.direction === this);
    this.flash_ctr++;
    this.flash_ctr %= FLASH_CTR;
  }
}
)
function draw() {
  background('beige');
  [src, aux, dst].forEach(function(t) { 
    t.draw(); 
    t.flash_pole();
  })
}

図3 draw()関数を修正する

var moving_disk = null;
(略)
function draw() {
  background('beige');
  [src, aux, dst].forEach(function(t) {
    t.draw();
    t.flash_pole();
  })

  if (moving_disk == null) {
    moving_disk = pop_disk();
  } else {
    var finished_p = draw_moving_disk();
    if (finished_p) {
      turn();
      moving_disk = null;
    }
  }
}

図4 円盤を移動させる修正

(略)
const FLASH_CTR = 20;
const STEPS = 30;

class Vector {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class Position extends Vector {
  constructor(x, y) { super(x, y); }

  move(vec) {
    this.x += vec.x;
    this.y += vec.y;
  }
}  

class Disk {
  constructor(level) {
  this.level = level;
  this.color = COLORS[level];
  this.r = (DISK_R-POLE_R)*(N_DISKS-level)
              / N_DISKS + POLE_R;
}

  draw(pos) {
    stroke('black');
    fill(this.color);
    ellipse(pos.x, pos.y, 2*this.r);
  }
}

class MovingDisk extends Disk {
  constructor(level, from, to) {
    super(level); 
    this.pos = new Position(from.pos.x, from.pos.y);
    this.vec = new Vector((to.pos.x-from.pos.x)/STEPS, 
                          (to.pos.y-from.pos.y)/STEPS);
    this.move_ctr = 0;
    this.from = from;
    this.to = to;
  }

  step_forward() {
    this.pos.move(this.vec);
    this.move_ctr++;
  }

  finish_p() {
    var ret_flag = false;
    if (ret_flag = (this.move_ctr == STEPS)) {
      this.to.disks.push(new Disk(this.level));
    }
    return ret_flag;
  }
}

class Tower {
(略)
  flash_pole() {
    this.exchanging = (this.direction.direction === this);
    this.flash_ctr++;
    this.flash_ctr %= FLASH_CTR;
  }

  get toplevel() {
    var l = this.disks.length;
    // '-1'は円盤が一つもないことを示す
    return (l > 0) ? this.disks[l-1].level : -1;
  }
}

var src = new Tower('Source', N_DISKS);
(略)
src.direction = (N_DISKS % 2 == 1) ? dst : aux;

// 移動中の円盤
var moving_disk = null;

function pop_disk() {
  var towers = [src, aux, dst].filter(t => t.exchanging);
  var idx, from, to;
  idx = (towers[0].toplevel > towers[1].toplevel) ? 0 : 1;
  [from, to] = [towers[idx], towers[1-idx]];
  return new MovingDisk(from.disks.pop().level, from, to);
}

function draw_moving_disk() {
  var d = moving_disk;
  d.step_forward();
  d.draw(d.pos);
  return d.finish_p();
}

function turn() {
  // まだ何もしない
}

function setup() {
  createCanvas(C_WIDTH, C_HEIGHT);
  frameRate(30);
}

function draw() {
  background('beige');
  [src, aux, dst].forEach(function(t) { 
    t.draw(); 
    t.flash_pole();
  })

  if (moving_disk == null) {
    moving_disk = pop_disk();
  } else {
    var finished_p = draw_moving_disk();
    if (finished_p) {
      turn();
      moving_disk = null;
    }
  }
}

図6 trun()関数

function turn() {
  [moving_disk.from, moving_disk.to].forEach(function(t) {
    t.direction = ([src, aux, dst]
      .filter(x => (x !== t) && (x !== t.direction)))[0];
    t.exchanging = false;
  })
}

図7 終了条件を追加

(略)
function pop_disk() {
  var towers = [src, aux, dst].filter(t => t.exchanging);
  var idx, from, to;
  if (towers[0].toplevel == towers[1].toplevel) { 
    noLoop(); 
    return null; 
  };
  idx = (towers[0].toplevel > towers[1].toplevel) ? 0 : 1;
  [from, to] = [towers[idx], towers[1-idx]];
  return new MovingDisk(from.disks.pop().level, from, to);
}
(略)

センサーボードで学ぶ電子回路の制御(Vol.61掲載)

投稿日:2019.07.25 | カテゴリー: コード

著者:米田 聡

シルスクリプトマガジンでは、小型コンピュータボード「Raspberry Pi」(ラズパイ)向けのセンサー搭載拡張ボード「ラズパイセンサーボード」を制作しました。第8回では、前回I/Oエキスパンダ「MCP23017」で増やしたGPIO 端子に7セグメントLEDを接続して制御します。

シェルスクリプトマガジン Vol.61は以下のリンク先でご購入できます。

図5 7セグメントLEDに数字を出力するクラスライブラリ(ssegled.py)

from mcpgpio import MCPGPIO

class LED7SEG():
  __leds = [  [0, 1, 2, 8, 9, 10],    # 0
              [2, 8],                 # 1
              [9, 8, 11, 0, 1],       # 2
              [9, 8, 11, 2, 1],       # 3
              [10,11, 2, 8],          # 4
              [9, 10, 11, 2, 1],      # 5
              [9, 10, 11, 2, 1, 0],   # 6
              [9, 8, 2],              # 7
              [0, 1, 2, 8, 9, 10, 11],# 8
              [1, 2, 8, 9, 10, 11]    # 9
          ]

  def __init__(self):
    self.gpio = MCPGPIO()
    self.gpio.setup(0, MCPGPIO.OUTPUT)
    self.gpio.setup(1, MCPGPIO.OUTPUT)
    self.gpio.setup(2, MCPGPIO.OUTPUT)
    self.gpio.setup(3, MCPGPIO.OUTPUT)
    self.gpio.setup(8, MCPGPIO.OUTPUT)
    self.gpio.setup(9, MCPGPIO.OUTPUT)
    self.gpio.setup(10, MCPGPIO.OUTPUT)
    self.gpio.setup(11, MCPGPIO.OUTPUT)

  def off(self):
    for l in self.__leds[8]:
      self.gpio.output(l, MCPGPIO.LOW)
  
  def print(self, n):
    if (n < 0) or (n > 9):
      return
      
    self.off()
    for l in self.__leds[n]:
      self.gpio.output(l, MCPGPIO.HIGH)

図6 テストスクリプト(count.py)

from ssegled import LED7SEG
import time

led = LED7SEG()

for i in range(10):
  led.print(i)
  time.sleep(1)

第8回 自動でアーカイビングする

投稿日:2019.07.16 | カテゴリー: 記事

 大容量のハードディスクやSSD(Solid State Drive)が安価に購入できても、パソコンやコンピュータ内のファイル整理は重要です。しかし、いざファイルを整理しようとして削除してもよいかどうか迷うものも多数あるでしょう。とりあえずは、まとめて圧縮することでアーカイブ(書庫)化して取っておくのがよいでしょう。

 そこで今回は、ファイルを自動でまとめて圧縮するアーカイビングのシェルスクリプトを作成します(図1)。

図1 ファイルをフォルダごとアーカイビング

第7回 DMを自動送信する(STARTTLS編)

投稿日:2019.07.2 | カテゴリー: 記事

 第6回では、「ダイレクトメール」(DM)を送信するときにメールサーバーへ接続するプロトコルとして「SMTPS」(SMTP over SSL/TLS)を用いました。もう一つ、安全にメールサーバーにメッセージを送信する著名なプロトコルとして「SMTP STARTTLS」があります。これは、完全に通信が暗号化されたSMTPSと違い、最初は平文で途中から暗号化するという通信方法が可能です。ちなみに、SMTPSでは「465」番のTCPポートを、SMTP STARTTLSでは「587」番のTCPポートを使用します。

 SMTP STARTTLSの場合、SMTPSとはアクセス方法が異なるので、シェルスクリプトの書き方が違います。そこで今回は、SMTP STARTTLSを対応したメールサーバーからDMを自動送信するシェルスクリプトを作成します(図1)。

図1 SMTP STARTTLS対応のメールサーバーからDM送信

第6回 DMを自動送信する(SMTPS編)

投稿日:2019.06.18 | カテゴリー: 記事

 電子メールで大量の「ダイレクトメッセージ」(DM)を送りたい場合、手作業よりも自動化しておくとよいでしょう。特に、相手ごとにメッセージの内容を少し変えて送信したいときには、自動化が不可欠です。

 そこで今回は、DMを自動送信するシェルスクリプトを作成します(図1)。

第5回 文書をPDF化する

投稿日:2019.06.4 | カテゴリー: 記事

 Microsoft OfficeやLibreOfficeなどのオフィスソフトで作成した文書を皆で閲覧可能にする場合、「PDF」(Portable Document Format)形式に変換しておけばよいでしょう。誤って書き換えてしまったりすることがなく、コメントなども付与できて便利です。最近のオフィスソフトには、PDFファイルへ変更する機能があります。また、印刷するときにPDFファイルに保存することも可能です。ただし、これらは自ら操作しなくてはいけません。

 そこで今回は、オフィス文書をPDF形式のファイルに自動変換するシェルスクリプトを作成します(図1)。

図1 オフィス文書をPDFファイルに変換

ラズパイセンサーボード向けソースコード集

投稿日:2019.05.30 | カテゴリー: コード

シェルスクリプトマガジンとビット・トレード・ワンで共同制作したRaspberry Pi拡張ボード「ラズパイセンサーボード」のソースコード集です。雑誌と一緒にご活用ください。

ソースコードの入手先

2018年12月号(Vol.57)特集1「ラズパイでセンサーを扱う」
2019年2月号(Vol.58)連載「ラズパイセンサーボードで学ぶ 電子回路の制御」第5回
・2019年4月号(Vol.59)連載「ラズパイセンサーボードで学ぶ 電子回路の制御」第6回(コードなし)
2019年6月号(Vol.60)連載「ラズパイセンサーボードで学ぶ 電子回路の制御」第7回
2019年8月号(Vol.61)連載「ラズパイセンサーボードで学ぶ 電子回路の制御」第8回
2019年10月号(Vol.62)連載「ラズパイセンサーボードで学ぶ 電子回路の制御」第9回
2019年12月号(Vol.63)連載「ラズパイセンサーボードで学ぶ 電子回路の制御」第10回
・2020年2月号(Vol.64)連載「ラズパイセンサーボードで学ぶ電子回路の制御」第11回(コードなし)
・2020年6月号(Vol.66)連載「ラズパイセンサーボードで学ぶ電子回路の制御」第13回
2020年8月号(Vol.67)連載「ラズパイセンサーボードで学ぶ電子回路の制御」最終回


※関連記事掲載時に追加していきます。

※ラズパイ入門ボード向けソースコード集はこちら

Webアプリケーションの正しい作り方(Vol.60記載)

投稿日:2019.05.25 | カテゴリー: コード

著者:しょっさん

 ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならないのでしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。第1回は、動くソフトウエアとは何かを解説していきます。

シェルスクリプトマガジン Vol.60は以下のリンク先でご購入できます。

図1 経費精算Webアプリケーション

const express = require('express');
const app = express()
const models = require('./models');
const expenses = models.expense;
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const users = {
  'user01': 'p@ssw0rd',
  'user02': 'ewiojfsad'
};

app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({
  secret: 'secret',
  resave: false,
  saveUninitialized: false,
  cookie: {
    maxAge: 24 * 30 * 60 * 1000
  }
}));

app.get('/login', (req, res) => {
  res.send('<h1>LOGIN</h1><form action="/login" method="post">ユーザーID:<input type="text" name="user" size="40"><br />パスワード<input type="password" name="password"><input type="submit" value="ログイン">');
});

app.post('/login', (req, res) => {
  if (eval("users." + req.body.user) === req.body.password) {
    req.session.user = req.body.user;
  }
  res.redirect('/');
});

app.post('/expense', (req, res) => {
  expenses.create(req.body)
    .then(() => {
      res.redirect('/');
    });
});

app.get('/', (req, res) => {
  const user = req.session.user || '名無しの権兵衛';
  res.writeHead(200, { "Content-Type": "text/html" });
  res.write(<h1>Hello ${user}</h1><table><tr><th>ID</th><th>申請者名</th><th>日付</th><th>経費タイプ</th><th>経費詳細</th><th>金額</th></tr>);
  expenses.findAll()
    .then(results => {
      for (let i in results) {
        res.write(<tr><td>${results[i].id}</td><td>${results[i].user_name}</td><td>${results[i].date}</td><td>${results[i].type}</td><td>${results[i].description}</td><td>${results[i].amount}</td></tr>);
      }
      res.write('</table><a href="/login">ログイン</a><a href="/submit">経費入力</a>');
      res.end();
    });
});

app.get('/submit', (req, res) => {
  const user = req.session.user || '名無しの権兵衛';
  res.send(<h2>経費入力</h2><form action="/expense" method="post">申請者名:<input type="text" name="user_name" value="${user}"><br />日付:<input type="date" name="date"><br />経費タイプ:<input type="text" name="type"><br />経費詳細:<input type="text" name="description"><br />金額:<input type="number" name="amount"><br /><input type="submit" value="経費申請">);
});

const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(http://localhost:${port});
})

「Visual Studio Code」を便利に使う(Vol.60掲載)

投稿日:2019.05.25 | カテゴリー: コード

著者:あかね

 米Microsoft社発のオープンソースエディタ「Visual Studio Code」には「Extension」と呼ばれる機能拡張用のソフトウエアが多数提供されています。本連載では便利なExtensionの使い方を中心に紹介します。第3回は、Windowsの OS標準のCL(I シェル環境)「PowerShell」で記述したスクリプトをデバックするためのExtensionを紹介します。

シェルスクリプトマガジン Vol.60は以下のリンク先でご購入できます。

図9 サンプルスクリプト(test.ps1)

# Variable declaration
$KaigenDate #改元日
$Wareki #和暦
$CultureInfo #フォーマット前の情報

$CultureInfo = New-Object system.Globalization.CultureInfo("ja-JP");
$CultureInfo.DateTimeFormat.Calendar = New-Object System.Globalization.JapaneseCalendar

# Main
$KaigenDate= Get-Date -Date '2019/05/01'
$Wareki=$KaigenDate.ToString("ggy年M月d日", $CultureInfo)
Write-Host '改元日は'$Wareki'です!'

図16 引数を必要とするサンプルスクリプト

# WarekiDisp という名前の関数。引数に与えた日付(yyyymmdd)を和暦表示します。
param([string]$arg_date)

# Variable declaration
$conv_Date #引数で指定されたものをDateに設定
$Wareki #和暦
$CultureInfo #フォーマット前の情報

$CultureInfo = New-Object system.Globalization.CultureInfo("ja-JP");
$CultureInfo.DateTimeFormat.Calendar = New-Object System.Globalization.JapaneseCalendar

# Main
$conv_Date=[datetime]::ParseExact($arg_date, "yyyyMMdd", $null);
$Wareki=$conv_Date.ToString("ggy年M月d日", $CultureInfo)

Write-Host $Wareki'です!'

特集1 ラズパイで電子回路の作成と制御(Vol.60掲載)

投稿日:2019.05.25 | カテゴリー: コード

著者:麻生二郎

 電子回路を触ってみたい、作ってみたい、制御してみたいと思ったときに、人気の小型コンピュータボード「Raspberry Pi」(ラズパイ)と組み合わせるのが意外と簡単です。本特集では、市販のモジュールと、ラズパイを使って電子回路
の作成や制御を素早く実現する方法を紹介します。

シェルスクリプトマガジン Vol.60は以下のリンク先でご購入できます。

図5 制御プログラム(bme280_sensor.py)

#!/usr/bin/env python3

import smbus2
import bme280
i2c = smbus2.SMBus(1)

data = bme280.sample(i2c, 0x76)
print('気温 :' + str(round(data.temperature, 1)) + '度')
print('湿度 :' + str(round(data.humidity, 1)) + '%')
print('気圧 :' + str(round(data.pressure, 1)) + 'hPa')

図8 制御プログラム(atd1602_lcd.py)

#!/usr/bin/env python3

from lcd_st7032 import ST7032

lcd = ST7032()
lcd.write("Shellscript")
lcd.setCursor(1, 0)
lcd.write([0xbc, 0xaa, 0xd9, 0xbd, 0xb8, 0xd8, 0xcc, 0xdf, 0xc4])

図11 制御プログラム(bh1750_sensor.py)

#!/usr/bin/env python3

import smbus2
from i2csense.bh1750 import BH1750

bus = smbus2.SMBus(1)
sensor = BH1750(bus)

sensor.update()
print(sensor.light_level)
print(sensor.current_state_str)

図14 制御プログラム(rain_sensor.py)

#!/usr/bin/env python3

import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.IN)

try:
  while True:
    rain = GPIO.input(17)
    if rain == 0:
      print("雨が降り始めました")
      GPIO.cleanup()
      break
except KeyboardInterrupt:
  GPIO.cleanup()

図17 制御プログラム(fire_sensor.py)

#!/usr/bin/env python3

import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.IN)

try:
  while True:
    fire = GPIO.input(17)
    if fire == 0:
      print("火災が発生しました")
      GPIO.cleanup()
      break
except KeyboardInterrupt:
  GPIO.cleanup()

図20 制御プログラム(pir_sensor.py)

#!/usr/bin/env python3

import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.IN)

try:
  while True:
    pir = GPIO.input(17)
    if pir == 1:
      print("人が侵入しました")
      GPIO.cleanup()
      break
except KeyboardInterrupt:
  GPIO.cleanup()

図26 制御プログラム(doorphone.py)

#!/usr/bin/env python3

from time import sleep
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.OUT)

try:
  for i in range(0,3):
    GPIO.output(17, GPIO.HIGH)
    GPIO.output(17, GPIO.LOW)
    sleep(2)
except KeyboardInterrupt:
  GPIO.cleanup()

図28 制御プログラム(sound_sensor.py)

#!/usr/bin/env python3

import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.IN)

try:
  while True:
    if GPIO.wait_for_edge(17, GPIO.RISING):
      print("指パッチン!")
      GPIO.cleanup()
      break
except KeyboardInterrupt:
  GPIO.cleanup()

図31 制御プログラム(soil_sensor.py)

#!/usr/bin/env python3

import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.IN)

try:
  while True:
    rain = GPIO.input(17)
    if rain == 1:
      print("水分量が足りません")
      GPIO.cleanup()
      break
except KeyboardInterrupt:
  GPIO.cleanup()

図34 制御プログラム(slope_sensor.py)

#!/usr/bin/env python3

import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.IN)

try:
  while True:
    if GPIO.wait_for_edge(17, GPIO.FALLING):
      print("傾きました")
      GPIO.cleanup()
      break
except KeyboardInterrupt:
  GPIO.cleanup()

図38 制御プログラム(distance_sensor.py)

#!/usr/bin/env python3

import time
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setup(27, GPIO.OUT)
GPIO.setup(17, GPIO.IN)

GPIO.output(27, GPIO.LOW)
time.sleep(0.3)
GPIO.output(27, GPIO.HIGH)
time.sleep(0.00001)
GPIO.output(27, GPIO.LOW)

try:
  while GPIO.input(17) == 0:
    palse_start = time.time()
  while GPIO.input(17) == 1:
    palse_end = time.time()
  palse_width_time = palse_end - palse_start
  distance = palse_width_time * 1000000 / 58
  print("距離は {0} cmです".format(distance))
  GPIO.cleanup()
except KeyboardInterrupt:
  GPIO.cleanup()

図42 制御プログラム(music_box.py)

#!/usr/bin/env python3

from time import sleep
import RPi.GPIO as GPIO
import sys

GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.OUT)

for _ in range(args):
  GPIO.output(17, GPIO.HIGH)
  sleep(0.01)
  GPIO.output(17, GPIO.LOW)
  sleep(0.05)
GPIO.cleanup()

Vol.60

投稿日:2019.05.25 | カテゴリー: バックナンバー

 ラズパイのおかげで電子工作が身近になってきています。とはいえ、誰でもすぐに始められるわけではありません。特集1では、市販されている12種類の電子回路モジュールを使って、やさしく、そして簡単に、ラズパイで電子回路の作成と制御ができる方法を紹介しています。こんなセンサーがほしい、液晶パネルに文字を表示したい人も満足できる内容です。
 特集2では、Webアプリケーションのプログラミング言語「PHP」をやさしく解説しています。短い、簡単なコードを書きながら、データベース管理システムを利用したWebアプリケーションを作成していきます。
 特集3では、本格的な業務システムをシェルスクリプトで構築できる「ユニケージ開発手法」を紹介しています。なぜシェルスクリプトで業務システムが作れるのか、ユニケージ開発手法の専用コマンドのusp Tukubaiとは何か、テキストファイルを利用したデータ管理の仕組みなど、入門者にはぴったりの内容となっています。
 特別企画では、ソフトウエア開発における「コーディング」「ビルド」「実装」「テスト」という一連の作業を楽にする「ツールチェーン」を取り上げました。IBM Cloudにおけるツールチェーンの構築・利用方法を具体的に分かりやすく紹介しています。
 このほか、新連載「Webアプリケーションの正しい作り方」、人気連載の「ラズパイセンサーボードで学ぶ電子回路の制御」なども掲載しています。
 今回も読み応え十分のシェルスクリプトマガジン Vol.60。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

バーティカルバーの極意(Vol.60掲載)

投稿日:2019.05.25 | カテゴリー: コード

著者:飯尾淳

 本連載の第5 回(Vol.51、2017 年12 月号)で、バーティカルバー(垂直棒)が3 本立っているという理由から「ハノイの塔」というパズルを取り上げました。今回は、そのときの考察を思い出しつつ、ハノイの塔を解くプログラムを再び考えます。
 ただし、今回は三つの塔を「上から見下ろした」状態、俯瞰(ふかん)で考えます。至ってシンプルなルールで解ける面白さを、動作の可視化プログラムを用いて確認してみましょう。

シェルスクリプトマガジン Vol.60は以下のリンク先でご購入できます。

図3 sketch.jsを書き換えたコード 

const COLORS = [ 'crimson', 'forestgreen', 'yellow',
   'royalblue', 'saddlebrown', 'hotpink', 'darkorange',
   'darkmagenta' ];
const N_DISKS = COLORS.length;
const BASE_LENGTH = 200;
const C_WIDTH  = 3.732 * BASE_LENGTH;
const C_HEIGHT = 3.500 * BASE_LENGTH;
const DISK_R = 0.9 * BASE_LENGTH;
const POLE_R = 15;
const POSITIONS = { 'Source'      : [0.268, 0.714],
                    'Auxiliary'   : [0.500, 0.286],
                    'Destination' : [0.732, 0.714] };

class Position {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class Disk {
  constructor(level) {
    this.level = level;
    this.color = COLORS[level];
    this.r = (DISK_R-POLE_R)*(N_DISKS-level)
                / N_DISKS + POLE_R;
  }
}

class Tower {
  constructor(name, disks, direction=null) {
    this.name = name;
    this.disks = [];
    for (var i = 0; i < disks; i++) 
      { this.disks.push(new Disk(i)); }
    this.direction = direction;
    var rx, ry;
    [rx,ry] = POSITIONS[name];
    this.pos = new Position(rx*C_WIDTH, ry*C_HEIGHT);
  }
}

var src = new Tower('Source', N_DISKS);
var aux = new Tower('Auxiliary',   0, src);
var dst = new Tower('Destination', 0, src);
// 円盤の数(N_DISKS)が奇数のときはDestinationを、
// そうでないときはAuxiliaryを向くようにする
src.direction = (N_DISKS % 2 == 1) ? dst : aux;

function setup() {
  createCanvas(C_WIDTH, C_HEIGHT);
  frameRate(30);
}

function draw() {
  // put drawing code here
}

図5 追加した描画に関するコード部分

class Disk {
  constructor(level) {
(略)
  }

  draw(pos) {
    stroke('black');
    fill(this.color);
    ellipse(pos.x, pos.y, 2*this.r);
  }
}

class Tower {
  constructor(name, disks, direction=null) {
(略)
  }

  draw() {
    var pos  = this.pos;
    var pos2 = this.direction.pos;
    var sx, sy, dx, dy, r;

    // 円盤を描く
    this.disks.forEach(function(d) { d.draw(pos) })

    // 支柱を描く
    stroke('brown');
    fill('white');
    ellipse(pos.x, pos.y, 2*POLE_R);

    // 向きを描く
    stroke('navy');
    [sx, sy] = [pos.x,  pos.y ];
    [dx, dy] = [pos2.x, pos2.y];
    r = POLE_R / Math.sqrt((dx-sx)*(dx-sx)+(dy-sy)*(dy-sy));
    [dx, dy] = [(dx-sx)*r+sx, (dy-sy)*r+sy];
    line(sx, sy, dx, dy);
  }
}
(略)
function setup() {
  createCanvas(C_WIDTH, C_HEIGHT);
  frameRate(30);
}

function draw() {
  background('beige');
  [src, aux, dst].forEach(function(t) { t.draw(); })
}

シェルスクリプトマガジンvol.60 Web掲載記事まとめ

投稿日:2019.05.25 | カテゴリー: コード

004 レポート Windows Subsystem for Linux 2
005 レポート LibrePlanet 2019開催
006 NEWS FLASH
008 特集1 ラズパイで電子回路の作成と制御/麻生二郎 コード掲載
028 特集2 PHP超入門/柏岡秀男 コード掲載
038 特集3 ユニケージ開発手法入門/當仲寛哲 コード掲載
051 姐のNOGYO
052 特別企画 ツールチェーン/小薗井康志、川副博、古川正宏 コード掲載
074 ラズパイセンサーボードで学ぶ 電子回路の制御/米田聡 コード掲載
078 人間とコンピュータの可能性/大岩元
080 Webアプリケーションの正しい作り方/しょっさん コード掲載
088 close/桑原滝弥・イケヤシロウ
090 中小企業手作りIT化奮戦記/菅雄一
096 香川大学SLPからお届け!/山下賢治 コード掲載
101 「Visual Studio Code」を便利に使う/あかね コード掲載
106 円滑コミュニケーションが世界を救う!/濱口誠一
108 バーティカルバーの極意/飯尾淳 コード掲載
114 法林浩之のFIGHTING TALKS/法林浩之
116 機械学習のココロ/石井一夫 コード掲載
121 漢のUNIX/後藤大地
128 UNIXの歴史を振り返る/古寺雅弘
134 ユニケージ新コードレビュー/岡田健 コード掲載
136 Techパズル/gori.sh
140 コラム「令和はサバイバルの時代」/シェル魔人

特別企画 ツールチェーン(Vol.60掲載)

投稿日:2019.05.25 | カテゴリー: コード

 著者:小薗井康志、川副博、古川正宏

 「コーディング」「ビルド」「実装」「テスト」という一連のソフトウエア開発作業を、「ツールチェーン」を使って、効率良く、そして楽にしてみませんか。クラウドサービス「IBM Cloud」が提供するツールチェーンで、その便利さを味わってみましょう。

シェルスクリプトマガジン Vol.60は以下のリンク先でご購入できます。

図21 サンプルで用意されているテストスクリプト

#!/bin/bash
export PATH=/opt/IBM/node-v6.7.0/bin:$PATH
# Push app
export CF_APP_NAME="staging-$CF_APP"
cf push "${CF_APP_NAME}"
push_result=$?
export APP_URL=https://$(cf app $CF_APP_NAME | grep -e urls: -e routes: | awk '{print $2}')
# View logs
#cf logs "${CF_APP_NAME}" --recent
deploy_status="pass"
if [ $push_result -ne 0 ]; then
deploy_status="fail"
fi
npm install -g grunt-idra3
idra --publishdeployrecord --env=$LOGICAL_ENV_NAME --status=$deploy_status --appurl=$APP_URL

特集2 PHP超入門(Vol.60掲載)

投稿日:2019.05.25 | カテゴリー: コード

著者:柏岡秀男

 「PHP」(PHP: Hypertext Preprocessor)は、Webアプリケーションの開発によく使われているプログラミング言語です。本特集では、WindowsやmacOSにPHPプログラムの実行環境やWebサーバー、DBMSサーバーをインストールできる「MAMP」というソフトウエアを使って、PHPプログラミングを手軽に体験する方法を紹介します。これを機会にぜひPHPプログラミングを始めてみてください。

シェルスクリプトマガジン Vol.60は以下のリンク先でご購入できます。

図1 現在時刻を表示するWeb ページをPHPで作成した例

<!DOCTYPE html>
<head>
  <meta charset="UTF-8">
  <title> 現在時刻</title>
</head>
現在時刻は
<?php
  echo date("H:i:s")
?>
です

図10 helloworld.php

<!DOCTYPE html>
<head>
  <meta charset="UTF-8">
 <title>Hello world</title>
</head>
<?php echo "Hello, World!"; ?>

図11 表示したWeb ページのソースコード

<!DOCTYPE html>
<head>
  <meta charset="UTF-8">
  <title>Hello world</title>
</head>
Hello, World!

図12 sample1.php

<?php
  $a = 1;
  $b = 2;
  echo $a + $b;
?>

図13 以下のサンプルコードの冒頭に付加するコード

<!DOCTYPE html>
<head>
  <meta charset="UTF-8">
  <title><?php echo __FILE__; ?></title>
</head>

図14 sample2.php

<?php
  $a = " シェルスクリプト";
  $b = " マガジン";
  echo $a . $b;
?>

図15 sample3.php

<pre>
<?php
  $a = array(1,2,3,4,5);
  $b = array("a" => 1,"b" => "abc","c" => "123");
  $c[0] = 123;
  $c[1] = $b;
  var_dump($a);
  var_dump($b);
  var_dump($c);
  echo $a[1];
  echo "\n";
  echo $b["c"];
  echo "\n";
?>
</pre>

図17 if 文の基本構文

<?php
  $a = 1;
  if($a > 10) {
    echo "a は10 より大きい";
  }
?>
<?php
  $a = 1;
  if($a > 10) {
    echo "a は10 より大きい";
  } else {
    echo "a は10 以下";
  }
?>

図18 input.php

<form method="post" action="input.php">
  <input type=text name="a">
  <input type=submit>
</form>
<?php
  var_dump($_POST);
?>

図19 sample_input.php

<form method="post" action="sample_input.php">
  <input type=text name="a">
  <input type=submit>
</form>
<?php
  if($_POST["a"] > 10 ) echo "10 より大きい";
?>

図20 sample_input2.php

<form method="post" action="sample_input2.php">
  <input type=text name="a" >
  <input type=submit>
</form>
<?php
  if (isset($_POST["a"])) {
    if($_POST["a"] > 10 ) {
      echo "10 より大きい";
    } else {
      echo "10 以下";
    }
  } else {
    echo " 数字を入力してください";
  }
?>

図25 connect.php

<?php
  try {
    $dbh = new PDO(
      "mysql:host=localhost;dbname=sampledatabase;charset=utf8",
      "phpuser","testpassword"
    );
    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  } catch (Exception $e) {
    die(" データベース接続失敗 ".$e->getMessage());
  }
  echo " 接続できました";
?>

図26 input.html

<!DOCTYPE html>
<head>
  <title>TODO データの入力画面</title>
</head>
<form method="post" action="add.php">
  <input type=text name="todo">
  <input type=submit>
</form>
TODO データを入力してください

図27 add.php

<?php
  var_dump($_POST);
  try {
    $dbh = new PDO(
      "mysql:host=localhost;dbname=sampledatabase;charset=utf8",
      "phpuser","testpassword"
    );
    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $sql = "INSERT INTO sample (todo) VALUES (:todo_value)";
    $stmt = $dbh->prepare($sql);
    $todo = urldecode($_POST["todo"]);
    $stmt->bindValue(":todo_value", $todo, PDO::PARAM_STR);
    $stmt->execute();
    echo " データを登録しました";
  } catch (Exception $e) {
    die(" データベース接続失敗 ".$e->getMessage());
  }
?>

図28 list.php

<?php
  try {
    $dbh = new PDO(
      "mysql:host=localhost;dbname=sampledatabase;charset=utf8",
      "phpuser","testpassword"
    );
    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $sql = "SELECT * FROM sample WHERE status IS NULL";
    $res = $dbh->query($sql);
    foreach($res as $row) {
      echo htmlspecialchars($row["todo"], ENT_QUOTES);
      echo "<br />";
    }
  } catch (Exception $e) {
    die(" データベース接続失敗 ".$e->getMessage());
  }
?>

図30 list2.php

<?php
  try {
    $dbh = new PDO(
    "mysql:host=localhost;dbname=sampledatabase;charset=utf8",
    "phpuser","testpassword"
    );
    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $sql = "SELECT * FROM sample WHERE status IS NULL";
    $res = $dbh->query($sql);
    foreach($res as $row) {
      $id = htmlspecialchars($row["id"], ENT_QUOTES);
      echo htmlspecialchars($row["todo"], ENT_QUOTES);
    echo "<a href='update.php?id=" . $id . "'> 完了</a><br />";
    }
  } catch (Exception $e) {
    die(" データベース接続失敗 ".$e->getMessage());
  }
?>

図31 update.php

<?php
  try {
    $dbh = new PDO(
      "mysql:host=localhost;dbname=sampledatabase;charset=utf8",
      "phpuser","testpassword"
    );
    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $sql = "UPDATE sample SET status = 1 WHERE id = :id_value";

    $stmt = $dbh->prepare($sql);
    $id = (int) $_GET["id"];
    $stmt->bindValue(":id_value", $id, PDO::PARAM_INT);
    $stmt->execute();
    echo " データを更新しました";
  } catch (Exception $e) {
    die(" データベース接続失敗 ".$e->getMessage());
  }
?>

香川大学SLPからお届け!(Vol.60掲載)

投稿日:2019.05.25 | カテゴリー: コード

著者:山下賢治

 今回は、Webブラウザでアクセスできる予定共有アプリの作成方法を紹介します。Webアプリケーションフレームワークの「Ruby on Rails」と、JavaScriptのライブラリである「FullCalendar」を組み合わせることで、マウス操作で予定の追加や変更が可能な予定共有アプリを手軽に作成できます。

シェルスクリプトマガジン Vol.60は以下のリンク先でご購入できます。

図2 ルーティング設定

Rails.application.routes.draw do
  resources :calendar, only: [:index]
end

図3 アプリのViewを作成する手順

<div id="calendar"></div>
$(document).on 'turbolinks:load',->
  $('#calendar').fullCalendar({})
  return
$(document).on 'turbolinks:before-cache',->
  $('#calendar').empty()
  return
/*
  *= require_tree .
  *= require_self
  *= require fullcalendar
*/
//= require moment
//= require jquery
//= require fullcalendar
//= require fullcalendar/locale-all
//= require fullcalendar/lang/ja
$(function(){
  $('#calendar').fullCalendar({});
})

図5 ルーティング設定の変更

Rails.application.routes.draw do
  resources :calendar, only: [:index]
  resources :schedules, only: [:index, :create]
end

図6 登録データをJSON形式で取り出すコードを追加

class SchedulesController < ApplicationController
  def index
    schedules = Schedule.all
    respond_to do |format|
      format.json {
        ender json:
        schedules.to_json()
      }
    end
  end
end

図7 登録済みのデータを表示するコードを追加

//= require rails-ujs
//= require activestorage
//= require turbolinks
//= require_tree .
//= require moment
//= require jquery
//= require fullcalendar
//= require fullcalendar/locale-all
//= require fullcalendar/lang/ja
$(function(){
  $('#calendar').fullCalendar({
    events: '/schedules.json'
  });
})

図9 「app/assets/javascripts/application.js」のコード改造例

$('#calendar').fullCalendar({
  selectable: true,
  selectHelper: true,
  draggable: true,
  select: function(start, end) {
    var title = prompt("予定名");
    var scheduleData;
    if ( title ) {
      scheduleData = {
        title: title,
        start: start,
        end: end
      };
      $('#calendar').fullCalendar('renderEvent', scheduleData, true);
      createSchedule(scheduleData);
    }
    $('#calendar').fullCalendar('unselect')
  },
  events: '/schedules.json'
});
createSchedule = function(scheduleData) {
  $.ajax({
    type: 'POST',
    url: "/schedules",
    data: {
      title: scheduleData.title,
      start: String(scheduleData.start),
      end: String(scheduleData.end),
      authenticity_token: $("#authenticity_token").val()
    }
  }).done(function(data) {
    alert("登録しました");
  }).fail(function(data) {
    alert("登録失敗しました");
  });
};

図10 「app/views/calendar/index.html.erb」ファイルに追加する記述

<%= hidden_field_tag "authenticity_token", form_authenticity_token %>

図11 「app/controllers/schedules_controller.rb」ファイルに追加する記述

def create
   Schedule.create(
     title: params[:title],
     start: DateTime.parse(params[:start]),
     end: DateTime.parse(params[:end])
   )
 end

ユニケージ新コードレビュー(Vol.60掲載)

投稿日:2019.05.25 | カテゴリー: コード

著者:岡田健

 ユニケージでは、小さな道具の「コマンド」をシェルスクリプトで組み合わせて、さまざまな業務システムを構築しています。本連載では、毎回あるテーマに従ってユニケージによるシェルスクリプトの記述例を分かりやすく紹介します。第7回は、書き方のルールとなるお作法と分かりやすいコードの書き方について解説します。

シェルスクリプトマガジン Vol.60は以下のリンク先でご購入できます。

図1 売上計算するシェルスクリプト

cat SALES                       |
#  1:店舗 2:商品No 3:日付 4:売数
#  5:売上 6:割引
join1 key=2 PRICE               | # 原価 / 売価を連結
#  1:店舗 2:商品No 3:原価 4:売価
#  5:日付 6:売数   7:売上 8:割引
join1 key=2 CATEGORY            | # 部門を連結
#  1:店舗 2:商品No 3:部門 4:原価
#  5:売価 6:日付   7:売数 8:売上
#  9:割引
lcalc '$3,$7,$8,$8-$7*$4'       | # 売数 / 売上 / 荒利計算
#  1:部門 2:売数 3:売上 4:粗利
msort key=1                     | # 部門でソート
sm2 1 1 2 4                     | # 売数 / 売上 / 荒利集計
sm5 1 1 2 4                     | # 合計行の付加
divsen 2 3 4                    | # 千で除算
divsen 3 4                      | # 千で再除算
lcalc '$1,$2,$3,$4,100*$4/$3'   | # 荒利率を求める
#  1:部門   2:売数 3:売上 4:粗利
#  5:粗利率
marume 5.1                      | # 四捨五入
join2 key=1 CATEGORY_NAME       | # カテゴリ名の連結
#  1:部門 2:部門名 3:売数 4:売上
#  5:粗利 6:粗利率
comma 3 4 5                     | # カンマ編集
keta                            | # 桁そろえ
keisen +e                       | # 罫線を引く
cat header -                      # 出力

図2 awkによる長いコードが挿入されたシェルスクリプト

cat ZAIKO             |
#  1:商品CD     2:規格CD     3:拠点CD   4:サイズCD
#  5:産地CD     6:ブランドCD 7:仕入先CD 8:発注先CD
#  9:在庫発生日 10:製造日    11:販売日  12:数量
(略)

図3 在庫を扱うシェルスクリプトの先頭部分

cat ZAIKO             |
#  1:商品CD     2:規格CD     3:拠点CD   4:サイズCD
#  5:産地CD     6:ブランドCD 7:仕入先CD 8:発注先CD
#  9:在庫発生日 10:製造日    11:販売日  12:数量
cjoin2 key=1 SHOHIN - |
#  1:商品CD   2:商品名      3:規格CD     4:拠点CD
#  5:サイズCD 6:産地CD      7:ブランドCD 8:仕入先CD
#  9:発注先CD 10:在庫発生日 11:製造日    12:販売日
#  13:数量
cjoin2 key=3 KIKAKU - |
#  1:商品CD   2:商品名    3:規格CD      4:規格名
#  5:拠点CD   6:サイズCD  7:産地CD      8:ブランドCD
#  9:仕入先CD 10:発注先CD 11:在庫発生日 12:製造日
#  13:販売日  14:数量
(略)

図4 各種マスターを使って在庫に名称を挿入するシェルスクリプトの一部

cat ZAIKO             |
#  1:商品CD     2:規格CD     3:拠点CD   4:サイズCD
#  5:産地CD     6:ブランドCD 7:仕入先CD 8:発注先CD
#  9:在庫発生日 10:製造日    11:販売日  12:数量
cjoin2 key=1 SHOHIN - |
#  1:商品CD   2:商品名      3:規格CD     4:拠点CD
#  5:サイズCD 6:産地CD      7:ブランドCD 8:仕入先CD
#  9:発注先CD 10:在庫発生日 11:製造日    12:販売日
#  13:数量
cjoin2 key=3 KIKAKU - |
#  1:商品CD   2:商品名    3:規格CD      4:規格名
#  5:拠点CD   6:サイズCD  7:産地CD      8:ブランドCD
#  9:仕入先CD 10:発注先CD 11:在庫発生日 12:製造日
#  13:販売日  14:数量
(略)

図5 図4の修正版

cat ZAIKO              |
#  1:商品CD     2:規格CD     3:拠点CD   4:サイズCD
#  5:産地CD     6:ブランドCD 7:仕入先CD 8:発注先CD
#  9:在庫発生日 10:製造日    11:販売日  12:数量
self 0 1               |
cjoin2 key=NF SHOHIN - |
delf NF-1              |
#  1:商品CD     2:規格CD     3:拠点CD   4:サイズCD
#  5:産地CD     6:ブランドCD 7:仕入先CD 8:発注先CD
#  9:在庫発生日 10:製造日    11:販売日  12:数量
#  13:商品名
self 0 3               |
cjoin2 key=NF KIKAKU - |
delf NF-1              |
#  1:商品CD     2:規格CD     3:拠点CD   4:サイズCD
#  5:産地CD     6:ブランドCD 7:仕入先CD 8:発注先CD
#  9:在庫発生日 10:製造日    11:販売日  12:数量
#  13:商品名    14:規格名
(略)

特集3 ユニケージ開発手法入門(Vol.60掲載)

投稿日:2019.05.25 | カテゴリー: コード

著者:當仲寛哲

業務システムを開発するときの選択肢として「ユニケージ
開発手法」があります。ユニケージ開発手法は、Linux/UNIX
のコマンドを基盤としたものです。「シェル魔人」と「りな」
の会話を通して、ユニケージ開発手法で業務システムがど
う作られるのかを理解しましょう。

シェルスクリプトマガジン Vol.60は以下のリンク先でご購入できます。

図4 ユニケージ開発手法によるシェルスクリプトの記述例

#!/bin/bash -vx
#
# JIKANTAI.CGI 時間帯別販売情報参照システムのCGI スクリプト
(略)
# POST データの受け取り
if [ ! -z "$CONTENT_LENGTH" ] ; then
dd bs=$CONTENT_LENGTH |
cgi-name -n_ -s_ > $tmp-name
ERROR_CHECK
else
(略)
# はめ込み用日付情報の作成
echo $day $cday                            |
# 1: 日付 2: 比較日コード
join1 key=2 $tmp-cdaynum -                 |
# 1: 日付 2: 比較日コード 3: 比較日
delf 2                                     |
# 1: 日付 2: 比較日
dayslash --output "yyyy 年_mm 月_dd 日" 1 2 |
cat > $tmp-day
ERROR_CHECK
# 現在時刻
curtime="$(dayslash -d $todayhms --output 'yyyy 年 mm 月 dd 日 HH:MM:SS')"
##################################################
# HTML 作成
cat $apld/HTML/JIKANTAI.HTML               |
mojihame -lSHOP_RECORDS - $tmp-data        |
mojihame -lSYOHIN_INFO - $tmp-info         |
mojihame -lSEARCH_DATE - $tmp-day          |
formhame -s_ -n_ - $tmp-name               |
calsed '###CURRENT_TIME###' "$curtime"     |
calsed '###USER###' "$USER"                |
cat > $tmp-html

図19 セールスレポートを作成するシェルスクリプト(demo)

#!/bin/bash -x
join1 key=2 PRICE SALES       | # 原価/ 売価を連結
join1 key=2 CATEGORY          | # カテゴリを連結
lcalc '$3,$7,$8,$8-$7*$4'     | # 売数/ 売上/ 荒利計算
msort -p4 key=1               | # カテゴリでソート
sm2 1 1 2 4                   | # 売数/ 売上/ 荒利集計
sm5 1 1 2 4                   | # 合計行の付加
divsen 2 3 4                  | # 千で除算
divsen 3 4                    | # 千で再除算
lcalc '$1,$2,$3,$4,100*$4/$3' | # 荒利率を求める
marume 5.1                    | # 四捨五入
join2 key=1 CATEGORY_NAME     | # カテゴリ名の連結
comma 3 4 5                   | # カンマ編集
keta                          | # 桁そろえ
keisen +e                     | # 罫線を引く
cat header -                    # 出力
exit 0

図21 コメントを入れた例

#!/bin/bash
# 商品コードに、原価/売価を連結
join1 key=2 PRICE SALES       |
# 商品コードにカテゴリーコードを連結
join1 key=2 CATEGORY          |
# [ レイアウト ]
# 1:店コード 2:商品コード 3:カテゴリコード 4:仕入価格 5:販売価格
# 6:販売日付 7:販売個数 8:販売金額 9:値引金額
# カテゴリコード、売数、売上、粗利(売上-売数x仕入価格)を計算
lcalc '$3,$7,$8,$8-$7*$4'     |
# [ レイアウト ]
# 1:カテゴリコード 2:販売個数 3:販売金額 3:粗利金額
msort -p4 key=1               | # カテゴリでソート
sm2 1 1 2 4                   | # 売数 / 売上 / 粗利集計
sm5 1 1 2 4                   | # 合計行の付加
divsen 2 3 4                  | # 千で除算
divsen 3 4                    | # 千で再除算 (売上・粗利は百万円単位)
# 粗利率を求める
lcalc '$1,$2,$3,$4,100*$4/$3' |
# [ レイアウト ]
# 1:カテゴリコード 2:販売個数 3:販売金額 4:粗利金額 5:粗利率
# 粗利率を四捨五入
marume 5.1                    |
# カテゴリ名の連結
join2 key=1 CATEGORY_NAME     |
# [ レイアウト ]
# 1:カテゴリコード 2:カテゴリ名 3:販売個数 4:販売金額 5:粗利金額
# 6:粗利率
comma 3 4 5                   | # カンマ編集
keta                          | # 桁そろえ
keisen +e                     | # 罫線を引く
cat header -                    # 出力
exit 0

図25 最新のレベル4を出力する

echo LV1.101.*      |
xargs cat           |
msort key=1@NF      |
upl key=1@NF LV4 -

Vol.60 補足情報

投稿日:2019.05.25 | カテゴリー: コード

目次

連載コラム「香川大学SLPからお届け!」の回のタイトルに誤りがありました。「バーコードリーダーを使って手軽なデータ収集システム」ではなく、「Ruby on RailsとFullCalendarで予定共有アプリを作る」です。 お詫びして訂正いたします 。

読者プレゼント

右上のQRコードが誤っていました。正しいQRコードは以下です。 お詫びして訂正いたします 。

姐のNOGYO

左上の別掲記事の「1998年」は「1989年」の誤りです。 お詫びして訂正いたします 。

情報は随時更新致します。

センサーボードで学ぶ電子回路の制御(Vol.60掲載)

投稿日:2019.05.25 | カテゴリー: コード

著者:米田聡

 シェルスクリプトマガジンでは、小型コンピュータボード「Raspberry Pi」(ラズパイ)向けのセンサー搭載拡張ボード「ラズパイセンサーボード」を制作しました。第7 回では、I2C のインタフェースに接続するI/Oエキスパンダ「MCP23017」でGPIO 端子を増やす方法を紹介します。

シェルスクリプトマガジン Vol.60は以下のリンク先でご購入できます。

図9 MCP23017のGPIOを読み書きするためのPythonライブラリ(mcpgpio.py)

from smbus import SMBus

class MCPGPIO():
  __IODIR  = [0x00, 0x01] # レジスタ番号
  __GPPU   = [0x0C, 0x0D]
  __GPIO   = [0x12, 0x13]
  __OLAT   = [0x14, 0x15]

  INPUT       = 1
  OUTPUT      = 0
  INPUTPULLUP = 3

  HIGH        = 1
  LOW         = 0

  def __init__(self,address = 0x20):
    self.bus = SMBus(1)
    self.addr = address

  def setup(self, pin, dir):
    if pin < 16:
      dir = self.bus.read_byte_data(self.addr,self.__IODIR[int(pin/8)])
      dir &= ~(0x01 << int(pin % 8))
      dir |= (dir & 1) << int(pin % 8)
      self.bus.write_byte_data(self.addr, self.__IODIR[int(pin/8)], dir)
      if (dir & 1) ==  1:
        pu  = self.bus.read_byte_data(self.addr,self.__GPPU[int(pin/8)])
        pu &= ~(0x01 << int(pin % 8))
        pu |= ((dir >> 1) & 1) << int(pin % 8)
        self.bus.write_byte_data(self.addr, self.__GPPU[int(pin/8)], pu)
    
  def input(self, pin):
    r = 0
    if pin < 16:
      gp = self.bus.read_byte_data(self.addr, self.__GPIO[int(pin/8)])
      r = (gp >> int(pin%8) & 1)
    return r

  def output(self, pin, val):
    if pin < 16:
      gp = self.bus.read_byte_data(self.addr, self.__GPIO[int(pin/8)])
      gp &= ~(0x01 << int(pin % 8))
      gp |= (val & 1) << int(pin % 8)
      self.bus.write_byte_data(self.addr, self.__GPIO[int(pin/8)], gp)

  @property
  def gpioa(self):
    return  self.bus.read_byte_data(self.addr, self.__GPIO[0])
    
  @gpioa.setter
  def gpioa(self, value):
    self.bus.write_byte_data(self.addr,self.__GPIO[0], value)
    
  @property
  def gpiob(self):
    return  self.bus.read_byte_data(self.addr, self.__GPIO[1])

  @gpiob.setter
  def gpiob(self, value):
    self.bus.write_byte_data(self.addr,self.__GPIO[1], value)

機械学習のココロ(Vol.60掲載)

投稿日:2019.05.25 | カテゴリー: コード

著者:石井一夫

 今回は、機械学習を実施する際に一番問題となる過学習の問題を取り上げます。過学習というのは、機械 学習のモデルが、ある特定の状況に過剰適合してしまい、新たなサンプルに対してうまく予測ができなくなるという現象です。

シェルスクリプトマガジン Vol.60は以下のリンク先でご購入できます。

図5 L1 正則化を実施するコード

l1_model = keras.models.Sequential([
    keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l1(0.001),
                    activation=tf.nn.relu, input_shape=(NUM_WORDS,)),
    keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l1(0.001),
                    activation=tf.nn.relu),
    keras.layers.Dense(1, activation=tf.nn.sigmoid)
])
l1_model.compile(optimizer='adam',
                loss='binary_crossentropy',
                metrics=['accuracy', 'binary_crossentropy'])
l1_model_history = l1_model.fit(train_data, train_labels,
                               epochs=20,
                               batch_size=512,
                               validation_data=(test_data, test_labels),
                               verbose=2)

図6 過学習の評価結果を表示するコード

plot_history([('baseline', baseline_history),
               ('l1', l1_model_history)])

第4回 ファイルサーバーを作る

投稿日:2019.05.21 | カテゴリー: 記事

 インターネット上にあるサーバー以外で、最も利用されているのが「ファイルサーバー」です。「NAS」(Netowork Attached Storage)や「ネットワーク対応HDD」「ネットワークハードディスク」などとも呼ばれています。このファイルサーバーですが、Linuxパソコンがあれば比較的簡単に構築できます。
 そこで今回は、Linux上にファイルサーバーを構築するシェルスクリプトを作成しましょう(図1)。

図1 シェルスクリプトでファイルサーバーを構築

第3回 写真を整理する

投稿日:2019.05.7 | カテゴリー: 記事

 スマートフォンならいつでもどこでも気軽に写真を撮影できます。ただ、その気軽に撮った写真データのせいでスマートフォンのストレージ容量の空き容量が少なくなることがあります。また、新しいスマートフォンを買い換えるときは、多量にある写真データを移し変えることは困難です。これらの場合、写真データをパソコンなどにバックアップする人も多いでしょう。バックアップの際、ファイル名を書き換えることができると便利です。

 そこで今回は、パソコンに保存した写真データのファイル名を書き換えるシェルスクリプトを作成します。

第2回 コメント行を削除する

投稿日:2019.04.16 | カテゴリー: 記事

 LinuxやUnix系OSの設定ファイルを見てみると、「#」で始まる行が説明が書かれた「コメント」になります。ドキュメントを見なくても設定が記述できるように、複数行にわたったコメントを使って詳細に説明が書かれていることもあります。
 コメントはとても便利です。しかし、コメントとコメントの間にデフォルト(初期状態)の設定が書き込まれている場合、コメントが邪魔になることもあります。そこで、今回はデフォルトの設定をすぐに確認するための、コメント行を取り除くシェルスクリプトを作成します。

仕様やロジックを考える

 LinuxやUnix系OSの設定ファイルの多くは「/etc」ディレクトリの下に保存されています。いくつかの設定ファイルを開いてみると、前述したように先頭に「#」が付いた行がコメントになっています。よって、今回のシェルスクリプトでは、行頭が「#」を見つけて、その行を削除すればよいわけです。
 また、設定ファイルによっては空白行も残さない方が見やすい場合もあります。そこで、空白行を残す場合と残さない場合の両方の処理ができるようにします。

第1回 Amazonの商品ページURLをきれいにする

投稿日:2019.04.2 | カテゴリー: 記事

 シェルスクリプトなどのプログラムを作成する上で、とても重要なのは「ロジック」を考えることです。簡潔で最適なロジックを思いつくのは重要ですが、シェルスクリプトの場合は、いろいろ悩まずにLinux/Unix系OSのコマンドリファレンスなどを見ながらロジックが浮かんだら、すぐに書いてみることです。
 自分が使えればよいので、知っているコマンドを組み合わせるだけで構いません。無駄が多かったり、不思議な処理になっていたりしても正しく動けば問題ありません。とりあえず、完成させることが重要です。
 本連載で紹介するシェルスクリプトの例もその前提で記述しています。後からもっと簡単に書ける方法が思いついてもそのままにしています。

連載 シェルスクリプトを書いてみよう

投稿日:2019.04.2 | カテゴリー: コード
イラスト:ヨーダヒデキ

 シェルスクリプトは、Linux/Unix系OSのコマンドのみで記述できるプログラムです。さまざまな処理を簡単に記述でき、LinuxやUnix系OSの環境があれば、すぐに実行して試せるのでとても便利です。本連載では、役立ちそうなシェルスクリプトを紹介しながら、シェルスクリプトの書き方を説明していきます(隔週更新予定)。
 なお、シェルスクリプトを開発・実行するには、Linux/Unix系OSがインストールされたパソコンが必要です。例えば、「連載 UbuntuではじめるLinuxサーバー」の第1回第4回で紹介した方法でUbuntu Serverをインストールしたパソコンを用意できます。

記事中で紹介したシェルスクリプトのコードは、以下のページから入手できます(WordPressのバグなのか、何らかのパターンで「$」などの文字が消えてしまうことがありますのでシェルスクリプト自体はこちらから入手してください)。
https://github.com/shellscript-magazine/rensai_shellscript1

目次
第1回 Amazonの商品ページURLをきれいにする
第2回 コメント行を削除する
第3回 写真を整理する
第4回 ファイルサーバーを構築する
第5回 文書をPDFファイルに変換する
第6回 DMを自動送信する(SMTPS編)
第7回 DMを自動送信する(STARTTLS編)
第8回 自動でアーカイビングする
第9回 グローバルIPアドレスを通知する
第10回 写真から場所を調べる
第11回 大切なメールだけをチャットルームに送る
第12回 コマンドを作る
最終回 不正アクセスを通知する

シェルスクリプトマガジンvol.59 Web掲載記事まとめ

投稿日:2019.03.25 | カテゴリー: コード

004 レポート Homebrew 2.0.0リリース
005 レポート runcの脆弱性問題
006 NEWS FLASH
008 特集1 Linuxパーフェクトカスタマイズ/麻生二郎 コード掲載
020 特集2 AWS Lambdaを好きな言語で使おう/岡本秀高 コード掲載
028 特別企画 リレー式コンピュータプログラミング/若菜魁、入口雄也、八木武尊
036 ラズパイセンサーボードで学ぶ 電子回路の制御/米田聡
040 試み/桑原滝弥・イケヤシロウ
042 バーティカルバーの極意/飯尾淳
046 円滑コミュニケーションが世界を救う!/濱口誠一
048 中小企業手作りIT化奮戦記/菅雄一
056 法林浩之のFIGHTING TALKS/法林浩之
058 漢のUNIX/後藤大地
064 人間とコンピュータの可能性/大岩元
066 香川大学SLPからお届け!/竹原一駿 コード掲載
071 姐のNOGYO
072 UNIXの歴史を振り返る/古寺雅弘
078 Node.js/Expressで楽々Webアプリ開発/しょっさん コード掲載
089 ユニケージ新コードレビュー/坂東勝也 コード掲載
096 Techパズル/gori.sh
098 コラム「ユニケージお作法のココロ」/シェル魔人

Vol.59

投稿日:2019.03.25 | カテゴリー: バックナンバー

 オープンソースのソフトウエアを組み合わせて、好みのOSが作れるのが「Linux」です。特に、好みに近いLinuxディストリビューションがあれば、それをカスタマイズするだけで自分だけのLinuxが作れます。特集1では、軽量で高速なLinuxディストリビューション「Lubuntu」をインストールメディアからカスタマイズして自分好みに仕立てる方法を紹介しています。
 特集2では、AWSのサーバーレスサービス「AWS Lambda」に追加された新機能「AWS Lambda Custom Runtimes」を扱っています。AWS Lambda Custom Runtimesが登場する以前は、サービスとして実行される「Lambda」関数は、Node.jsのJavaScriptやPython、Rubyで記述する方法しかありませんでした。AWS Lambda Custom Runtimesの登場により、言語環境となるランタイムを用意すれば、その言語でLambda関数を記述できるようになりました。
 特別企画では、1960年代に制作された国産リレー式コンピュータである「FACOM138A」のプログラミングに挑戦した大学生の奮闘記を紹介しています。
 このほか、「センサーボードで学ぶ 電子回路の制御」「姐のNOGYO」などの人気連載も掲載しています。

今回も読み応え十分のシェルスクリプトマガジン Vol.59。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

特集2 AWS Lambdaを好きな言語で使おう(Vol.59掲載)

投稿日:2019.03.25 | カテゴリー: コード

著者:岡本秀高

カンファレンス「AWS re:invent 2018」にて発表された新サービス「AWS Lambda Custom Runtimes」。これまではサポートをアナウンスした言語でしか「Lambda関数」を作成できませんでした。このサービスの登場により好きな言語で作成可能となりました。本特集では、AWS Lambda Custom Runtimesの使い方から、ランタイム(実行環境)を実際に作って利用するところまでを分かりやすく紹介します。

シェルスクリプトマガジン Vol.59は以下のリンク先でご購入できます。

図3 bootstrapファイルの内容

#!/bin/sh

set -euo pipefail

# Initialization - load function handler
source $LAMBDA_TASK_ROOT/"$(echo $_HANDLER | cut -d. -f1).sh"

# Processing
while true
do
  HEADERS="$(mktemp)"
  # Get an event
  EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
  REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)

  # Execute the handler function from the script
  RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA")

  # Send the response
  curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response"  -d "$RESPONSE"
done

図5 function.shファイルの内容

#!/bin/sh
function handler () {
  EVENT_DATA=$1
  echo "$EVENT_DATA" 1>&2;
  RESPONSE="Request: '$EVENT_DATA'"

  echo $RESPONSE
}

図6 template.yamlファイルの内容

AWSTemplateFormatVersion: 2010-09-09
Description: My PHP Application
Transform: AWS::Serverless-2016-10-31
Resources:
  myRuntime:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub ${AWS::StackName}-myRuntime
      Description: My fisrt custom runtime
      Runtime: provided
      Handler: function.handler
      MemorySize: 3008
      Timeout: 30
      Layers:
        - !Sub 作成したランタイムのLayerVersionArn

ユニケージ新コードレビュー(Vol.59掲載)

投稿日:2019.03.25 | カテゴリー: コード

著者:坂東勝也

ユニケージでは、小さな道具の「コマンド」をシェルスクリプトで組み合わせて、さまざまな業務システムを構築しています。本連載では、毎回あるテーマに従ってユニケージによるシェルスクリプトの記述例を分かりやすく紹介します。第6回は、仕入伝票処理システムを例に最新マスターを取得するコードをレビューします。

シェルスクリプトマガジン Vol.59は以下のリンク先でご購入できます。

図11 伝票番号で並べ替え

145 # L3取得
146 if [ -s $tmp-check_data.1 ] ; then
147 	cat $lv3d/SIRE_HEAD/SIRE_HEAD.$ymonth    |
148 	# 同じ伝票番号の情報を古い順にソート(最も新しいデータが一番下に来るように)
149 	msort key=1@NF@NF-1r                     > $tmp-sire_header_l3
150 	ERROR_CHECK
151 else
152 	:> $tmp-sire_header_l3
153 	ERROR_CHECK
154 fi

図12 更新された仕入伝票を調べる

130 # 仕入伝票ヘッダーのLV1データの取得
131 # LV1データがあるかチェック
132 ls $lv1d/$today/SIRE_HEAD_* |
133 gyo                         > $tmp-check_data.lv1
134 ERROR_CHECK

図13 指定月の仕入伝票の取得

156 # INPUT(L1)取得
157 if [ -s $tmp-check_data.2 ] ; then
158 	cat $lv1d/SIRE_HEAD_*                               |
159 	#dayslash --input yyyy/mm/dd --output yyyymmdd 2 -  |
160 	# 指定の日付の範囲内のデータを抽出
161 	uawk '$2>='$range_from' && $2<='$range_to''         |
162 	# 同じ伝票番号の情報を古い順にソート(最も新しいデータが一番下に来るように)
163 	msort key=1@NF@NF-1r                                > $tmp-sire_header_in
164 	ERROR_CHECK
165 else
166 	:> $tmp-sire-header_in
167 	ERROR_CHECK
168 fi

図14 L1とL3のデータをマージ

170 # L1とL3をマージ
171 # 同じ伝票番号の中で最も新しいもののみを残す
172 upl key=1 $tmp-sire_header_l3 $tmp-sire_header_in  |
173 # 削除フラグの立っている伝票を削除
174 delr NF-1 1                                        |
(略)
186 ERROR_CHECK

Vol.59 補足情報

投稿日:2019.03.25 | カテゴリー: コード

訂正・補足情報はありません。

情報は随時更新致します。

香川大学SLPからお届け!(Vol.59掲載)

投稿日:2019.03.25 | カテゴリー: コード

著者:竹原一駿

USB接続のバーコードリーダーを用いてバーコード化されたデータを読み取り、その結果をサーバーに送って集計するデータ収集システムを開発しました。今回は、同システムについて紹介します。クライアントはGo、集計サーバーは主にPerlで記述しています。クライアントもサーバーもDocker環境で簡単に動かせますので、ぜひ試してみてください。

シェルスクリプトマガジン Vol.59は以下のリンク先でご購入できます。

図8 バーコードリーダーで読み取ったデータを取得する関数

func ScanStdin() string {
  stdin := bufio.NewScanner(os.Stdin)
  stdin.Scan()
  return stdin.Text()
}

図9 意見を示す数値を文字列に変換するコード

const AGREE = "100" // 賛成
 const OPPOSITE = "200" // 反対
 const NEUTRAL = "300" // 中立

 if sData == AGREE {
   sData = "agree" // 意見の文字列を代入
 } else if sData == OPPOSITE {
   sData = "opposite"
 } else if sData == NEUTRAL {
   sData = "neutral"
 }

図10 データを集計サーバーに送るためのコード

resp, err := http.Get(baseurl)
if err != nil {
  fmt.Println(err)
  return err
}
defer resp.Body.Close()

図11 HTTPサーバー機能を提供するMyWebServerパッケージの記述

#!/usr/bin/perl
{
  package MyWebServer;
  use HTTP::Server::Simple::CGI;
  use base qw(HTTP::Server::Simple::CGI); #継承
  use strict;
  use warnings;
  # 以降の処理はここに書く.
}
my $pid = MyWebServer->new(80)->run();

図12 CGIで日本語を表示するための記述

print $cgi->header(-charset=>"utf-8"),
      $cgi->start_html(-lang => 'ja','Not found'),
      $cgi->h1('Not found'),
      $cgi->end_html;

図13 ハッシュ変数にサブルーチンのリファレンスを保存

my %dispatch = (
  '/index' => \&resp_index, # index
  '/insert' => \&resp_insert, # データの挿入
  '/operate' => \&resp_operate, # 質問番号の調整
  '/analy' => \&resp_analy, # 解析
);

図14 insertページを表示するコード

sub resp_insert {
  my $cgi = shift;
  return if !ref $cgi;

  my $user = $cgi->param('user'); # ユーザーID
  my $opi = $cgi->param('opi'); # 意見
  my $dbh = DBI->connect(
            "dbi:mysql:database=opinidb;".
            "host=mysql;port=3306",
            'user','password'
            ); #データベースの接続
  my $sth = $dbh->prepare(
            "INSERT INTO
            opinidb.opinion(USER,NUM,OPI)".
            " VALUES (?,?,?);"
            );
  $sth->execute($user,$ques_num,$opi); # SQL生成,実行
  $sth->finish;
  $dbh->disconnect; #接続終了
  print $cgi->header(-charset=>"utf-8"),
        $cgi->start_html("insert"),
        $cgi->h1("Question number is $ques_num"),
        "ユーザ:$user, 意見$opi",
        $cgi->end_html;
  }

図15 analyページを表示するコード

my @opilist = ('agree','opposite','neutral');

print $cgi->header(-charset=>"utf-8");
print $cgi->start_html(-lang => 'ja',"analy");
foreach(@opilist){
  print $cgi->h2("count : ".$_);
  my $sth = $dbh->prepare(
              "SELECT NUM AS ques_num , ".
              "COUNT(*) AS countopi ".
              "FROM opinidb.opinion ".
              "WHERE OPI LIKE '\%".$_."\%' ".
              "GROUP BY ques_num"
            );
  $sth->execute(); # SQL生成,実行
  print '<table border=1>';
  print '<tr><th>質問番号</th><th>選んだ人数</th></tr>';
  while(my $datahash = $sth->fetchrow_hashref){
    print '<tr><td>'.
    $datahash->{'ques_num'}.'</td><td>'.
    $datahash->{'countopi'}.'</td></tr>';
  }
  print '</table>';
  $sth->finish;
}

特集1 Linuxパーフェクトカスタマイズ(Vol.59掲載)

投稿日:2019.03.25 | カテゴリー: コード

著者:麻生二郎

Linuxには、「ディストリビューション」と呼ばれるさまざまな種類があります。しかし、その中から自分の好みに合ったものを見つけるのは意外と困難です。そこで、軽量なLinuxディストリビューション「Lubuntu」のインストールメディアをカスタマイズして、自分好みのLinuxディストリビューションを手に入れましょう。

シェルスクリプトマガジン Vol.59は以下のリンク先でご購入できます。

図27 スライドショーの設定を書き換える箇所

    Slide {
        Image {
            anchors.centerIn: parent
            id: image1
            x:0
            y:0
            width: 810
            height: 485
            fillMode: Image.PreserveAspectFit
            smooth: true
            source: "shmag1.png"
        }
    }
    Slide {
        Image {
            anchors.centerIn: parent
            id: image2
            x: 0
            y: 0
            width: 810
            height: 485
            fillMode: Image.PreserveAspectFit
            smooth: true
            source: "shmag2.png"
        }
    }
}

Node.js/Expressで楽々Webアプリ開発(Vol.59掲載)

投稿日:2019.03.25 | カテゴリー: コード

著者:しょっさん

プログラミング言語「JavaScript」の実行環境「Node.js」と「Express」フレームワークを使って、基本となるWebアプリの開発手法を習得しましょう。最終回は、サンプルの「蔵書管理アプリケーション」の課題を解決し、「SPA」(Single Page Application)として実装します。

シェルスクリプトマガジン Vol.59は以下のリンク先でご購入できます。

図2 認証時にアクセストークンを発行する部分のプログラム

var express = require('express');
var router = express.Router();
const jwt = require('jsonwebtoken');
const models = require('../models'),
  User = models.user;

var app = express();

// パスワードハッシュ化
const hashPassword = (password, salt) => {
  if (password) {
    var bcrypt = require('bcrypt');
    return bcrypt.hashSync(password, salt);
  } else { return null; }
};

// ログイン処理
router.post('/', (req, res) => {
  const username = req.body.email;
  const password = req.body.password;
  User.findOne({ where: { email: username } })
    .then(user => {
      if (!user) {
        res
          .status(401)
          .json({ errors: { message: 'Incorrect email.' } });
      } else if (hashPassword(password, user.salt) !== user.password) {
        res
          .status(401)
          .json({ errors: { message: 'Incorrect password.' } });
      } else {
        const opts = {
          issuer: process.env.ISSUER,
          audience: process.env.AUDIENCE,
          expiresIn: process.env.EXPIRES,
        };
        const secret = process.env.SECRET;
        res
          .status(200)
          .json({ 'token': jwt.sign({ id: user.id }, secret, opts) });
      }
    });
});

図3 URLへのアクセスしたときのアクセストークンの検査部分のプログラム

const passport = require('passport');
const passportJWT = require('passport-jwt');
const ExtractJWT = passportJWT.ExtractJwt;
const JWTStrategy = passportJWT.Strategy;

// using authentication strategy
passport.use(new JWTStrategy(
  {
    jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(),
    issuer: process.env.ISSUER,
    audience: process.env.AUDIENCE,
    secretOrKey: process.env.SECRET
  }, (jwt_payload, done) => {
    User.findOne({ where: { id: jwt_payload.id } })
      .then(user => {
        if (user) {
          done(null, user);
        } else {
          done(null, false);
        }
      })
      .catch(err => {
        return done(err, false);
      });
  }
));

図4 カスタムコールバックを作成している部分のプログラム

// passport-jwt: custom callback
// Tokenを持っていなかったり、Invarid Tokenの場合、Json形式で返答しなかったりするので、カスタムコールバックで準備する必要がある
const jwt = (req, res, next) => {
  passport.authenticate('jwt', { session: false }, (err, user, info) => {
    if (err) { return next(err); }
    if (!user) {
      return res.status(401).json({ 'errors': { 'message': info || 'user unknown' } }).end();
    }
    req.user = user;
    next();
  })(req, res, next);
};

// ルーティング: 認証が必要なURIには、jwt関数コールを追記するだけでよい
app.use('/api/auth', auth);
app.use('/api/books', jwt, books);

図5 表現に合わせてJSON形式で返却する

  // 1冊の本の情報を取得する
  _get_book(book_id) {
    return libraries.findOne({
      where: {
        user_id: this._user_id,
        id: book_id
      },
      include: [{
        model: comments,
        required: false
      }]
    });
  }

  // 1冊の本の詳細を表示する
  find(req, res) {
    this._get_book(req.params.id)
      .then(result => {
        res
          .status(200)
          .json(result.get());
      }).catch(() => {
        res
          .status(200)
          .json({});
      });
  }

図6 エラー画面をJSON形式に変更する

// catch 404 and forward to error handler
app.use((req, res, next) => {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handler
app.use((err, req, res, next) => {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.json({
    success: false,
    errors: {
      message: err.message
    }
  });
});

図7 テストコードを変更する

describe('with Login', () => {
  // store a jwt token
  let jwt_token;

  beforeAll((done) => {
    request(app)
      .post('/api/auth/')
      .send(userCredentials)
      .end((err, res) => {
        jwt_token = res.body.token;
        done();
      });
  });

  describe('GET /api/books/', () => {
    it('respond with REST', (done) => {
      request(app)
        .get('/api/books/')
        .set('Accept', 'application/json')
        .set('Authorization', Bearer ${jwt_token})
        .expect('Content-Type', /json/)
        .expect(200, done);
    });
  });
});

最終回 大切なデータを守る(ディレクトリーの暗号化)

投稿日:2019.02.5 | カテゴリー: 記事

 社内に設置したサーバーでは、ストレージやマシン自体の盗難に遭うこともあります。大切なデータを保存しているのなら、保存先のディレクトリーごと暗号化しておくのがお勧めです (図1) 。

図1 暗号化していれば中身が見られない

Vol.58

投稿日:2019.01.25 | カテゴリー: バックナンバー

 インターネット上のサイトで欠かせないサーバーソフトウエアが、データを格納・管理する「データベース管理システム」です。特集1では、オープンソースのデータベース管理システムとして人気が高い「MySQL」を紹介します。入手方法、Linuxディストリビューション「CentOS」へのインストール方法だけでなく、データベースの核となる「ストレージエンジン」、運用管理やトラブルシューティングなどで重要な「ログ」、データベース処理で知っておきたい「トランザクション」や「ロック」に触れます。
 特集2では、小型コンピュータボード「Raspberry Pi」上で「IchigoJam BASIC RPi」による「BASIC」プログラミングを紹介します。BASICは、古くからある言語で、プログラミングの基本が学べます。子供から大人まで楽しめますので、親子で一緒にプログラミングを始めましょう。
 特別企画では、サイボウズの業務改善プラットフォーム「kintone」上で実用的な業務システムの「交通費申請システム」を作成します。入力フォームにテキストボックスやボタンなどの部品を配置し、簡単なワークフローを設定するだけなので、プログラムを一切書く必要はありません。無料のアカウントを登録すれば、すぐに試せます。
 このほか、「中小企業手作りIT化奮戦記」「センサーボードで学ぶ 電子回路の制御」などの人気連載も掲載しています。
 今月も読み応え十分のシェルスクリプトマガジン Vol.58。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

香川大学SLPからお届け!(Vol.58掲載)

投稿日:2019.01.25 | カテゴリー: コード

著者:竹原 一駿
2018年秋に広島と香川で「オープンソースカンファレンス」(OSC)が開催されました。SLPは、両OSCでブースを出展し、OSC香川では3人の学生がライトニングトーク(LT)を行いました。今回は、これらの活動の内容や、それによって得られた体験などについて報告します。OSCへの一般参加や出展などを考えている読者の方の参考になれば幸いです。

シェルスクリプトマガジン Vol.58は以下のリンク先でご購入できます。

図2 自作コンテナエンジンのソースコードの一部
unshareシステムコールによるLinux Namespaceの分離
unshare(
  CloneFlags::CLONE_NEWPID
    | CloneFlags::CLONE_NEWIPC
    | CloneFlags::CLONE_NEWUTS
    | CloneFlags::CLONE_NEWNS
    | CloneFlags::CLONE_NEWUSER,)
fork/execシステムコールによるコンテナ内子プロセス生成
match fork() {
  Ok(ForkResult::Parent { child, .. }) => {
    match waitpid(child, None).expect("waitpid faild") {
      WaitStatus::Exited(_, _) => {}
      WaitStatus::Signaled(_, _, _) => {}
      _ => eprintln!("Unexpected exit."),
    }
  }
  Ok(ForkResult::Child) => {
    sethostname(&self.name).expect("Could not set hostname");
    fs::create_dir_all("proc").unwrap_or_else(|why| {
      eprintln!("{:?}", why.kind());
    });
    println!("Mount procfs ... ");
    mounts::mount_proc().expect("mount procfs failed.");
    let cmd = CString::new(self.command.clone()).unwrap();
    let default_shell = CString::new("/bin/bash").unwrap();
    let shell_opt = CString::new("-c").unwrap();
    execv(&default_shell, &[default_shell.clone(), shell_opt, cmd]).expect("execution faild.");
  }
  Err(_) => eprintln!("Fork failed"),
}
図5 「数字に隠された真実」のソースコードの一部
数値を1桁ずつ分解して、すべての和を求める関数
function add_1(number, result_text, c) {
  var num = [];
  if (parseInt(number / 10) == 0 && (number != 6 || number != 9)) { return 0; } // 1桁になって悪魔の数字じゃなければ失敗
  num = num_split_onedigit(number); // 数値を1桁ずつ分解
  sum = num_add_onedigit(num); // すべて加算
  result_text[c] = array_value_text(num, sum, "+"); // 計算過程の文字列を作成
  var check = akumanumber_check(sum, result_text); // 悪魔の数字かどうかチェック
  if (check != 0) { return check; } // 悪魔の数字なら終了
  var result = add_1(sum, result_text, c + 1); // 処理1を実行(再帰)
  if (result != 0) { return result; } else { result_t  ext.splice(c, 1); }
  var result = mult_1(sum, result_text, c + 1); // 処理2を実行(相互再帰)
  if (result != 0) { return result; } else { result_text.splice(c, 1); }
  return 0;
}
数値を1桁ずつ分解して、すべての積を求める関数
function mult_1(number, result_text, c) {
  var num = [];
  if (parseInt(number / 10) == 0 && (number != 6 || number != 9)) { return 0; } // 1桁になって悪魔の数字じゃなければ失敗
  num = num_split_onedigit(number); // 数値を1桁ずつ分解
  sum = num_mult_onedigit(num); // すべて乗算
  result_text[c] = array_value_text(num, sum, "×"); // 計算過程の文字列を作成
  var check = akumanumber_check(sum, result_text); // 悪魔の数字かどうかチェック
  if (check != 0) { return check; } // 悪魔の数字なら終了
  var result = add_1(sum, result_text, c + 1); // 処理1を実行(相互再帰)
  if (result != 0) { return result; } else { result_text.splice(c, 1); }
  var result = mult_1(sum, result_text, c + 1); // 処理2を実行(再帰)
  if (result != 0) { return result; } else { result_text.splice(c, 1); }
  return 0;
}
図9 サーバー監視システムのソースコードの一部
int SystemAnalyzer::GetPreTick_(void)
{
  // 演算に使用されたTick値を取得
  FILE *infile = fopen("/src/proc/stat", "r");
  if (NULL == infile) {
    cout << "[GetCPUUsage]<<Cannot open /src/proc/stat"
         << endl;
    return 0;
  }
  int usr, nice, sys;
  char buf[1024]; // 文字列"cpu"の部分の入力用
  int result = fscanf(infile, "%s %d %d %d",
                      buf, &usr, &nice, &sys);
  if (result == -1) {
    cout << "[GetCPUUsage]<<Cannot read fscanf"
         << endl;
    return 0;
  }
  fclose(infile);
  return usr + nice + sys;
}

Vol.58 補足情報

投稿日:2019.01.25 | カテゴリー: コード

Techパズル 第11回

「タテのカギ」の「18 現実」が抜けていました。お詫びして訂正いたします。

バーティカルバーの極意

p.73のタイトル部分の辰己先生の「己」が「巳」になっていました。お詫びして訂正いたします *

* 2019年1月25日に掲載した訂正情報にも誤りがあり、当該の先生にもご迷惑をおかけしたことをお詫び申し上げます。

情報は随時更新致します。

シェルスクリプトマガジンvol.58 Web掲載記事まとめ

投稿日:2019.01.25 | カテゴリー: コード

シェルスクリプトマガジン Vol.58で掲載しているコードをまとめています。

プレゼント&アンケートページはこちら

シェルスクリプトマガジン Vol.58は以下のリンク先でご購入できます。

004 レポート Linuxカーネル5.0
005 レポート Microsoft社のProject Mu
006 NEWS FLASH
008 特集1 オープンソースのデータベース管理システム MySQL入門/梶山隆輔
030 特集2 ラズパイでBASIC/土肥毅大、岡優樹、納富志津
040 特別企画 kintoneで作る交通費申請システム/佐山ウィリアム 明裕、ぺそ、檀原由香子
050 ラズパイセンサーボードで学ぶ 電子回路の制御/米田聡  コード掲載
055 姐のNOGYO
056 中小企業手作りIT化奮戦記/菅雄一
062 円滑コミュニケーションが世界を救う!/濱口誠一
064 「Visual Studio Code」を便利に使う/山本美穂
068 新年号/桑原滝弥・イケヤシロウ
070 バーティカルバーの極意/飯尾淳
076 法林浩之のFIGHTING TALKS/法林浩之
078 漢のUNIX/後藤大地
084 人間とコンピュータの可能性/大岩元
086 機械学習のココロ/石井一夫  コード掲載
092 Node.js/Expressで楽々Webアプリ開発/しょっさん  コード掲載
100 香川大学SLPからお届け!/竹原一駿  コード掲載
106 UNIXの歴史を振り返る/古寺雅弘
114 ユニケージ新コードレビュー/技術研究員
120 Techパズル/gori.sh
122 コラム「仕事座右の銘」/シェル魔人

Node.js/Expressで楽々Webアプリ開発(Vol.58掲載)

投稿日:2019.01.25 | カテゴリー: コード

著者:しょっさん
プログラミング言語「JavaScript」の実行環境「Node.js」と「Express」フレームワークを使って、基本となるWebアプリの開発手法を習得しましょう。第4回は「蔵書管理アプリケーション」のサンプルプログラムで認証機能を実現す
る方法を解説します。

シェルスクリプトマガジン Vol.58は以下のリンク先でご購入できます。

図3 「models/user.js」ファイルにLibrariesテーブルとの関係性を追記
user.associate = function (models) {
  // associations can be defined here
  user.hasMany(models.Library, { foreignKey: 'user_id' });
};
return user;return user;
図4 モデルファイル「models/library.js」の修正
'use strict';
module.exports = (sequelize, DataTypes) => {
  var Library = sequelize.define('Library', {
    book_title: DataTypes.STRING,
   author: DataTypes.STRING,
    publisher: DataTypes.STRING,
    image_url: DataTypes.STRING(2048),
    user_id: DataTypes.INTEGER
  },
    {
      underscored: true
    });
  Library.associate = function (models) {
    // associations can be defined here
    Library.hasMany(models.Comment, { foreignKey: 'book_id'});
  };
  return Library;
};
図5 マイグレーションファイルの追記内容
user_id: {
  type: Sequelize.INTEGER,
  allowNull: false,
  foreignKey: true,
  references: {
    model: 'users',
    key: 'id',
  },
  onUpdate: 'RESTRICT',
  onDelete: 'RESTRICT',
},
図6 作成したseed(日付-demo-user.js)
'use strict';
module.exports = {
  up: (queryInterface, Sequelize) => {
    const models = require('../models');
    return models.user.bulkCreate([
      {
        id: 1,
        email: 'tak@oshiire.to',
        password: '$2b$10$t73WMpPlvyhkWuL.ALWe..OKbU1q1ssR4K5ezVTXLlvaDMtUuAqve',
        salt: '$2b$10$t73WMpPlvyhkWuL.ALWe..'
      },
      {
        id: 2,
        email: 'sho@oshiire.to',
        password: '$2b$10$t73WMpPlvyhkWuL.ALWe..OKbU1q1ssR4K5ezVTXLlvaDMtUuAqve',
        salt: '$2b$10$t73WMpPlvyhkWuL.ALWe..'
      },
      {
        id: 3,
        email: 'shosan@oshiire.to',
        password: '$2b$10$t73WMpPlvyhkWuL.ALWe..OKbU1q1ssR4K5ezVTXLlvaDMtUuAqve',
        salt: '$2b$10$t73WMpPlvyhkWuL.ALWe..'
      }
    ]);
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.bulkDelete('users', null, {});
  }
};
図8 passportストラテジの定義
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;

const hashPassword = (password, salt) => {
  var bcrypt = require('bcrypt');
  var hashed = bcrypt.hashSync(password, salt);
  return hashed;
};

passport.use(new LocalStrategy(
  {
    usernameField: 'email',
    passwordField: 'password'
  },
  (username, password, done) => {
    models.user.findOne({ where: { email: username } }).then(user => {
      if (!user)
        return done(null, false, { message: 'Incorrect email.' });
      if (hashPassword(password, user.salt) !== user.password)
        return done(null, false, { message: 'Incorrect password.' });
      return done(null, user.get());
    });
  }
));
図9 セッション連携の関数「serializeUser」と「deserializeUser」
var session = require('express-session');

passport.serializeUser((user, done) => {
  done(null, user.id);
});

passport.deserializeUser((id, done) => {
  models.user.findById(id).then(user => {
    if (user) {
      done(null, user.get());
    } else {
      done(user.errors, null);
    }
  });
});
図10 ミドルウエアで認証済みか否かの判定
app.use((req, res, next) => {
  if (req.isAuthenticated())
    return next();
  if (req.url === '/' || req.url === '/login')
    return next();
  res.redirect('/');
});

app.use('/', index);
app.use('/books', books);
図11 テストコード(supertest-spec.js)の主要部分
/* eslint-env jasmine */

// routing テスト
const request = require('supertest-session');
const app = require('../app');

//let's set up the data we need to pass to the login method
const userCredentials = {
  email: 'tak@oshiire.to',
  password: 'password'
};
const wrongEmailCredentials = {
  email: 'foo',
  password: 'password'
};
const wrongPasswordCredentials = {
  email: 'tak@oshiire.to',
  password: 'foo'
};

//now let's login the user before we run any tests
const authenticatedUser = request(app);

describe('POST /login', () => {
  it('should redirect to / with unloggined user', (done) => {
    request(app)
      .get('/books/')
      .expect(302)
      .expect('Location', '/', done);
  });
  it('should success login with correct user', (done) => {
    request(app)
      .post('/login')
      .send(userCredentials)
      .expect(302)
      .expect('Location', '/books/', done);
  });
  it('should deny login with wrong email', (done) => {
    request(app)
      .post('/login')
      .send(wrongEmailCredentials)
      .expect(302)
      .expect('Location', '/', done);
  });
  it('should deny login with wrong password', (done) => {
    request(app)
      .post('/login')
      .send(wrongPasswordCredentials)
      .expect(302)
      .expect('Location', '/', done);
  });
});

describe('with Login', () => {
  beforeAll((done) => {
    authenticatedUser
      .post('/login')
      .send(userCredentials)
      .expect(302, done);
  });

  describe('GET /', () => {
    it('respond with http', (done) => {
      authenticatedUser
        .get('/')
        .set('Accept', 'text/html')
        .expect(200, done);
    });
  });
 :
(中略)
 :
});

describe('GET /logout', () => {
  beforeAll((done) => {
    authenticatedUser
      .post('/login')
      .send(userCredentials)
      .expect(302, done);
  });

  it('should go back to login', (done) => {
    authenticatedUser
      .get('/logout')
      .set('Accept', 'text/html')
      .expect(302)
      .expect('Location', '/', done);
  });
});

機械学習のココロ(Vol.58掲載)

投稿日:2019.01.25 | カテゴリー: コード

著者:石井 一夫
今回は、機械学習による2値分類の方法を紹介します。映画評論のテキストを好評価な内容か悪い評価の 内容かに分類するという例題のTensorFlowのチュートリアルマニュアルに沿って、2値分類のモデリングの定番である「ロジスティック回帰分析」について解説します。

シェルスクリプトマガジン Vol.58は以下のリンク先でご購入できます。

図4 ライブラリのインポートとIMDBデータセットの取り込み
import tensorflow as tf
from tensorflow import keras
import numpy as np
imdb = keras.datasets.imdb
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)
図5 IMDBデータセットの中身を表示するコード
print(train_labels[0])
print(train_data[0])
図6 元の単語を表示する関数を定義するコード
# 整数の索引に単語を割り当てる辞書を検索する関数
word_index = imdb.get_word_index()
# 最初の索引で変換
word_index = {k:(v+3) for k,v in word_index.items()}
word_index["<PAD>"] = 0
word_index["<START>"] = 1
word_index["<UNK>"] = 2 # 不明
word_index["<UNUSED>"] = 3
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
def decode_review(text):
    return ' '.join([reverse_word_index.get(i, '?') for i in text])
図7 データをテンソル化するコード
train_data = keras.preprocessing.sequence.pad_sequences(
               train_data, value=word_index["<PAD>"],
          padding='post', maxlen=256)
test_data = keras.preprocessing.sequence.pad_sequences(
               test_data, value=word_index["<PAD>"],
               padding='post', maxlen=256)
図8 学習モデルを構築するコード
# 入力データの指定形式は、映画評論に用いられた単語数(10,000 単語)をとる
vocab_size = 10000
model = keras.Sequential()
model.add(keras.layers.Embedding(vocab_size, 16)) # 入力層
model.add(keras.layers.GlobalAveragePooling1D()) # 中間層I
model.add(keras.layers.Dense(16, activation=tf.nn.relu)) # 中間層II
model.add(keras.layers.Dense(1, activation=tf.nn.sigmoid)) # 出力層
図10 モデルをコンパイルするコード
model.compile(optimizer=tf.train.AdamOptimizer(),
              loss='binary_crossentropy',
              metrics=['accuracy'])
図11 訓練用データセットを分割するコード
x_val = train_data[:10000]
partial_x_train = train_data[10000:]
y_val = train_labels[:10000]
partial_y_train = train_labels[10000:]
図12 モデルの訓練用コード
history = model.fit(partial_x_train,
                     partial_y_train,
                     epochs=40,
                     batch_size=512,
                     validation_data=(x_val, y_val),
                     verbose=1)
図13 モデルの性能評価用コード
results = model.evaluate(test_data, test_labels)
print(results)
図14 損失のグラフを表示するコード
%matplotlib inline
import matplotlib.pyplot as plt

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(1, len(acc) + 1)

# 青点グラフ
plt.plot(epochs, loss, 'bo', label='Training loss')
# 青線グラフ
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()

plt.show()
図16 精度のグラフを表示するコード
plt.clf() # 図を初期化
acc_values = history_dict['acc']
val_acc_values = history_dict['val_acc']

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()

plt.show()

ラズパイセンサーボードで学ぶ電子回路の制御(Vol.58掲載)

投稿日:2019.01.25 | カテゴリー: コード

著者:米田 聡
シェルスクリプトマガジンでは、小型コンピュータボード「Raspberry Pi」(ラズパイ)向けのセンサー搭載拡張ボード「ラズパイセンサーボード」を制作しました。第5回では、このボードを使った電子回路制御を取り上げます。具体的には、明るさ・近隣センサーに近づく物体の検出です。

シェルスクリプトマガジン Vol.58は以下のリンク先でご購入できます。

図3 割り込みに対応したVCNL4020ライブラリ(VCNL4020.py)
import smbus
import time
import RPi.GPIO as GPIO
from threading import BoundedSemaphore

class VCNL4020():

  _ALS_OD       = 0b00010000  # オンデマンド明るさ計測スタート
  _PROX_OD      = 0b00001000  # オンデマンド近接計測スタート
  _ALS_EN       = 0b00000100  # 明るさ繰り返し計測有効
  _PROX_EN      = 0b00000010  # 近接繰り返し計測有効
  _SELFTIMED_EN = 0b00000001  # 内蔵タイマー有効
  
  _CONT_CONV    = 0b10000000  # Continue Conversion有効
  _AMBIENT_RATE = 0b00010000  # 明るさの計測レート(default:2sample/s)
  _AUTO_OFFSET  = 0b00001000  # 自動オフセットモード有効
  _AVERAGING    = 0b00000101  # 平均化(default:32conv)

  _COMMAND_REG       = 0x80  # コマンドレジスタ
  _PID_REG           = 0x81  # プロダクトIDレジスタ
  _PROX_RATE_REG     = 0x82  # 近接測定レートジスタ
  _IR_CURRENT_REG    = 0x83  # 近接測定用赤外線LED電流設定レジスタ(default=20mA)
  _AMBIENT_PARAM_REG = 0x84  # 明るさセンサーパラメータレジスタ

  _AMBIENT_MSB       = 0x85  # 明るさ上位バイト
  _AMBIENT_LSB       = 0x86  # 明るさ下位バイト

  _PROX_MSB          = 0x87  # 近接上位バイト
  _PROX_LSB          = 0x88  # 近接下位バイト

  _INT_CONTROL_REG   = 0x89  # 割り込み制御レジスタ

  _LOW_TH_MSB        = 0x8A  # Lowしきい値(MSB)
  _LOW_TH_LSB        = 0x8B  # Lowしきい値(LSB)
  _HIGH_TH_MSB       = 0x8C  # Highしきい値(MSB)
  _HIGH_TH_LSB       = 0x8D  # Highしきい値(LSB)

  _INT_STATUS_REG    = 0x8E  # 割り込みステータス

  _INT_NO            = 0x06  # int = GPIO6

  # コールバック
  __callbackfunc     = None

  def __init__(self, i2c_addr = 0x13, busno = 1):
    self.addr = i2c_addr
    self.i2c = smbus.SMBus(busno)
    
    self._write_reg(self._COMMAND_REG, self._ALS_OD  |\
                       self._PROX_OD |\
                       self._ALS_EN  |\
                       self._PROX_EN |\
                       self._SELFTIMED_EN )
                       
    self._write_reg(self._IR_CURRENT_REG, 2 )  # 20mA
                       
    self._write_reg(self._AMBIENT_PARAM_REG, self._CONT_CONV    |\
                        self._AMBIENT_RATE |\
                        self._AUTO_OFFSET  |\
                        self._AVERAGING )
    self.semaphore = BoundedSemaphore()

    # GPIO設定
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(self._INT_NO, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.add_event_detect(self._INT_NO, GPIO.FALLING, callback=self.__interruptfunc)

    time.sleep(0.6)      # 初回測定まで待つ

  def _write_reg(self, reg, value):
    self.i2c.write_byte_data(self.addr, reg, value)

  def _read_reg(self, reg):
    return self.i2c.read_byte_data(self.addr, reg)

  # 高値用レジスタ設定
  def set_high_threshold(self, value):
    self.semaphore.acquire()
    h = (value & 0xFF00) >> 8
    l = value & 0x00FF
    self._write_reg(self._HIGH_TH_MSB, h)
    self._write_reg(self._HIGH_TH_LSB, l)
    self.semaphore.release()

  # 低値用レジスタ設定
  def set_low_threshold(self, value):
    self.semaphore.acquire()
    h = (value & 0xFF00) >> 8
    l = value & 0x00FF
    self._write_reg(self._LOW_TH_MSB, h)
    self._write_reg(self._LOW_TH_LSB, l)
    self.semaphore.release()

  # 割り込み有効化
  def enable_interrupt(self, callbackfunc=None, prox=True, samples=1):
    self.semaphore.acquire()

    self.__callbackfunc = callbackfunc
    value = self._read_reg(self._INT_CONTROL_REG)

    if callbackfunc is not None:
      if prox:
        value |= 0b00000010
      else:
        value |= 0b00000011
    else:
        value &= 0b11111100

    # samples
    samples &= 0b00000111
    samples = samples << 5
    value &= 0b00011111
    value |= samples

    self._write_reg( self._INT_CONTROL_REG, value)
    self.semaphore.release()

  # 割り込み関数
  def __interruptfunc(self, ch):
    if ch != self._INT_NO:
      return
    if self.__callbackfunc is not None:
      self.__callbackfunc(self.luminance, self.proximity)


  @property
  def luminance(self):
    self.semaphore.acquire()
    d = self.i2c.read_i2c_block_data(self.addr, self._AMBIENT_MSB, 2)
    self.semaphore.release()
    return (d[0] * 256 + d[1]) / 4
  
  @property
  def proximity(self):
    self.semaphore.acquire()
    d = self.i2c.read_i2c_block_data(self.addr, self._PROX_MSB, 2)
    self.semaphore.release()
    return (d[0] * 256 + d[1])
図4 物体が近づいたら割り込みを発生させるサンプルプログラム(simpletest.py)
#!/usr/bin/env python3
import time
from  VCNL4020 import  VCNL4020

sensor = VCNL4020()

# コールバック関数
def callback(lux, prox):
  print('センサーに何かが接近しています')
  print('現在の明るさ:'+str(lux) )
  print('近接センサー:'+str(prox) )
  # 割り込み再設定
  sensor.enable_interrupt(callback)

# しきい値高を設定
sensor.set_high_threshold(2500)
# しきい値低を設定
sensor.set_low_threshold(0)
# 割り込み有効化
sensor.enable_interrupt(callback)

try:
  while True:
    time.sleep(1)

except KeyboardInterrupt:
  term = True

第27回 電源障害からサーバーを守る(UPS)

投稿日:2018.12.14 | カテゴリー: 記事

社内サーバーなどの運用管理で見落としがちなのが「電源」に対する保護です。安定した電圧や電流を常に供給しなければ、サーバーが不安定になって停止や故障しかねません。
安定した電源を確保するための重要なハードウエアが「無停電電源装置」(UPS:Uninterruptible Power Supply)です。UPSを用いると、電圧値や電流値が低下したときに補えたり、電源断が発生したときに自動シャットダウンでサーバーを安全に停止させたり、雷などによる異常な高電圧を防いだりできます(図1)。

図1 電源を保護する無停電電源装置(UPS)

今回は、このUPSをサーバーにつないでサーバーの電源を制御する方法を紹介します。管理するサーバーには、Ubuntu Server 16.04 LTSがインストールされているものとします。

第26回 アクセスを制限する(AppArmor)

投稿日:2018.11.30 | カテゴリー: 記事

コンピュータを守るセキュリティの仕組みとして「強制アクセス制御」(Mandatory Access Control)があります。強制アクセス制御では、「パーミッション」などで定められているOSの標準的な制限ではなく、ユーザーや実行プログラムに対してより厳しい制限を設定できます。

例えば、あるプログラムに対して、参照しかしないファイルへの書き込み、利用するはずがないファイルへのアクセス、関係がない別のプログラムの呼び出しを禁止できます(図1)。

図1 強制アクセス制御による制限

このような強制アクセス制御をLinuxで実現できるソフトウエアには、いくつかあります。Ubuntu Serverでは「AppArmor」というソフトウエアが標準で導入されていて、有効になっています。このAppArmorは、Linux OSの核となる「カーネル」のセキュリティフレームワーク「Linux Security Modules」(LSM)を使って実装しています。

Python標準モジュール9選(Vol.57掲載)

投稿日:2018.11.25 | カテゴリー: コード

著者:降籏洋行、西川公一朗

Pythonには、豊富な機能を備える標準ライブラリが用意されています。しかし機能豊富すぎるが故に、初心者にとっては「この標準ライブラリモジュールは、実際の開発において、こういう時に役に立つ」という情報を探しにくかったり、リファレンスの説明だけだと使用方法をイメージしづらかったりします。本特集では、知っておくと役立つ便利な標準ライブラリモジュール9個を厳選して、その活用法を紹介します。

シェルスクリプトマガジン Vol.57は以下のリンク先でご購入できます。

 

特集1 ラズパイでセンサーを使う(Vol.57掲載)

投稿日:2018.11.25 | カテゴリー: コード

著者:米田聡、麻生二郎

シェルスクリプトマガジンでは、小型コンピュータボード「Raspberry Pi」のオリジナル拡張ボード第2弾として「ラズパイセンサーボード」を製作しました。サンプルプログラムで、搭載されている湿温度・気圧センサー、
ガスセンサー、照度センサーの使い方を紹介します。

シェルスクリプトマガジン Vol.57は以下のリンク先でご購入できます。

Vol.57

投稿日:2018.11.25 | カテゴリー: バックナンバー

シェルスクリプトマガジンでは、小型CPUボード「Raspberry Pi」のオリジナル拡張ボード第2弾として「ラズパイセンサーボード」を制作しました。このセンサーボードには湿温度・気圧センサー、ガスセンサー、明るさセンサーを搭載しています。特集1では、サンプルプログラムを基にしてラズパイセンサーボードの使い方を紹介します。

特集2では、プログラミング言語「Python」の標準ライブラリに含まれる便利で役立つモジュールを9個紹介します。機能が豊富なため、初心者には使い方が分からないものもあります。実際の活用方法を含めながら、分かりやすく解説します。

特集3では、注目のデザインアプリ「Adobe XD CC」を紹介します。Adobe XD CCは、UI/UXデザイン、プロトタイプ、共同作業ツールとして動作します。一部の制限はありますが、無料でも使えます。Adobe XD CCで次世代のデザインアプリを体験してみてください。

特別企画では、ユーザー企業向けにセキュリティ人材不足を解消する方法を解説します。どのような人材が必要なのか、どうすれば人材を育てられるか、アウトソースをどう活用すべきかなど、有益な情報を紹介します。

このほか、「1960年代から現在まで UNIXの歴史を振り返る」「Extensionで機能追加 オープンソースエディタ「Visual Studio Code」を便利に使う」という二つの新連載が始まりました。

今月も読み応え十分のシェルスクリプトマガジン Vol.57。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

漢のUNIX(Vol.57掲載)

投稿日:2018.11.25 | カテゴリー: コード

著者:後藤大地

これまで、JSONデータを読み込んで処理する方法を取り上げてきた。JSONデータをパースしながら処理する方法や、メモリー上に展開したJSONデータを、指定したフォーマットで出力する方法を解説した。今回も引き続き同じ「Jansson」ライブラリを使うが、JSONデータを読み込んで処理するのではなく、生成する方法を紹介する。

シェルスクリプトマガジン Vol.57は以下のリンク先でご購入できます。

 

シェルスクリプトマガジンvol.57 Web掲載記事まとめ

投稿日:2018.11.25 | カテゴリー: コード

シェルスクリプトマガジン Vol.57で掲載しているコードをまとめています。

プレゼント&アンケートページはこちら

シェルスクリプトマガジン Vol.57は以下のリンク先でご購入できます。

004 レポート LPI-Japanインタビュ
005 レポート 第34回のTechLION開催
006 NEWS FLASH
008 特集1 ラズパイでセンサーを使う/米田聡、麻生二郎 コード掲載
020 特集2 Python標準モジュール9選/降籏洋行、西川公一朗 コード掲載
038 特集3 Adobe XD CCに触れてみよう/湯口りさ、北村崇
052 特別企画 セキュリティ人材不足を解決する/園田道夫
058 c人間とコンピュータの可能性/大岩元
060 UNIXの歴史を振り返る/古寺雅弘
066 法林浩之のFIGHTING TALKS/法林浩之
068 スズラボ通信/すずきひろのぶ コード掲載
073 姐のNOGYO
074 バーティカルバーの極意/飯尾淳 コード掲載
080 RESEARCHES FOR FUTURE/飯尾淳
082 中小企業手作りIT化奮戦記/菅雄一
088 円滑コミュニケーションが世界を救う!/濱口誠一
090 「Visual Studio Code」を便利に使う/山本美穂 コード掲載
094 香川大学SLPからお届け!/飯國隆志 コード掲載
098 統合/桑原滝弥・イケヤシロウ
100 アジャイル開発 Let’s Practice!/熊野憲辰
104 Node.js/Expressで楽々Webアプリ開発/しょっさん コード掲載
112 漢のUNIX/後藤大地 コード掲載
122 ユニケージ新コードレビュー/村本禎実 コード掲載
128 Techパズル/gori.sh
130 コラム「世の中の流れとは別に」/シェル魔人

「Visual Studio Code」を便利に使う(Vol.57掲載)

投稿日:2018.11.25 | カテゴリー: コード

著者:山本美穂

米Microsoft 社発のオープンソースエディタ「Visual Studio Code」には「Extension」と呼ばれる機能拡張用のソフトウエアが多数提供されています。本連載では便利なExtension の使い方を中心に紹介します。第1回は、PaaS「Azure App Service」と連携できるExtension です。

シェルスクリプトマガジン Vol.57は以下のリンク先でご購入できます。

 

もっと見る

新しい記事一覧へ戻る

  • -->