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

test

Vol.71 補足情報

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

目次

 p.3の先頭の行にあるページ数の「44」は「56」の誤りです。お詫びして訂正いたします。

レポート

 p.4で紹介した20%割引クーポンは、Linux30周年に伴って30%割引に変更されました。

連載 Red Hatのプロダクト

 p.62の右上にある『を実行して「oc」コマンドを入手します』は、『を実行して「oc」コマンドのパスを確認します』の誤りです。お詫びして訂正いたします。

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

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

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

004 レポート Linuxシス管向け日本語講座
005 レポート Windows 10用無償RPAツール
006 NEWS FLASH
008 特集1 はじめてのExcel VBA/松井元
018 特集2 1Rでも快適なテレワーク環境/北谷明日香
030 特集3 ラズパイ4でQ&Aサイトを構築する/麻生二郎 コード掲載
040 特別企画 ゼロから始めるEthereum/志茂博、高畑祐輔 コード掲載
051 Hello Nogyo!
052 Raspberry Piを100%活用しよう/米田聡
056 レッドハットのプロダクト/田中司恩
065 MySQLのチューニング/稲垣大助
064 Webhook/桑原滝弥、イケヤシロウ
074 香川大学SLPからお届け!/樋口史弥 コード掲載
080 法林浩之のFIGHTING TALKS/法林浩之
082 中小企業手作りIT化奮戦記/菅雄一
086 Pythonあれこれ/飯尾淳 コード掲載
092 Bash入門/大津真
098 Techパズル/gori.sh
100 コラム『ユニケージの作法は「生き方」に通じる』/シェル魔人

特集3 ラズパイでQ&Aサイトを構築(Vol.71記載)

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

著者 麻生二郎

小型コンピュータボード「Raspberry Pi」(ラズパイ)の最大の特徴(Picoを除く)は、LinuxなどのOSが動作することです。そのOSとして「Ubuntu」という、パソコンやサーバー向けで人気の高いLinuxディストリビューションが利用できます。本特集では、ラズパイとUbuntuを組み合わせて、Stack Overflowに似たサービスを提供できるQ&Aサイトを構築します。

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

図20 無線LANの設定

    wifis:
        wlan0:
            access-points:
                SSID:
                    password: パスワード
            dhcp4: true

図27 固定IPアドレスを割り当てる

wifis:
  wlan0:
    dhcp4: false
    addresses: [192.168.10.100/24]
    gateway4: 192.168.10.1
    nameservers:
      addresses: [192.168.10.1]
    access-points:
            SSID:
              password: パスワード

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

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

著者:樋口史弥

COVID-19の流行により、香川大学でもサークル活動に制限がかかっています。そこで、ICカードと非接触ICカードリーダーを用いて、部室の入退室をモニタリングするシステムを作成しました。部室の利用状況をリアルタイムに確認できれば密を避ける目安になりますし、何かあったときのための記録としても有用です。

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

図6 IDmを読み取って表示するPythonコード

import nfc
import binascii

def on_connect(tag):
  data={'idm': binascii.hexlify(tag.idm).decode('utf-8')}
  print(data)
  return True

def main():
  with nfc.ContactlessFrontend('usb:ベンダーID:プロダクトID') as clf:
    while clf.connect(rdwr={'targets': ['212F'],
                            'on-connect': on_connect}):
      pass

if __name__ == "__main__":
  main()

図7 サーバーと連携させるために挿入するPythonコード

import json
from os import getenv
import urllib.request

def post_data(data):
  headers = { 'Content-Type': 'application/json', }
  req = urllib.request.Request(getenv('REQUEST_URI'),
                               data=json.dumps(data).encode('utf-8'),
                               headers=headers, method='POST')
  try:
    with urllib.request.urlopen(req) as res:
      if res.code == 200:
        result = json.loads(res.read().decode('utf-8'))['result']
        if result == 'in':
          print('IN')
        else:
          print('OUT')
  except urllib.error.HTTPError as err:
    print("err: ", err.read())

図12 退出かどうかを調べるSQL 文

$sql = 'SELECT * '.
        'FROM log INNER JOIN user '.
        'ON log.user_id=user.id '.
        'WHERE exit_time IS NULL '.
        'AND enter_time >= DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 1 DAY) '.
        'AND user.idm=? '.
        'ORDER BY log.enter_time DESC LIMIT 1';

図13 入室情報を表示するコード(index.js)により追加される情報の例

<div class="use_now">
  <div>
    <div class="time">2021-03-12 12:54:31</div>
    <div class="name">guest</div>
  </div>
  <div>
    <div class="time">2021-03-12 12:42:27</div>
    <div class="name">user1</div>
  </div>
</div>

図15 音を鳴らすために挿入するPythonコード

from gpiozero import TonalBuzzer
from gpiozero.tones import Tone
from time import sleep

def beep(hertz):
  bz = TonalBuzzer(4)
  bz.play(Tone(frequency=hertz))
  sleep(0.2)
  bz.stop()

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

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

著者:大津真

本連載ではシェルスクリプトの書き方をやさしく紹介します。対象とするシェルは、多くのLinuxディストリビューションが標準シェルとして採用する「Bash」です。最終回となる今回は、関数について解説します。

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

図1 シェルスクリプト「hello1.sh」の内容

#!/bin/bash
function hello() {
    echo "Hello Function"
}

hello
hello
hello

図2 シェルスクリプト「param_test1.sh」の内容

#!/bin/bash
function param_test() {
    echo "\$0: $0"
    echo "\$1: $1"
    echo "\$2: $2"
    echo "\$3: $3"
    echo "\$4: $4"    
    echo "\$#: $#"
    echo "\$@: $@"
}

param_test 春 夏 秋 冬

図3 シェルスクリプト「scope1.sh」の内容

#!/bin/bash
function scope_test() {
    g1="グローバル"
    local l1="ローカル"
}

scope_test
echo "g1: $g1"
echo "l1: $l1"

図4 シェルスクリプト「file_test1.sh」の内容

#!/bin/bash
function check_file() {
    if [[ -f $1 ]]; then
        echo "ファイルが存在します"
        return 0
    else
        echo "$1が見つかりません"
        return 1
    fi
}

if [[ $# -eq 0 ]]; then
    echo "引数でファイルを指定してください"
    exit 1
fi

check_file $1
echo "終了ステータス: $?"

図5 ライブラリファイル「my_lib.sh」の内容

function check_file() {
    if [[ -f $1 ]]; then
        echo "ファイルが存在します"
        return 0
    else
        echo "$1が見つかりません"
        return 1
    fi
}

図6 シェルスクリプト「file_test2.sh」の内容

#!/bin/bash
source ./my_lib.sh

if [[ $# -eq 0 ]]; then
    echo "引数でファイルを指定してください"
    exit 1
fi

check_file $1
echo "終了ステータス: $?"

図7 シェルスクリプト「sum_test1.sh」の内容

#!/bin/bash
function calc_sum() {
    sum=0
    for n in $(seq $1)
    do
        sum=$(expr $sum + $n)
    done
    echo $sum
}
# 引数があるかどうかのチェック
if [[ $# -eq 0 ]]; then
    echo "引数を指定してください"
    exit 1
fi
# 引数が整数値であることを調べる
expr $1 + 1 &> /dev/null
if [[ $? -ge 2 ]]
then
    echo "整数値を指定してください"
    exit $?
fi    
# calc_sum関数を呼び出す
echo "1から$1までの総和: $(calc_sum $1)"

図8 シェルスクリプト「test1.sh」の内容

#!/bin/bash
num=10
sum=0
for n in $(seq $num)
do
    sum=$(expr $sum + $n)
done
echo "総和: $sum"

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

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

著者:しょっさん

ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならないでしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。最終回は、本番環境として恥ずかしくないシステムをリリースする方法を解説します。

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

図3 最終的に実装された各レイヤーのソースコード

■「src/api/expense.ts」の経費精算部分

import { Request, Response, NextFunction } from "express";
import Express from "express";
import { ExpenseModel } from "./interfaces/expenseModel";
import { ExpenseController } from "./interfaces/expenseController";

const router = Express.Router();

// DBモデルおよびコントローラのインスタンス化
const expense_model = new ExpenseModel();
const expense_controller = new ExpenseController(expense_model);
// コントローラへ、DBモデルのインスタンスを引き継いでいます

// POST 経費の入力
router.post("/", async (req: Request, res: Response, next: NextFunction) => {
  const result = await expense_controller.submitExpense(req.body!);
  res.send(result);
});
(略)
export default router;

■「src/interfaces/expenseController.ts」の経費精算部分

import { SubmitExpense } from "../usecases/SubmitExpense";
import { IExpenseValue } from "../domains/expenseEntity";
import { ExpenseRepository } from "../adapters/ExpenseRepository";
import { IExpenseModel } from "./IExpenceModel";

export class ExpenseController {
  private expenseRepository: ExpenseRepository;

// DBモデルのインスタンスを基にリポジトリをインスタンス化
  constructor(model: IExpenseModel) {
    this.expenseRepository = new ExpenseRepository(model);
  }

  async submitExpense(
    expense: IExpenseValue
  ): Promise<IExpenseValue> {

    try {
	// リポジトリのインスタンスをユースケースへ引き継いでユースケースを実行
      const usecase = new SubmitExpense(this.expenseRepository);
      const result = await usecase.execute(expense);
      return result.read();
    } catch (error) {
      throw new Error(error);
    }
  }
(略)
}

■「src/adapters/ExpenseRepository.ts」の経費精算部分

import { ExpenseEntity } from "../domains/expenseEntity";
import { IExpenseRepository } from "./IExpenseRepository";
import { IExpenseModel } from "../interfaces/IExpenceModel";

export class ExpenseRepository implements IExpenseRepository {
  private expense_model: IExpenseModel;

	// コントローラから引き継いだDBモデルのインスタンスをここで保持
  constructor(model: IExpenseModel) {
    this.expense_model = model;
  }

  store(expense: ExpenseEntity): Promise<ExpenseEntity> {
	// 特にフォーマットなど変更を今回はしていないので、そのまま保管メソッドをコール
    return this.expense_model.store(expense);
  }
}

図5 アクセス認可を制御する方法

■ロールによって実行するモデル操作を変更する

export class ExpenseModel implements IExpenseModel {
    private _userModel: IUserModel

  constructor(user: IUser) {
    this._userModel = user;
  }
(略)
  findUnapproval(boss_id: number): Promise<ExpenseEntity[]> {
	if (this._userModel.role.find(role => role === 'APPROVER') {
        return Expense.findAll({
          where: Sequelize.literal(
            approval = ${approval_status.unapproved}
          ),
(略)
      } else {
        return Expense.findAll({
          where: Sequelize.literal(
            approval = ${approval_status.unapproved} and user_id IN (SELECT id FROM users WHERE boss_id = '${boss_id}')
          ),
(略)
      }
    }

■経費精算テーブルにアクセス認可用の「role」カラムを追加してアクセス認可させる

export class ExpenseModel implements IExpenseModel {
    private _userModel: IUserModel

  constructor(user: IUser) {
    this._userModel = user;
  }
(略)
  findUnapproval(boss_id: number): Promise<ExpenseEntity[]> {
      return Expense.findAll({
        where: {
          approval: ${approval_status.unapproved}
          role: ${this._userModel.role}
        }
(略)
      }
    }

Vol.70 補足情報

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

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

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

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

004 レポート RHEL互換OSのCentOS終了
005 NEWS FLASH
008 特集1 第4世代Ryzenプロセッサ/麻生二郎
014 特集2 FIDO2の最新動向/五味秀仁、板倉景子 コード掲載
026 特別企画 Forguncyを使ってみよう/須山亜紀
040 Raspberry Piを100%活用しよう/米田聡 コード掲載
044 レッドハットのプロダクト/森和哉 コード掲載
054 MySQLのチューニング/稲垣大助
059 バーティカルバーの極意/飯尾淳 コード掲載
064 CI/CD/桑原滝弥、イケヤシロウ
066 中小企業手作りIT化奮戦記/菅雄一
070 法林浩之のFIGHTING TALKS/法林浩之
072 香川大学SLPからお届け!/高嶋真輝 コード掲載
082 円滑コミュニケーションが世界を救う!/濱口誠一
084 Webアプリの正しい作り方/しょっさん コード掲載
096 シェルスクリプトの書き方入門/大津真 コード掲載
102 Techパズル/gori.sh
104 コラム「ユニケージとその将来」/シェル魔人

特集2 FIDO2の最新動向(Vol.70記載)

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

著者:五味秀仁、板倉景子

パスワード課題の解決に注力する業界団体FIDO(ファイド)アライアンスは、
フィッシング攻撃に耐性のある、シンプルで堅牢な認証の展開を推進してい
ます。その新しい仕様「FIDO2」は、標準化団体W3Cで策定されるWeb認証仕
様「WebAuthn」(ウェブオースン)を包含し、さらにAndroidとWindowsに加えて、iOSやmacOSにも対応するなど、プラットフォーム拡大を進めています。本特集では、FIDO2の概要や仕組み、最新動向について解説します。

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

図8 publicKeyデータの例

{
  "publicKey": {
    "attestation": "direct",
    "authenticatorSelection": {
      "authenticatorAttachment": "platform",
      "requireResidentKey": false,
      "userVerification": "required"
    },
    "challenge": "bWhMbDgxc1h4Z3B...",,
    "excludeCredentials": [],
    "pubKeyCredParams": [
      {
        "alg": -7,
        "type": "public-key"
      }
    ],
    "rp": {
      "id": "example.com",
      "name": "Example corporation"
    },
    "timeout": 60000,
    "user": {
      "displayName": "Ichiro Suzuki",
      "id": [70, 60, ...],
      "name": "ichiro.suzuki@example.com”
    }
  }
}

図9 認証器に登録要求を出すJavaScriptプログラムの例

if (!window.PublicKeyCredential) {
  WebAuthn APIが使えない場合の処理	
}
// プラットフォーム認証器が使える場合
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
  .then(function () {
    // 以下で設定するのがPublicKeyCredentialCreationOptions
    var options = { "publicKey": ... }; 
    return navigator.credentials.create(options);
  }).then(function (newCredentialInfo) {
    生成されたクレデンシャルの処理
  }).catch( function(err) {
    エラー処理
  });

図10 PublicKeyCredentialデータの例

{
  "id": "sL39APyTmisrjh11vghaqNfuru...",
  "rawId": "sL39APyTmisrjh11vghaqNf...",
  "response": {
    "attestationObject": "eyJjaGFsbGVuZ2...",
    "clientDataJSON": "o2NmbXRmcGFja2..."
  },
  "type": "public-key"
}

図11 attestationObject項目に設定される情報の例

{
  "fmt": "packed",
  "attStmt": {
    "alg": -7,
    "sig": "MEYCIQDyCG+pKJmcV...",
    "x5c": [
      "MIICQjCCAcmgAwIBA..."
    ]
  },
  "authData": {
    "credentialData": {
      "aaguid": "vfNjNcR0U8...",
      "credentialId": "Y0GeiMghzi...",
      (略)
    },
    (略)
  }
}

図12 clientDataJSON項目に設定される情報の例

{
  "challenge": "bWhMbDgxc1h4Z3B...",
  "origin": "https://example.com",
  "type": "webauthn.create"
}

図14 publicKeyデータの例

{
  "publicKey": {
    "allowCredentials": [
      {
        "id": "Y0GeiMghzi...",
        "type": "public-key"
      }
    ],
    "challenge": "lZgXWOY0Go6HxmQP03...",
    "rpId": "example.com",
    "timeout": 60000,
    "userVerification": "required"
  }
}

図15 認証器に認証要求を出すJavaScriptプログラムの例

// 以下で設定するのがPublicKeyCredentialRequestOptions
var options = { "publicKey": ... };
navigator.credentials.get(options)
  .then(function (assertion) {
  アサーションをRPサーバーに返す処理
}).catch(function (err) {
  エラー処理
});

図16 PublicKeyCredentialデータの例

{
  "id": "Y0GeiMghzi...",
  "rawId":"Y0GeiMghzi...",
  "response": {
    "authenticatorData": "yHxbnq0iOEP3QN0K...",
    "clientDataJSON": "eyJjaGFsbG...",
    "signature": "NRUVOWSMtH..."
  },
}

Raspberry Piを100%活用しよう(Vol.70掲載)

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

著者:米田聡

小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第3回は、電源断でもラズパイを正常終了するための拡張基板を扱います。

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

図3 ADRSZUPコマンドのソースコード(ADRSZUP.c)

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <string.h>
#include <time.h>
#include <linux/reboot.h>

#define TRUE    1
#define FALSE   0

/* エラーメッセージ */
void error(char *s)
{
    fputs(s, stderr);
}

/* GPIO初期化 */
int initGpio(unsigned int gpio)
{
    char buf[256];
    int i, fd;

    // export
    fd = open("/sys/class/gpio/export", O_WRONLY);
    if( fd < 0 ) {
        error("/sys/class/gpio/export cant be opened \n");
        return FALSE;
    }
    sprintf(buf,"%d",gpio);
    write(fd, buf, strlen(buf));
    close(fd);

    // direction
    sprintf(buf, "/sys/class/gpio/gpio%d/direction", gpio);
    for( i=0; i < 10000; i++) {
        fd = open(buf,O_WRONLY );
        if(fd >= 0) break;
    }
    if(fd < 0) {
        error("Direction cant opened\n");
        return FALSE;
    }
    sprintf(buf,"in");
    write(fd, buf, strlen(buf));
    close(fd);
    
    // High -> Low falling edge
    sprintf(buf, "/sys/class/gpio/gpio%d/edge", gpio);
    for( i=0; i < 10000; i++) {
        fd = open(buf,O_WRONLY );
        if(fd >= 0) break;
    }
    if( fd < 0 ) {
        error("Edge cant opended\n");
        return FALSE;
    }
    sprintf(buf, "falling");
    write(fd, buf, strlen(buf));
    close(fd);

    return TRUE;
}

/* GPIO開放 */
int deinitGpio(unsigned int gpio)
{
    char buf[256];
    int fd;

    sprintf(buf, "%d", gpio);
    fd = open("/sys/class/gpio/unexport", O_WRONLY);
    if(fd < 0 ){
        error("GPIO cant opened");
        return FALSE;
    }
    write(fd, buf, strlen(buf));
    close(fd);

    return TRUE;
}

/* シャットダウン */
int shutdown(void)
{
    sync();
    sync();
    return reboot(LINUX_REBOOT_CMD_POWER_OFF);
}

#define PWDN_GPIO  6    // 電源断通知GPIO番号

int main(int argc, char *argv[])
{
    int retval = 0;
    int fd;
    char buf[256];
    char c;

    if(! initGpio(PWDN_GPIO) ) {
        error("GPIO cant be initialized\n");
        return 0;
    }

    // GPIOオープン
    sprintf(buf,"/sys/class/gpio/gpio%d/value", PWDN_GPIO );
    fd = open(buf, O_RDONLY);
    if(fd < 0) {
        error("Value cant opened");
        return 0;
    }
    // 空読み
    read(fd, &c, 1);
    while(1) {
        struct pollfd pfd;
        pfd.fd = fd;
        pfd.events = POLLPRI;
        pfd.revents = 0;
        // GPIO fallingイベント待ち
        lseek(fd, 0, SEEK_SET);
        int r = poll(&pfd, 1, -1);
        fputs("Power down detected\nWait for 5 seconds\n", stdout);
        // 5秒待つ
        sleep(5);
        read(fd, &c, 1);
        if( c == '0' ) {
            close(fd);
            deinitGpio(PWDN_GPIO);
            fputs("Shutdown now\n",stdout);
            retval = shutdown();
            break;
        }
    }
    return retval;
}

図4 /etc/systemd/system/adrszup.serviceファイルの内容

[Unit]
Description=Auto shutdown process for ADRSZUP

[Service]
Type=simple
Restart=no
User=root
ExecStart=/usr/local/bin/ADRSZUP

[Install]
WantedBy=multi-user.target

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

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

著者:森和哉

近年、ビジネスのさまざまな分野において、業務の熟練者の高齢化と、後継者不足が深刻化しています。中でも、「計画策定」業務は、豊富な経験や特殊なスキルが必要とされることから、特に属人化が進行しています。「Red Hat Business Optimizer」は、そのような企業の計画策定業務を標準化し、より効率的な計画の立案を可能にします。

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

図8 制約条件「すべてのシフトに従業員が割り当てられていること」のコード

■employeeRosteringScoreRules.drl

(略)
rule "Assign every shift"
    when
        Shift(employee == null)
    then
        scoreHolder.penalize(kcontext);
end
(略)

■RosterConstraintConfiguration.java

(略)
public static final String CONSTRAINT_ASSIGN_EVERY_SHIFT = "Assign every shift";
(略)
@ConstraintWeight(CONSTRAINT_ASSIGN_EVERY_SHIFT)
private HardMediumSoftLongScore assignEveryShift = HardMediumSoftLongScore.ofMedium(1);
(略)

図9 制約条件「従業員が勤務を希望している時間帯へのシフトの割り当て」のコード

■employeeRosteringScoreRules.drl

(略)
rule "Desired time slot for an employee"
    when
        $availability: EmployeeAvailability(
                state == EmployeeAvailabilityState.DESIRED,
                $e : employee,
                $startDateTime : startDateTime,
                $endDateTime : endDateTime) 
        Shift(employee == $e,
                $startDateTime < endDateTime, 
                $endDateTime > startDateTime) 
    then
        scoreHolder.reward(kcontext, $availability.getDuration().toMinutes());
end
(略)

■RosterConstraintConfiguration.java

(略)
public static final String CONSTRAINT_DESIRED_TIME_SLOT_FOR_AN_EMPLOYEE = "Desired time slot for an employee";
(略)
@ConstraintWeight(CONSTRAINT_DESIRED_TIME_SLOT_FOR_AN_EMPLOYEE)
    private HardMediumSoftLongScore desiredTimeSlot = HardMediumSoftLongScore.ofSoft(10);
(略)

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

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

著者:飯尾淳

コロナ禍の影響を受けて、2020年度は大学の講義の多くがオンライン講義になりました。対面での講義に比べると、情報伝達の面でオンライン講義はかなり不利です。また、受講状況がどうだったかについても気になります。最終回となる今回は、久しぶりに棒グラフが登場します。棒グラフで、講義コンテンツがいつ視聴されたのかを可視化します。

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

図4 未読率を計算するRubyスクリプト「midoku.rb」

#!/usr/bin/env ruby
#
# for n in seq -w 1 13;do 
#   ./midoku.rb Rawdata/rawdata_$n.csv $n;
# done

m1 = m2 = m3 = 0

File.open(ARGV[0], "r") {|f|
  f.each_line {|line|
    a = line.chomp.split(',')
    m1 += a.count('未読')
    m2 += a.count('閲覧済')
    m3 += a.count('更新後未読')
  }
}
printf "%s,%d,%d,%d,%04.1f\n",
  ARGV[1],m1,m2,m3,m1.to_f*100/(m1+m2+m3).to_f

図7 視聴率の総計を計算するRubyスクリプト「hours.rb」

#!/usr/bin/env ruby
#
# cat Rawdata/* > all.csv
# ./hours.rb all.csv | sort | uniq -c | \
# sort -n -k 2 | awk '{printf("%d,%d\n", $2,$1)}' > hours.csv

number = "(\\d+)"
m1 = m2 = m3 = 0

File.open(ARGV[0], "r") {|f|
  f.each_line {|line|
    a = line.chomp.split(',')
    a.map!{|x| /#{number}:#{number}:#{number}/.match(x).to_a[1] }
    a -= [nil]
    a.map{|x| print x, "\n" }
  }
}

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

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

著者:高嶋真輝

 今回は、本物に限りなく近いデータを生成できる「敵対的生成ネットワーク」(Generative Adversarial Networks)という機械学習の技術と、「Fashion-MNIST」という服飾画像のデータセットを用いて、靴の画像生成に挑戦します。

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

図3 各種ライブラリをインポートするためのコード

%matplotlib inline 
import tensorflow as tf 
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.datasets import fashion_mnist
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense, Flatten, Reshape, LeakyReLU
from tensorflow.keras.optimizers import Adam
from google.colab import drive

図4 モデルの入力次元を設定するためのコード

img_rows = 28 #画像の横軸
img_cols = 28 #画像の縦軸
channels = 1  #画像のチャンネル数(モノクロは「1」、カラーは「3」)
img_shape = (img_rows, img_cols, channels)
z_dim = 100   #ノイズベクトルの次元数

図5 生成器のモデルを構築する関数を定義するためのコード

# 生成器モデル構築(画像形状情報:tuple, 入力ノイズベクトル次元数:int)
def generatorBuilder(img_shape, z_dim):
  model = Sequential()
  # 全結合層(ノード数:int, 入力の形状(第1層のときのみ):z_dim(int))
  model.add(Dense(128, input_dim=z_dim))
  # LeakyReLUによる活性化(alpha=負の場合の傾き:double)
  model.add(LeakyReLU(alpha = 0.2))
  # 出力層(全結合層)と活性化(ノード数:int, activation = :活性化関数(string))
  model.add(Dense(28*28*1, activation="tanh"))    
  # 整形(整形後の形状:img_shape(tuple))
  model.add(Reshape(img_shape))
  return model

図9 生成器のモデルを構築する関数を定義するためのコード

# 識別器モデル構築(画像形状情報:tuple(rows:int,cols:int,channels:int)
def discliminatorBuilder(img_shape):
  model = Sequential()
  # データを並べる(第1層なので入力の形状:img_shape(tuple))
  model.add(Flatten(input_shape=img_shape))  
  # 全結合層(ノード数:int)
  model.add(Dense(128))  
  # LeakyReLUによる活性化(alpha= : double)
  model.add(LeakyReLU(alpha = 0.01))
  #出力層と活性化(ノード数:int, activation = :活性化関数(string))
  model.add(Dense(1, activation="sigmoid"))  
  return model

図11 GANのシステムモデルを構築する関数を定義するためのコード

# GANモデル構築関数
def ganBuilder(generator, discriminator):
  model = Sequential()
  model.add(generator)
  model.add(discriminator)
  return model

図12 学習モデルをコンパイルするためのコード

# 識別器モデル作成(入力形状:img_shape(tuple))
discriminator = discliminatorBuilder(img_shape)  
# 識別器コンパイル
# (loss = :損失関数, optimizer = :最適化手法, metrics = :評価関数)
discriminator.compile(loss = "binary_crossentropy",
                      optimizer=Adam(),
                      metrics=['accuracy'])
# 生成器モデル作成(出力形状:img_shape(tuple), 入力形状:z_dim(int))
generator = generatorBuilder(img_shape, z_dim)
discriminator.trainable = False # 識別器モデルの学習停止
# GANモデル作成(生成器モデル:generator, 識別器モデル:discriminator)
gan = ganBuilder(generator, discriminator)
# GANコンパイル:(loss = :損失関数, optimizer = :最適化手法)
gan.compile(loss = "binary_crossentropy",
            optimizer = Adam())

図13 損失などのデータを格納する変数を定義するためのコード

losses = []
accuracies = []
iteration_checkpoints = []

図14 学習用データを準備するためのコード

# 訓練用データの抽出
(trainData, trainLabel), (_, _) = fashion_mnist.load_data()
trainData = trainData / 127.5 - 1.0
trainData = np.expand_dims(trainData, axis=3)
# 真画像ラベル作成
batch_size = 128
real = np.ones((batch_size, 1))
# 偽画像ラベル作成
fake = np.zeros((batch_size, 1))
# 靴のデータの抜き出し
# trainLabelは、5がサンダル、7がスニーカ、9がブーツ
trainData = trainData.tolist()
trainShoes = []
for i in range(len(trainLabel)):
  if trainLabel[i] in [5,7,9]:
    trainShoes.append(trainData[i])

図15 学習処理用の関数を定義するコード

def train(iterations, batch_size, sample_interval):
  for iteration in range(iterations):
    # 真画像群の作成
    idx = np.random.randint(0, len(trainShoes), batch_size)
    imgs = [trainShoes[i] for i in idx]
    imgs = np.asarray(imgs)
    # 生成画像群の作成
    # ランダムノイズ作成(正規分布の平均値:float, 標準偏差:float, 形状:tuple)
    z = np.random.normal(0,1,(batch_size, z_dim))
    # 生成画像の取得
    gen_imgs = generator.predict(z)
    # 識別器訓練:真偽のそれぞれで実施
    d_loss_real = discriminator.train_on_batch(imgs, real)
    d_loss_fake = discriminator.train_on_batch(gen_imgs, fake)
    # 損失と正確さを計算(2損失の合算を半分にする)
    d_loss, accuracy = 0.5 * np.add(d_loss_real, d_loss_fake)
    ## 生成画像群の作成(GAN学習用)
    z = np.random.normal(0,1,(batch_size, z_dim))
    g_loss = gan.train_on_batch(z, real)
    if (iteration + 1) % sample_interval == 0:
      # 記録と出力
      losses.append((d_loss, g_loss))
      accuracies.append(100.0 * accuracy)
      iteration_checkpoints.append(iteration + 1)
      print("%d [D loss: %f, acc: %.2f%%][G loss: %f]" %
            (iteration + 1, d_loss, 100.0*accuracy, g_loss))
      ## 学習済みgeneratorを渡す(iteration+1は保存に使う)
      showSample(generator, iteration+1)

図16 画像の表示と保存をする関数を定義するコード

# 画像生成と表示
# (学習済みgenerator:TensorFlowSequential, 画像表示用の行, 列:int)
def showSample(generator, iteration, img_grid_rows=4, img_grid_cols=4):
  z = np.random.normal(0, 1, (img_grid_rows*img_grid_cols, z_dim))
  gen_imgs = generator.predict(z)  # 画像生成
  gen_imgs = 0.5 * gen_imgs + 0.5  # 画素値を[0, 1]でスケーリング
  #画像の表示領域の確保(画像表示の行, 列, 画像サイズ, 表示画像の行と列を共有)
  fig,axs = plt.subplots(img_grid_rows,
                         img_grid_cols,
                         figsize=(4,4),
                         sharey=True,
                         sharex=True)
  cnt = 0
  #画像の表示
  for i in range(img_grid_rows):
    for j in range(img_grid_cols):
      axs[i, j].imshow(gen_imgs[cnt, :, :, 0], cmap='gray')
      axs[i,j].axis('off')
      cnt += 1
  # 現在のfigをGoogleドライブのMy Driveに保存
  fig.savefig('./gdrive/My Drive/'+str(iteration)+'FMShoesfig.png')

Jetson & Pi 電力測定ボードの販売開始

投稿日:2021.01.20 | カテゴリー: コード
Jetson Nano & Pi 電力測定ボード

 シェルスクリプトマガジン Vol.69(2020年12月号)の特集1で扱った、Jetson Nano 開発キットとRaspberry Piの両方で利用できる拡張基板「Jetson Nano & Pi 電力測定ボード」の販売を、自社サイトでも開始いたしました。特別価格の4540円(別途送料360円)で購入できます。
 Jetson Nano & Pi 電力測定ボードは、デジタル電流・電圧・電力計モジュール「INA260」と単色有機ELディスプレイ「SSD1306」(OLEDモジュール)を搭載しています。INA260で、Jetson Nano 開発キットやRaspberry Piの消費電力をリアルタイムに測定できます。そして、SSD1306にはさまざまな情報を表示可能です。

単色有機ELディスプレイ「SSD1306」

 Jetson Nano & Pi 電力測定ボードを購入して、Jetson Nano 開発キットとRaspberry Piを便利に使いましょう。

購入ページはこちらです。

特集1 Jetson & Pi 電力測定ボードの使い方(Vol.69記載)

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

著者:北崎 恵凡

ユーザーコミュニティ「Jetson Japan User Group」のメンバーである筆者が設計した、小型コンピュータボードの「Jetson Nano」と「Raspberry Pi」で共通に使える拡張基板「Jetson & Pi 電力測定ボード」をシェルスクリプトマガジンオリジナルとして作成しました。本特集では、このJetson & Pi電力測定ボードの使い方を紹介します。

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

Part1 Jetson & Pi 電力測定ボードを動かす

図7 電源電圧を表示するサンプルプログラム「ina260.py」のソースコード

import smbus
i2c = smbus.SMBus(1)
word = i2c.read_word_data(0x40, 0x02) & 0xFFFF
result = ( (word << 8) & 0xFF00 ) + (word >> 8)
volt = result * 1.25 / 1000

図9 ina260_adafruit.pyのソースコード

import time
import board
import adafruit_ina260

i2c = board.I2C()
ina260 = adafruit_ina260.INA260(i2c)
while True:
    print("Current: %.2f Voltage: %.2f Power: %.2f"
        %(ina260.current, ina260.voltage, ina260.power))
    time.sleep(1)

図12 ina260_plot.pyのソースコード

(略)
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
(略)
NUM_BUF_POINTS = 180
PLOT_INTERVAL = 1000

def get_values():
    return([float("{0:.2f}".format(ina260.current / 1000)), float("{0:.2f}".format(ina260.voltage)), float("{0:.2f}".format(ina260.power / 1000))])
(略)
def plot(i):
    global Data

    zone_names = get_names()
    zone_temps = get_values()
    print(zone_temps)
    Data = np.append(Data, np.array([zone_temps]), axis = 0)
    if i >= NUM_BUF_POINTS:
        Data = np.delete(Data, 0, axis = 0)

    plt.cla()
    plt.plot(Data, marker = 'x')
    plt.xlim(0, NUM_BUF_POINTS)
    plt.ylim(0.0, 10.0)
    plt.title('Current Monitor', fontsize = 14)
    plt.xlabel('Time', fontsize = 10)
    plt.ylabel('Current[A],Voltage[V],Power[W]', fontsize = 10)
    plt.tick_params(labelsize=10)
    plt.grid(True)
    plt.legend(labels = zone_names, loc = 'upper left', fontsize = 10)

def main():
    global Data

    zone_names = get_names()
    print(zone_names)

    Data = np.empty((0, len(zone_names)), float)

    fig = plt.figure(figsize=(10, 4))
    ani = animation.FuncAnimation(fig, plot, fargs = (), interval = PLOT_INTERVAL)
    plt.show()
(略)

図14 ssd1306_stats.pyのソースコード

(略)
from PIL import Image, ImageDraw, ImageFont
import adafruit_ssd1306
(略)
i2c = busio.I2C(SCL, SDA)
(略)
disp = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c)
(略)
width = disp.width
height = disp.height
image = Image.new("1", (width, height))
(略)
draw = ImageDraw.Draw(image)
(略)
    cmd = "hostname -I | cut -d' ' -f1"
    IP = subprocess.check_output(cmd, shell=True).decode("utf-8")
    cmd = "top -bn1 | grep load | awk '{printf \"CPU Load: %.2f\", $(NF-2)}'"
    CPU = subprocess.check_output(cmd, shell=True).decode("utf-8")
    cmd = "free -m | awk 'NR==2{printf \"Mem: %s/%s MB  %.2f%%\", $3,$2,$3*100/$2 }'"
    MemUsage = subprocess.check_output(cmd, shell=True).decode("utf-8")
    cmd = 'df -h | awk \'$NF=="/"{printf "Disk: %d/%d GB  %s", $3,$2,$5}\''
    Disk = subprocess.check_output(cmd, shell=True).decode("utf-8")
(略)
    draw.text((x, top + 0), "IP: " + IP, font=font, fill=255)
    draw.text((x, top + 8), CPU, font=font, fill=255)
    draw.text((x, top + 16), MemUsage, font=font, fill=255)
    draw.text((x, top + 25), Disk, font=font, fill=255)
(略)
    disp.image(image)
    disp.show()

図19 ina260_oled.pyのソースコード

(略)
import adafruit_ina260
i2c2 = board.I2C()
ina260 = adafruit_ina260.INA260(i2c2)
(略)
  c = ina260.current
  v = ina260.voltage
  p = ina260.power
  print("Current: %.2f Voltage: %.2f Power: %.2f" %(c, v, p))
(略)
  draw.text((x, top + 0), "Current(mA): " + str("{0:.2f}".format(c)) + ' 	', font=font, fill=255)
  draw.text((x, top + 14), "Voltage(V): " + str("{0:.2f}".format(v)) + ' 	', font=font, fill=255)
  draw.text((x, top + 28), "Power(mW): " + str("{0:.2f}".format(p)) + ' 	', font=font, fill=255)
(略)
  disp.image(image)
  disp.show()
(略)

図21 ssd1306_jp_font.pyのソースコード

(略)
font = ImageFont.truetype('usr/share/fonts/truetype/fonts-japanese-gothic.ttf', 14)
(略)
draw.text((x, top + 0), "てすと", font=font, fill=255)
draw.text((x, top + 14), "日本語", font=font, fill=255)
(略)

図28 imagenet-console_oled.pyのソースコード

(略)
draw.text((0, 0), "分類: " + translator.convert(class_desc), font=font, fill=255)
draw.text((0, 14), "確率: " + "{:0.2f}%".format(confidence * 100), font=font, fill=255)
(略)

図32 imagenet-camera_oled.pyのソースコード

(略)
draw.text((0, 0), "分類: " + translator.convert(class_desc), font=font2, fill=255)
draw.text((0, 14), "確率: " + "{:0.2f}%".format(confidence * 100), font=font2, fill=255)
(略)

特集2 1日で理解するGoプログラミング(Vol.69記載)

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

著者:KinoCode、ハリー

「エンジニアが次に学びたい言語」のランキングでたびたび上位にランクインしているGo言語。本特集では、Goの特徴やプログラミングについての概要を、1日で学習できるようにコンパクトに解説します。この機会にぜひGoを学んでみてください。

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

図13 挨拶文を表示するサンプルプログラム「greeting.go」のコード

package main
import ("fmt")

func main(){
    fmt.Println("Good morning")
    fmt.Println("Good afternoon")
    fmt.Println("Good evening")
}

Raspberry Piを100%活用しよう(Vol.69掲載)

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

著者:米田 聡

小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第2回は、赤外線信号の受信が可能な拡張基板を扱います。

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

図3 /boot/config.txtファイルの末尾に追加する2行

dtoverlay=gpio-ir,gpio_pin=4
dtoverlay=gpio-ir-tx,gpio_pin=13

図4 /etc/lirc/lirc_options.confファイルの変更箇所

(略)
driver     = default
device     = /dev/lirc1
(略)

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

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

著者:伊藤 智博

最近は昔と比べ、ビジネス要件のレベルが高くなりました。この要件を実現するためさまざまな技術が新たに誕生していますが、それらの技術を開発者が組み合わせて使用するのは非常に困難です。第5回では、これらの技術を組み合わせて高いレベルの要件を簡単に実現するJavaフレームワークの「Quarkus」を紹介します。

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

図7 「quarkus-getting-started/src/main/java/sample/GreetingResource.java」ファイルの内容

package sample;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/hello")
public class GreetingResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "hello";
    }
}

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

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

著者:飯尾 淳

Twitterのトレンドを分析に関する解説の最終回です。これまで、TwitterのトレンドAPIを叩いてトレンドを集め、Twitter Standard search APIでトレンドに関するツイートを収集、そのデータに基づいてトレンドの構造を分析する手法を紹介しました。今回は、その日の主要なトピックは何だったかを可視化する方法を解説します。

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

図7 JSONデータを得るスクリプト(get_data.py)

#!/usr/bin/env python

import json
import urllib.request
import sys
import re

# replace urlbase to the base address of your application
# this script should be called as $ ./get_data.py 2019-02-01
# which means the argument should be a date formatted in YYYY-MM-DD
urlbase = 'http://iiojun.xyz/twt'
jsonapi = '/api/' + sys.argv[1]

word_dict = {}
words = [] 

req = urllib.request.Request(urlbase+jsonapi)
with urllib.request.urlopen(req) as res:
  content = json.loads(res.read().decode('utf8'))
  # loop for labels
  for item in content:
    label = item['label']
    wid = item['id']
    url2 = "{0}/api/trends/{1}".format(urlbase, wid)
    req2 = urllib.request.Request(url2)
    dct = {}
    # loop for words of each label
    with urllib.request.urlopen(req2) as res2:
      content2 = json.loads(res2.read().decode('utf8'))
      for item2 in content2[0]:
        word = item2['word']
        freq = item2['freq']
        dct[word] = freq
        # keep the all words used in the nodes in the array 'words'
        if not word in words: words.append(word)
      # keep the ary of {word, freq} in the dictionary 'word_dict'
      word_dict[label] = dct
print("label", end="")
for item in words:
  print("\t{0}".format(item), end="")
print()

for key in word_dict:
  print(key, end="")
  dct = word_dict[key]
  for item in words:
    freq = dct[item] if item in dct else 0.0
    print("\t%6.3f" % freq, end="")
  print()

図9 コサイン類似度を計算するスクリプト(calc_cos_sim.py)

#!/usr/bin/env python3

import numpy as np
import sys

def cos_sim(v1, v2):
  return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))

lines = sys.stdin.readlines()

lines.pop(0)
wdic = {}
visited = []

for line in lines:
  words = line.split('\t')
  label = words.pop(0)
  wdic[label] = list(map(lambda x: float(x),words))

for key1 in wdic:
  for key2 in wdic:
    visited.append(key2+key1)
    if key1 == key2 or key1+key2 in visited: continue
    print("{0}\t{1}\t{2:9.6f}"
       .format(key1,key2,cos_sim(wdic[key1],wdic[key2])))

図11 dotスクリプトを作成するスクリプト(mk_net.rb)

#!/usr/bin/ruby

word_ary = []
freq_ary = []
node_set = []
node_color = {}
color_tbl = %w(khaki lemonchiffon aliceblue mistyrose coral
  goldenrod aquamarine lavender palegreen gold lightpink 
  plum yellow lightcyan lavenderblush gainsboro 
  yellowgreen lightsteelblue palegoldenrod lightskyblue 
  greenyellow plum cornflowerblue)
th_val = 0.75
mthd = 'fdp'
title = 'topicmap'

STDIN.each {|line|
  (kw1,kw2,freq) = line.split(/\t/)
  word_ary.push(kw1) unless word_ary.include?(kw1)
  word_ary.push(kw2) unless word_ary.include?(kw2)
  if freq.to_f > th_val
    freq_ary.push(line)
    flag = false
    node_set.each {|x|
      if x.include?(kw1) && x.include?(kw2)
        flag = true
        break
      end
      len0 = x.length
      x.push(kw2) if x.include?(kw1) && !x.include?(kw2)
      x.push(kw1) if !x.include?(kw1) && x.include?(kw2)
      if len0 < x.length
        flag = true
        break
      end
    }
    node_set.push([kw1, kw2]) unless flag
  end 
}

def get_set(ary_of_ary, x)
  ret_ary = []
  ary_of_ary.each {|ary|
    ret_ary = ret_ary + ary if ary.include?(x)
  }
  return ret_ary
end
def delete_set(ary_of_ary, x)
  ary_of_ary.each {|ary|
    ary_of_ary.delete(ary) if ary.include?(x)
  }
end

freq_ary.each {|x|
  (kw1,kw2,freq) = x.split(/\t/)
  x1 = get_set(node_set, kw1)
  x2 = get_set(node_set, kw2)
  next if (x1 == x2)
  x3 = x1 | x2
  delete_set(node_set, kw1)
  delete_set(node_set, kw2)
  node_set.push(x3)
}

word_ary.map {|x| node_color[x] = 'white' }

node_set.each_with_index {|value,index|
  i = index % color_tbl.length
  value.map{|x| node_color[x] = color_tbl[i] }
}

print "graph \"#{title}\" {\n"
print " graph [\n    layout = #{mthd}\n  ];\n"

word_ary.each {|x|
  printf "  \"%s\" [ fontname = \"ヒラギノ丸ゴ\"; style = \"filled\"; fillcolor = \"%s\"; fontcolor = \"%s\" ];\n", x, node_color[x], 'black'
}

while (!freq_ary.empty?) do
  (f,t,prob) = freq_ary.shift.split(/\t/)
  printf "  \"%s\" -- \"%s\" [label = \"%4.2f\"];\n", f, t, prob
end

print "}\n"

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

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

著者:石塚 美伶

今回は、音楽の「耳コピ」を支援する音源可視化ツールをPythonで制作する方法について紹介します。制作するツールは、WAVファイルを読み取って音を判別し、その音をピアノの鍵盤で示すGIFアニメーションを生成します。

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

図4 「main.py」ファイルに記述する内容(その1)

import scipy.io.wavfile             # WAVファイルを読み込むために使用
from scipy import signal            # 極大値を求めるために使用
import numpy as np                  # データの整形や高速フーリエ変換に使用
import pandas as pd                 # ピアノの音階辞書を作成するために使用
import matplotlib.pyplot as plt     # 図の作成に使用
import matplotlib.animation as anm  # アニメーション作成に使用
import matplotlib.patches as pat    # 長方形を描画するために使用
import time                         # 時間を表示するために使用

図5 「main.py」ファイルに記述する内容(その2)

wav_filename = "./source.wav"
# 音声ファイルの読み込み
rate, data = scipy.io.wavfile.read(wav_filename)

print("サンプリング周波数:", rate)
print("データ数:", len(data))

if data.ndim < 2:  # 配列の次元数
    print("モノラル")
else:
    print("ステレオ")
    data = np.ravel(data)[::2]  # 連結して偶数要素を抽出する

#(振幅)の配列を作成 (「-1」~「1」の範囲に正規化)
data = data / 32768

# データを0.1秒ごとに分割
split_datas = np.array_split(data, int(len(data) / (rate * 0.1)))

図6 「main.py」ファイルに記述する内容(その3)

count = 0
ex_freqency = []  # 抽出したデータを格納するために用意
for short_data in split_datas:
    ex_freqency.append([])  # データを格納するために空リストを追加
    # フーリエ変換により周波数成分と振幅を取得
    fft_short_data = np.abs(np.fft.fft(short_data))    
    freqList = np.fft.fftfreq(short_data.shape[0], d=1.0/rate)

    maxid = signal.argrelmax(fft_short_data, order=2)  # 極大値を求める
    for i in maxid[0]:
        if fft_short_data[i] > 10 and 25 < freqList[i] < 4200:
            ex_freqency[count].append(freqList[i])  # 周波数を格納
    count += 1

図8 「main.py」ファイルに記述する内容(その4)

piano_dic = pd.read_csv("./piano_dict.csv", encoding="utf-8")
print(piano_dic)

black_keys = piano_dic[piano_dic["scaleNameEn"].str.contains("#")].index
print(black_keys)

count = 0
keys = []  # 含まれる周波数の行
for row in ex_freqency:
    keys.append([])  # 各フレームの周波数を格納するために空リストを追加
    for i in row:
        # 差が最小の音階
        key = piano_dic.loc[abs(piano_dic.frequency - i).idxmin(), "keyNumber"]
        if (key in keys[count]) == False:
            keys[count].append(key)  # 重複してなければ音階を追加
    count += 1
print(keys)

図9 「main.py」ファイルに記述する内容(その5)

fig, ax = plt.subplots(figsize = (10, 2))

# 各フレームの描画
def update(i, fig_title, data_list, ax):
    if i != 0:
        plt.cla()  # 現在描写されているグラフを消去
    ax.axis("off") # 軸と目盛りを削除
    ax.set_xlim(0, 52.1)
    ax.set_ylim(-0.5, 2.5)
    skip = False
    white_count = 0
    plt.title(fig_title + time.strftime("%M:%S", time.gmtime(i * 0.1)))
    for j in range(0, 88):
        if skip == True:
            skip = False  # フラグを降ろす
            continue      # 既に描画した白鍵をスキップする
        if j in black_keys:
            # 黒鍵の右側の白鍵を描画
            color = "white"
            if j + 1 in data_list[i]:
                color = "red"  # 音が鳴っていれば色を赤にする
            # 長方形を作成
            rec = pat.Rectangle(xy = (white_count, 0), \
                                width = 1, height = 1.5, \
                                fc = color, ec = "black")
            ax.add_patch(rec)  # Axesに長方形を追加
            skip = True        # スキップフラグを立てる
            # 白鍵の上に黒鍵を描画
            color = "gray"
            x, y = white_count - 0.3, 0.5
            w, h = 0.6, 1
        else:
            # 白鍵を描画
            color = "white"
            x, y = white_count, 0
            w, h = 1, 1.5
        if j in data_list[i]:
            color = "red"    # 音が鳴っていれば色を赤にする
        # 長方形を作成 
        rec = pat.Rectangle(xy = (x, y), width = w, \
                            height = h, fc = color, ec = "black")
        ax.add_patch(rec)    # Axesに長方形を追加
        white_count += 1     # 白鍵の数をカウント

# アニメーションを生成
ani = anm.FuncAnimation(fig, update, fargs=("Mimicopy ", keys, ax), \
                        interval=100, frames=len(keys))
# GIFファイルとして保存
ani.save("Sample.gif", writer="pillow")

中小企業手作りIT化奮戦記(Vol.69掲載)

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

著者:菅 雄一

 2000年にLinuxサーバーを導入して以来、筆者は、主に費用面での手軽さからOSS(オープンソースソフトウエア)を活用したシステム構築に取り組んできた。だが、2020年になってWindowsサーバーに初めて触れることになった。勤務先の会社が、バッファローの「TeraStation」という法人向けのNAS(Network Attached Storage)製品を購入し、そのOSがWindowsサーバーだったからだ。今回は、そのWindowsサーバー搭載のNAS製品を設定した話を書く。

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

図3 Sambaの設定ファイル(smb.conf)の記述例

[global]
map to guest = bad user
guest account = nobody

[public]
path = /home/samba
guest ok = yes

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

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

筆者:大津 真

本連載ではシェルスクリプトの書き方をやさしく紹介します。対象とするシェルは、多くのLinuxディストリビューションが標準シェルとして採用する「Bash」です。第5回は、複数の文字列を柔軟なパターンで指定できる正規表現の基礎について解説します。

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

図2 シェルスクリプト「pref1.sh」の内容

#!/bin/bash

if [[ $# -ne 1 ]]; then
    echo "都道府県名を一つ指定してください"
    exit 1
fi

file=meibo.txt
if grep ":${1}$" $file; then
    count=$(grep -c ":${1}$" $file)
    echo "-- ${1}は${count}件ありました --"
else
    echo "-- ${1}は見つかりませんでした --"
fi

図3 シェルスクリプト「whileRead1.sh」の内容

#!/bin/bash

while read line
do
    echo $line
done < meibo.txt

図4 シェルスクリプト「whileRead2.sh」の内容

#!/bin/bash

cat meibo.txt | while read line
do
    echo $line
done

図5 シェルスクリプト「tokyo.sh」の内容

#!/bin/bash

count=0
grep ":東京$" meibo.txt | while read line
do
    echo "$((++count)):${line}"
done

図6 シェルスクリプト「addpref.sh」の内容

#!/bin/bash

while read line
do
    if echo $line | grep -q ":東京$"; then
        echo $line | sed "s/:東京$/:東京都/"
    elif echo $line | grep -q -e ":大阪$" -e ":京都$"; then
        echo $line | sed "s/:\(..\)$/:\1府/"
    elif echo $line | grep -q ":北海道"; then
        echo $line
    else
        echo $line | sed "s/:\(..*\)$/:\1県/"
    fi
done < meibo.txt

Vol.69 補足情報

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

Techパズル

p.92の問題①は、

ではなく、

です。お詫びして訂正いたします。
※ PDF版とKindle版では訂正を反映する予定です。

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

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

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

004 レポート MacのApple M1
005 レポート セキュリティゲーム「TERMINAL」
006 NEWS FLASH
008 特集1 Jetson & Pi 電力測定ボードの使い方/北崎恵凡 コード掲載
026 特集2 1日で理解するGoプログラミング/KinoCode、ハリー コード掲載
036 特別企画 Redmineで始めるプロジェクト管理/前田剛
048 Raspberry Piを100%活用しよう/米田聡 コード掲載
051 Hello Nogyo!
052 レッドハットのプロダクト/伊藤智博 コード掲載
062 Docker/桑原滝弥、イケヤシロウ
064 バーティカルバーの極意/飯尾淳 コード掲載
070 法林浩之のFIGHTING TALKS/法林浩之
072 香川大学SLPからお届け!/石塚美伶 コード掲載
076 円滑コミュニケーションが世界を救う!/濱口誠一
078 中小企業手作りIT化奮戦記/菅雄一 コード掲載
084 シェルスクリプトの書き方入門/大津真 コード掲載
092 Techパズル/gori.sh
094 コラム「長続きの秘訣」/シェル魔人

Vol.68 補足情報

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

特集1 Windows 10でWSL 2を使おう

WSL 2ですが、Windows 10 May 2020 Updateを適用したWindows 10 バージョン2004以降だけでなく、バージョン1909や1903にもバックポートされて使えるようになりました。詳しくは、こちらを参照してください。

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

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

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

004 レポート 暗号通信のTLS1.2以前に脆弱性
005 NEWS FLASH
008 特集1 Windows 10でWSL 2を使おう/三沢友治
020 特集2 高機能CMS Drupal入門/小薗井康志
034 特別企画 量子コンピュータの基礎を知る/沼田祈史、小林有里
046 Raspberry Piを100%活用しよう/米田聡 コード掲載
049 Hello Nogyo!
050 レッドハットのプロダクト/小杉研太 コード掲載
059 中小企業手作りIT化奮戦記/菅雄一
064 法林浩之のFIGHTING TALKS/法林浩之
066 香川大学SLPからお届け!/山下賢治 コード掲載
072 RPA/桑原滝弥、イケヤシロウ
074 Webアプリの正しい作り方/しょっさん コード掲載
084 円滑コミュニケーションが世界を救う!/濱口誠一
086 バーティカルバーの極意/飯尾淳 コード掲載
092 シェルスクリプトの書き方入門/大津真 コード掲載
100 Techパズル/gori.sh
102 コラム「人生の入り口」/シェル魔人

Raspberry Piを100%活用しよう(Vol.68掲載)

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

著者:米田 聡

小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第1回は、電子ペーパーディスプレイ搭載の拡張基板を扱います。

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

図3 電子ペーパーディスプレイに文字を表示するサンプルプログラム(text.py)

from inky import Inky
from PIL import Image, ImageFont, ImageDraw

DEFAULT_FONT = '/usr/share/fonts/truetype/fonts-japanese-gothic.ttf'
FONT_SIZE = 24
LINE_HEIGHT = 26

ink = Inky()
# 2値イメージの作成
image = Image.new('P',(ink.width, ink.height))
draw = ImageDraw.Draw(image)
font = ImageFont.truetype(DEFAULT_FONT, FONT_SIZE)

# 文字描画
draw.text((0, 0), "シェルスクリプト"  , font=font, fill=1)
draw.text((0,26), "マガジン"          , font=font, fill=1)
draw.text((0,52), "ゼロ・ワンシリーズ", font=font, fill=1)
draw.text((0,78), "電子ペパーモニタ"  , font=font, fill=1)
# セットして表示
ink.set_image(image)
ink.show()

図5 電子ペーパーディスプレイに画像を表示するサンプルプログラム(logo.py)

from inky import Inky
from PIL import Image

ink = Inky()

img = Image.open("shelllogo.png")
# サイズ変換
img = img.resize((ink.width, ink.height))
# 2値画像への変換
img = img.convert('1', dither=True)
# セットして表示
ink.set_image(img)
ink.show()

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

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

著者:小杉 研太

前回(第3回)に引き続き、アプリやデータの連携を実現するためのミドルウエア製品「Red Hat Integration」を紹介します。第4回はRed Hat Integrationに含まれる「Red Hat AMQ」と「Red Hat Fuse」のアップストリームとなる「Strimzi」と「Apache Camel」について触れます。

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

図17 KafkaとSlackを統合できるコード

FromKafkaToSlack.java
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.slack.SlackComponent;

public class FromKafkaToSlack extends RouteBuilder {

    @Override
    public void configure() throws Exception {
        final SlackComponent slackComponent = (SlackComponent) this.getContext().getComponent("slack");
        slackComponent.setWebhookUrl(Webhook URL);

        from("kafka:my-topic?brokers=my-cluster-kafka-bootstrap:9092")
            .routeId("from-kafka-to-slack")
            .to("slack:#my-kafka-project");
    }
}

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

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

著者:山下 賢治

初めまして。香川大学 工学研究科 修士1年の山下賢治です。今回は、JavaScriptライブラリ「React」と、API向けのクエリー言語「GraphQL」を用いて、GitHubで公開されているリポジトリの検索Webアプリケーションを作成します。リポジトリの絞り込みには、開発言語とスター数を利用します。

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

図3 「src/auth.js」ファイルに記述する内容

import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

const httpLink = createHttpLink({
  uri: 'https://api.github.com/graphql',
});

const authLink = setContext(() => {
  const TOKEN = process.env.REACT_APP_TOKEN;
  return {
    headers: {
      Authorization: Bearer ${TOKEN},
    },
  };
});

export const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache(),
});

図4 「src/index.js」ファイルに記述する内容

import React from 'react';
import ReactDOM from 'react-dom';
import { ApolloProvider } from '@apollo/client';
import { client } from './auth';
import RepoInfo from './components/RepoInfo';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(
  <React.StrictMode>
    <ApolloProvider client={client}>
      <RepoInfo />
    </ApolloProvider>
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

図5 「src/graphql/index.js」ファイルに記述する内容

import { gql } from '@apollo/client';
export const SEARCH_REPO = gql
  query getData($queryString: String!) {
    search(query: $queryString, type: REPOSITORY, first: 10) {
      nodes {
        ... on Repository {
          databaseId
          nameWithOwner
          openGraphImageUrl
        }
      }
    }
  }
;

図6 「src/components/RepoInfo.jsx」ファイルに記述する内容

import React, {useState, useEffect, useCallback} from 'react';
import {useLazyQuery} from '@apollo/client';
import { SEARCH_REPO } from '../graphql';

const useRepoData = () => {
  const [getData, {loading, error, data}] = useLazyQuery(SEARCH_REPO)
  const [query, setQuery] = useState('')
  const fetchData = useCallback(() => {
    getData({
      variables: {
        queryString: query
      }
    });
  }, [getData, query]);
  useEffect(() => {
    fetchData()
  }, [fetchData])
  return [setQuery, {loading, error, data}]
}
const RepoInfo = () => {
  const [fetchData, {loading, error,data}] = useRepoData()
  const handleOnClick = () => {
    fetchData(language:python stars:>100)
  }
  if (loading) return <p>Loading Repository</p>
  if (error) return <p>Error while searching Repository</p>
  return (
    <>
      <button onClick={handleOnClick}>
        search
      </button>
      {
        data ? data.search.nodes.map(
          repo =>
            <div key={repo.databaseId}>
              <h2>{repo.nameWithOwner}</h2>
              <img src={repo.openGraphImageUrl} alt='repoImage' />
            </div>
        )
          : <></>
      }
    </>
  )
}
export default RepoInfo;

図9 変更後の「src/components/RepoInfo.jsx」ファイルの内容

import React, {useState, useEffect, useCallback} from 'react';
import {useLazyQuery} from '@apollo/client';
import { SEARCH_REPO } from '../graphql';

const useInput = initialValue => {
  const [value, set] = useState(initialValue)
  return {value, onChange: (e) => set(e.target.value)}
}
const useRepoData = () => {
  const [getData, {loading, error, data}] = useLazyQuery(SEARCH_REPO)
  const [query, setQuery] = useState('')
  const fetchData = useCallback(() => {
    getData({
      variables: {
        queryString: query
      }
    });
  }, [getData, query]);
  useEffect(() => {
    fetchData()
  }, [fetchData])
  return [setQuery, {loading, error, data}]
}
const RepoInfo = () => {
  const stars = useInput(0)
  const language = useInput('')
  const [fetchData, {loading, error,data}] = useRepoData()
  const handleOnClick = () => {
    fetchData(language:${language.value} stars:>${stars.value})
  }
  if (loading) return <p>Loading Repository</p>
  if (error) return <p>Error while searching Repository</p>
 return (
    <>
      <label>
        Language :
        <input type='text' {...language} />
      </label>
      <label>
        More than :
        <input type='text' {...stars} />
        Stars
      </label>
      <button onClick={handleOnClick}>
        search
      </button>
      {
        data ? data.search.nodes.map(
          repo =>
            <div key={repo.databaseId}>
              <h2>{repo.nameWithOwner}</h2>
              <img src={repo.openGraphImageUrl} alt='repoImage' />
            </div>
        )
          : <></>
      }
    </>
  )
}
export default RepoInfo;

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

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

著者:しょっさん

ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならないでしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。第8回は、第1回で述べた「クリーンアーキテクチャ」に習って、ロジックと、フレームワークやライブラリ、その他の処理を分離します。

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

図1 経費精算申請を処理しているコード

import { Request, Response, NextFunction } from "express";
import Express from "express";
import { Expense } from "../models/expense";
const router = Express.Router();

// POST 経費の入力
router.post("/", (req: Request, res: Response, next: NextFunction) => {
  Expense.create(req.body)
    .then((result) => {
      res.status(200).json(result);
    })
    .catch((err) => {
      console.log(err);
      res.status(400).json({ id: 20002, message: err });
    });
});

図3 「User Entity」オブジェクト

class User {
  id: number;
  name: string;
  salaray: number;
}

図5 「common/index.ts」ファイル

export enum approval_status {
  minimum,
  unapproved,
  approved,
  reject,
  reimburse,
  maximum,
}

// エンティティ用のオブジェクトの基本構成
export abstract class EntityObject<T> {
  protected props: T;

  protected constructor(props: T) {
    this.props = props;
  }
}

// プリミティブ型のビジネスルール実装のための基本構成
export abstract class PrimitiveObject<T> extends EntityObject<T> {
  get value(): T {
    return this.props;
  }
}

図6 「domains/expenseEntity.ts」ファイル

import { EntityObject, approval_status, PrimitiveObject } from "../common";

export const MAX_LENGTH = 64;
export const MAX_AMOUNT = 1000000;

// 費目名のルール
class Type extends PrimitiveObject<string> {
  static create(value: string): Type {
    if (value.length > MAX_LENGTH || value.length <= 0)
      throw new Error("費目名が長すぎるか、ありません");
    return new Type(value);
  }
}

// 承認コードのルール
class Approval extends PrimitiveObject<approval_status> {
  static create(value: approval_status = approval_status.unapproved): Approval {
    if (value <= approval_status.minimum || value >= approval_status.maximum)
      throw new Error("承認コードがおかしい");
    return new Approval(value);
  }
}

// 請求金額のルール
class Amount extends PrimitiveObject<number> {
  static create(value: number): Amount {
    if (value <= 0 || value >= MAX_AMOUNT)
      throw new Error("請求金額が範囲を超えている");
    return new Amount(value);
  }
}

// 経費精算で利用されるクラスの実態
interface IExpenseProps {
  id?: number | undefined;
  user_id: string;
  user_name?: string;
  date: Date;
  type: Type;
  description?: string | null;
  approval: Approval;
  amount: Amount;
}

// オブジェクトを構成する要素
export interface IExpenseValue {
  id?: number | undefined;
  user_id: string;
  user_name?: string;
  date: Date;
  type: string;
  description?: string | null;
  approval: approval_status;
  amount: number;
}

export class ExpenseEntity extends EntityObject<IExpenseProps> {
  constructor(props: IExpenseProps) {
    super(props);
  }

  set approval(status: approval_status) {
    this.props.approval = Approval.create(status);
  }

  static create(values: IExpenseValue): ExpenseEntity {
    return new ExpenseEntity({
      id: values.id,
      user_id: values.user_id,
      user_name: values.user_name,
      date: values.date,
      type: Type.create(values.type),
      description: values.description,
      approval: Approval.create(values.approval),
      amount: Amount.create(values.amount),
    });
  }

  public read(): IExpenseValue {
    return {
      id: this.props.id,
      user_id: this.props.user_id,
      user_name: this.props.user_name,
      date: this.props.date,
      type: this.props.type.value,
      description: this.props.description,
      approval: this.props.approval.value,
      amount: this.props.amount.value,
    };
  }
}

図7 「usecases/SubmitExpense.ts」ファイル

import { IExpenseRepository } from "./IExpenseRepository";
import { ExpenseEntity, IExpenseValue } from "../domains/expenseEntity";

export class SubmitExpense {
  private _expenseRepository: IExpenseRepository;

  constructor(expenseRepository: IExpenseRepository) {
    this._expenseRepository = expenseRepository;
  }

  execute(expense: IExpenseValue) {
    const e = ExpenseEntity.create(expense);
    return this._expenseRepository.store(e);
  }
}

図8 「usecases/IExpenseRepository.ts」ファイル

import { ExpenseEntity } from "../domains/expenseEntity";

export interface IExpenseRepository {
  findAllApproved(): Promise<ExpenseEntity[]>;
  findAllRejected(): Promise<ExpenseEntity[]>;
  findUnapproval(id: string): Promise<ExpenseEntity[]>;
  updateApproval(id: number, expense: ExpenseEntity): Promise<ExpenseEntity>;
  findById(id: number): Promise<ExpenseEntity>;
  update(expense: ExpenseEntity): Promise<ExpenseEntity>;
  store(expense: ExpenseEntity): Promise<ExpenseEntity>;
}

図9 「interfaces/ExpenseController.ts」ファイル

import { SubmitExpense } from "../usecases/SubmitExpense";
import { IExpenseValue } from "../domains/expenseEntity";
import { ExpenseRepository } from "./expenseRepository";

export class ExpenseController {
  async submitExpenseController(expense: IExpenseValue): Promise<IExpenseValue> {
    const expenseRepository = new ExpenseRepository();

    try {
      const usecase = new SubmitExpense(expenseRepository);
      const result = await usecase.execute(expense);
      return result.read();
    } catch (error) {
      throw new Error(error);
    }
  }
}

図10 「interfaces/ExpenseRepository.ts」ファイル

import { Expense } from "../../models/expense";
import { approval_status } from "../common";
import { ExpenseEntity } from "../domains/expenseEntity";
import { IExpenseRepository } from "../usecases/IExpenseRepository";

export class ExpenseRepository implements IExpenseRepository {
  findAllApproved(): Promise<ExpenseEntity[]> {
    return Expense.findAll({
      where: {
        approval: approval_status.approved,
      },
    }).then((results) => {
      return results.map((value, index, array) => {
        return ExpenseEntity.create(value);
      });
    });
  }
(略)
  store(e: ExpenseEntity): Promise<ExpenseEntity> {
    return Expense.create(e.read())
      .then((result) => {
        return ExpenseEntity.create(result);
      })
      .catch((err) => {
        throw new Error("請求処理が失敗しました");
      });
  }
}

図11 「expense.ts」ファイル

// POST 経費の入力
router.post("/", (req: Request, res: Response, next: NextFunction) => {
  const e = new ExpenseController();

  e.submitExpenseController(req.body!)
    .then((result) => {
      res.status(200).json(result);
    })
    .catch((err) => {
      res.status(400).json({ id: "20201", message: err });
    });
});

図13 「index.ts」ファイル

// API
app.use("/api/auth", auth);
app.use("/api/expense", Authorization.isAuthorized, expense);
app.use(
  "/api/payment",
  Authorization.isAuthorized,
  Authorization.isAccounting,
  payment
);
app.use(
  "/api/approval",
  Authorization.isAuthorized,
  Authorization.isBoss,
  approval
);

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

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

著者:飯尾 淳

Twitterのトレンド分析に関する解説も、とうとう3回目に突入しました。今回は、共起ネットワークグラフの描画処理について説明します。描画にはD3.jsというグラフ描画フレームワークを使います。この描画処理では、データの受け渡し方法にちょっとした工夫をしており、それについても解説します。

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

図2 トレンド表示画面を構成するビューのソースコード

<h2>
  <%= link_to @trend.label,
              "https://twitter.com/search?q=#{@trend.label}",
              :target => '_blank' %>
</h2>
<p>
  <%= t('collected') %>
  <%= link_to l(@trend.collected, format: :long),
              "../#{@trend.collected}", :class => 'href' %>
  <%= link_to t('prev_item'), trend_path(@prev),
              :class => 'href' if @prev != nil %>
  <%= link_to t('next_item'), trend_path(@next),
              :class => 'href' if @next != nil %>
</p>
<div id="graph_canvas" data-src="<%= api_trend_path(@trend) %>">
</div> 

図5 サーバー側の処理をするRailsのコントローラのコード

class Api::TrendsController < ApplicationController
  def index
    render json: Trend.where(collected: params[:date])
  end

  def show
    l = []
    @trend = Trend.find(params[:id])
    l.push(@trend.nodes)
    @trend.nodes.each {|n|
      l.push(n.links)
    }
    render json: l
  end
end 

図6 クライアント側の処理をするコード

$(document).on('turbolinks:load', function() {
  if ($('#graph_canvas').attr('data-src') != undefined) {
    $.ajax({
      url:      $('#graph_canvas').attr('data-src'),
      dataType: 'json',
      success:  function(data) { drawGraph(data); },
      error:    function(data) { alert('error'); }
    });
  }
});
function drawGraph(data) {
    "use strict"
    var width, height, chartWidth, chartHeight, margin
    d3.select("#svg").remove()
    var svg = d3.select("#graph_canvas")
                .append("svg").attr("id", "svg")
    var chartLayer = svg.append("g").classed("chartLayer", true)
    setSize()
    drawChart(convertData(data))    
    function convertData(data) {
        var nodes = data.shift()
        var n_ary = nodes.map(function(d) {
                      d['r'] = d.freq / 4 + 15; return d })
        var l_hash = {}
        var ctr = 0
       for (var n_links of data) {
            for (var link of n_links) {
                if (l_hash[link.id] == undefined) {
                    l_hash[link.id] = { line_width: link.corr / 20, 
                                        source: nodes[ctr] }
                } else { l_hash[link.id]['target'] = nodes[ctr] }
            }
            ctr++
        }
        return { nodes: n_ary, links: Object.values(l_hash) }
    }
    function setSize() {
        width = document.querySelector("#graph_canvas").clientWidth
        height = document.querySelector("#graph_canvas").clientHeight
        margin = { top:0, left:0, bottom:0, right:0 }
        chartWidth = width - (margin.left+margin.right)
        chartHeight = height - (margin.top+margin.bottom)
        svg.attr("width", width).attr("height", height)
        chartLayer
            .attr("width", chartWidth)
            .attr("height", chartHeight)
            .attr("transform",
                  "translate("+[margin.left, margin.top]+")")
    }
    function drawChart(data) {
        var STEM_LENGTH=30
        var simulation = d3.forceSimulation()
            .force("link",
             d3.forceLink().id(function(d) { return d.index }))
            .force("collide",
             d3.forceCollide(function(d) { return d.r + STEM_LENGTH })
               .iterations(16) )
            .force("charge", d3.forceManyBody())
            .force("center",
             d3.forceCenter(chartWidth / 2, chartHeight / 2))
            .force("y", d3.forceY(0))
            .force("x", d3.forceX(0))
        var link = svg.append("g")
            .attr("class", "links")
            .selectAll("line")
            .data(data.links)
            .enter()
            .append("line")
            .attr("stroke", "brown")
            .attr("stroke-width", function(d) { return d.line_width })
        var node_label = svg.append("g")
            .attr("class", "nodes")
            .selectAll("g")
            .data(data.nodes)
            .enter().append("g")
            .call(d3.drag()
                .on("start", dragstarted)
                .on("drag", dragged)
                .on("end", dragended));
        var node = node_label.append("circle")
            .attr("r", function(d) { return d.r })
            .attr("fill", function(d) {
                return (d.freq > 60.0) ?
                        "moccasin" : (d.freq > 20.0) ?
                        "lemonchiffon" : (d.freq > 5.0) ?
                        "beige" : "lavender" });
        var label = node_label.append("text")
            .attr("text-anchor", "middle")
            .attr("font-family", "Arial")
            .attr("dy", "0.5em")
            .attr("font-size", function(d) {return d.r / 1.5; })
            .text(function(d) { return d.word; })
        var ticked = function() {
            link.attr("x1", function(d) { return d.source.x; })
                .attr("y1", function(d) { return d.source.y; })
                .attr("x2", function(d) { return d.target.x; })
                .attr("y2", function(d) { return d.target.y; });
            node_label.attr("transform",
                function(d) { return "translate("+d.x+","+d.y+")"; })
        }
        simulation.nodes(data.nodes).on("tick", ticked);
        simulation.force("link").links(data.links);
        function dragstarted(d) {
            if (!d3.event.active) {
                simulation.alphaTarget(0.1).restart();
            }
            d.fx = d.x;
            d.fy = d.y;
        }
        function dragged(d) {
            d.fx = d3.event.x;
            d.fy = d3.event.y;
        }
        function dragended(d) {
            if (!d3.event.active) simulation.alphaTarget(0);
            d.fx = null;
            d.fy = null;
        }
    }
}

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

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

著者:大津 真

本連載ではシェルスクリプトの書き方をやさしく紹介します。対象とするシェルは、多くのLinuxディストリビューションが標準シェルとして採用する「Bash」です。第4回は、繰り返し処理を実現する制御構造を中心に解説します。

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

図1 リストにある果物名を一つずつ表示するシェルスクリプトの例

for name in メロン バナナ イチゴ ミカン
do
    echo $name
done

図2 シェルスクリプト「showArgs1.sh」の内容

#!/bin/bash
for name in $@
do
    echo $name
done

図3 シェルスクリプト「showArgs2.sh」の内容

#!/bin/bash
for name in "$@"
do
    echo $name
done

図4 シェルスクリプト「showArgs3.sh」の内容

#!/bin/bash
for name
do
    echo $name
done

図5 シェルスクリプト「colon_to_comma2.sh」の内容

#!/bin/bash
if [[ $# -eq 0 ]]; then
  echo "引数でファイルを指定してください"
  exit 1
fi
if [[ ! -f $1 ]]; then
  echo "$1が見つかりません"
  exit 1
fi
fname=$1
cp "$fname" "${fname}~"
tr ":" "," < "${fname}~" > "$fname"

図6 シェルスクリプト「colon_to_comma3.sh」の内容

#!/bin/bash
if [[ $# -eq 0 ]]; then
    echo "引数でファイルを指定してください"
    exit 1
fi
for file in $@
do
    if [[ ! -f $file ]]; then
        echo "$fileが見つかりません"
        exit 1
    fi
    fname=$file
    echo "変換中: $file"
    cp "$fname" "${fname}~"
    tr ":" "," < "${fname}~" > "$fname"
done

図7 シェルスクリプト「hello10.sh」の内容

#!/bin/bash
for i in $(seq 10)
do
    echo "$i: こんにちは"
done

図8 シェルスクリプト「case1.sh」の内容

#!/bin/bash
case $1 in
    [a-z]*)
        echo "アルファベット小文字で始まります"
        ;;
    [A-Z]*)
        echo "アルファベット大文字で始まります"
        ;;
    [0-9]*)
        echo "数字で始まります"
        ;;
    *)
        echo "その他"
esac

図9 シェルスクリプト「case2.sh」の内容

#!/bin/bash
for file
do
    case $file in
        *.txt)
            echo "テキスト: $file"
            ;;
        *.htm | *.html)
            echo "HTML: $file"
            ;;
        *)
            echo "その他: $file"
    esac
done

図10 シェルスクリプト「pat1.sh」の内容

#!/bin/bash
path="/home/o2/music/sample.test.mp3"
echo '${path#/*/} = ' ${path#/*/}
echo '${path##/*/} = ' ${path##/*/}
echo '${path%.*} = ' ${path%.*}
echo '${path%%.*} = ' ${path%%.*}

図11 シェルスクリプト「cgExt1.sh」の内容

#!/bin/bash
if [[ $# -eq 0 ]]; then
    echo "引数でファイルを指定してください"
    exit 1
fi

for file
do
    case $file in
        *.htm)
            newfile=${file%.*}.html
            echo "$file to $newfile"
            mv $file $newfile
            ;;
        *.jpeg)
            newfile=${file%.*}.jpg
            echo "$file to $newfile"
            mv $file $newfile
            ;;
    esac
done

図12 シェルスクリプト「while1.sh」の内容

#!/bin/bash
read -p "文字列? " str
while [[ -n $str ]]
do
    echo $str | tr "a-z" "A-Z"
    read -p "文字列? " str
done

図13 シェルスクリプト「while2.sh」の内容

#!/bin/bash
while true
do
    read -p "文字列? " str
    if [[ -z $str ]]; then
        break
    fi
    echo $str | tr "a-z" "A-Z"
done

図14 シェルスクリプト「cgExt2.sh」の内容

#!/bin/bash
if [[ $# -eq 0 ]]; then
    echo "引数でファイルを指定してください"
    exit 1
fi

for file
do
    if [[ ! -f $file ]]; then
        echo "${file}が見つかりません"
        continue
    fi
    case $file in
        *.htm)
            newfile=${file%.*}.html
            echo "$file to $newfile"
            mv $file $newfile
            ;;
        *.jpeg)
            newfile=${file%.*}.jpg
            echo "$file to $newfile"
            mv $file $newfile
            ;;
    esac
done

ラズパイセンサーボードで学ぶ電子回路の制御(Vol.67掲載)

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

著者:米田 聡

シェルスクリプトマガジンでは、小型コンピュータボード「Raspberry Pi」(ラズパイ)向けのセンサー搭載拡張ボード「ラズパイセンサーボード」を制作しました。最終回は、前回作成したスクリプトによって記録された温度と湿度をグラフ化して、Webブラウザで閲覧可能にします。

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

図1 サンプルのHTMLテンプレートファイル(~/webapp/templates/hello.html)

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>こんにちは世界</title>
</head>
<body>
    <div>{{ text }}</div>
</body>
</html>

図2 サンプルのWebアプリケーションスクリプト(~/webapp/hello.py)

from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/' , methods=['GET','POST'])
def index():
  message = 'Hello, World'
  return render_template("hello.html", text=message)

if __name__ == '__main__':
  app.run(host='0.0.0.0', port=5000)

図4 トップページのテンプレートファイル(~/webapp/templates/index.html)

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>温度・湿度グラフ化ツール</title>
</head>
<body>
  <div>グラフ化する日時の範囲を指定してください。</div>
  <form action="graph" method="post">
    <div>開始日時</div>
    <input id="startdt" type="datetime-local" name="startdt"
    min="{{ mindt }}" max="{{ maxdt }}" required> 
    <div>終了日時</div>
    <input id="enddt" type="datetime-local" name="enddt"
    min="{{ mindt }}" max="{{ maxdt }}" required> 
    <div><input type="submit" value="実行"></div>
  </form>
</body>
</html>

図5 テンプレートファイルからトップページを作成するスクリプト(~/webapp/app.py)

from flask import Flask, request,render_template
import sqlite3
import datetime

DBNAME="weather.sqlite3"

app = Flask(__name__)

@app.route('/')
def index():
  conn = sqlite3.connect(DBNAME)
  cur = conn.cursor()

  sql = "SELECT min(dt) from bme"
  cur.execute(sql)
  conn.commit()
  res = cur.fetchone()
  m = datetime.datetime.fromisoformat(res[0])
  mindt = m.strftime("%Y-%m-%dT%H:%M")

  sql = "SELECT max(dt) from bme"
  cur.execute(sql)
  conn.commit()
  res = cur.fetchone()
  m = datetime.datetime.fromisoformat(res[0])
  maxdt = m.strftime("%Y-%m-%dT%H:%M")

  conn.close()
  return render_template("index.html", maxdt=maxdt, mindt=mindt)

# ここに後でグラフ表示ページの関数を追加する

if __name__ == '__main__':
  app.run(host='0.0.0.0', port=5000)

図8 グラフを表示するページのテンプレートファイル(~/webapp/templates/graph.html)

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>グラフ</title>
</head>
<body>
  <canvas id="bmeChart"></canvas>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.js"></script>
  <script>
    var elm = document.getElementById("bmeChart");
    var bmeChart = new Chart(elm, {
      type: 'line',
      data: {
        labels: [
          {% for d in data %}
             '{{ d[0] }}',
          {% endfor %}
        ],
        datasets: [
          {
             label: '気温(度)',
             data:[ 
               {% for d in data %}
               {{ d[1] }},
               {% endfor %}
             ],
             borderColor: "rgba(255,0,0,1)",
             backgroundColor: "rgba(0,0,0,0)",
           },
           {
             label: '湿度(%)',
             data: [
               {% for d in data %}
               {{ d[2] }},
               {% endfor %}
             ],
             borderColor: "rgba(0,255,0,1)",
             backgroundColor: "rgba(0,0,0,0)",                       
           },
        ],
      },
      options: {
      },
    });
  </script>
</body>

図9 app.pyに追加する関数

@app.route('/graph', methods=['POST'])
def graph():
  if request.method == 'POST':
    startdt = datetime.datetime.fromisoformat(request.form['startdt'])
    enddt = datetime.datetime.fromisoformat(request.form['enddt'])

    conn = sqlite3.connect(DBNAME)
    cur = conn.cursor()
    sql = "SELECT dt,temp,hum from bme where dt < " + "'" + enddt.strftime("%Y-%m-%d %H:%M:%S") + "' and dt > '" + startdt.strftime("%Y-%m-%d %H:%M:%S") + "'"
    cur.execute(sql)
    conn.commit()
    res = cur.fetchall()
    conn.close()
    # データ件数が200件以上なら100件台になるよう抑える
    if len(res) > 200:
      p = int(len(res) / 100)
      res = res[::p]
  return render_template("graph.html", data=res)

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

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

著者:杉本 拓

「Red Hat Integration」はアプリやデータの連携を実現するための、インテグレーションパターン、API 連携、API管理とセキュリティ、データ変換、リアルタイムメッセージング、データストリーミングなどを提供するオープンソース製品です。同製品には多くの機能が含まれていますが、本連載ではその概要と一部の機能を紹介します。

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

図18 二つのdependencyを追加する

   <dependency>
     <groupId>org.jboss.resteasy</groupId>
     <artifactId>resteasy-jackson2-provider</artifactId>
   </dependency>
   <dependency>
     <groupId>io.apicurio</groupId>
     <artifactId>apicurio-registry-utils-serde</artifactId>
     <version>1.2.1.Final</version>
   </dependency>

図19 「AvroRegistryExample.java」ファイルの内容

package com.redhat.kafka.registry;
 
import java.io.File;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.TimeUnit;
 
import javax.enterprise.context.ApplicationScoped;
 
import org.apache.avro.Schema;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericData.Record;
import org.eclipse.microprofile.reactive.messaging.Outgoing;
 
import io.reactivex.Flowable;
import io.smallrye.reactive.messaging.kafka.KafkaRecord;
 
@ApplicationScoped
public class AvroRegistryExample {
 
   private Random random = new Random();
   private String[] symbols = new String[] { "RHT", "IBM", "MSFT", "AMZN" };
 
   @Outgoing("price-out")
   public Flowable<KafkaRecord<String, Record>> generate() throws IOException {
       Schema schema = new Schema.Parser().parse(
           new File(getClass().getClassLoader().getResource("price-schema.avsc").getFile())
       );
       return Flowable.interval(1000, TimeUnit.MILLISECONDS)
           .onBackpressureDrop()
           .map(tick -> {
               Record record = new GenericData.Record(schema);
               record.put("symbol", symbols[random.nextInt(4)]);
               record.put("price", String.format("%.2f", random.nextDouble() * 100));
               return KafkaRecord.of(record.get("symbol").toString(), record);
           });
   }
}

図20 「price-schema.avsc」ファイルの内容

{
   "type": "record",
   "name": "price",
   "namespace": "com.redhat",
   "fields": [
       {
           "name": "symbol",
           "type": "string"
       },
       {
           "name": "price",
           "type": "string"
       }
   ]
}

図21 登録されたAvroのスキーマ

{
  "createdOn": 1575919739708,
  "modifiedOn": 1575919739708,
  "id": "prices-value",
  "version": 1,
  "type": "AVRO",
  "globalId": 4
}

図22 プロパティファイル

# Configuration file
kafka.bootstrap.servers=localhost:9092
 
mp.messaging.outgoing.price-out.connector=smallrye-kafka
mp.messaging.outgoing.price-out.client.id=price-producer
mp.messaging.outgoing.price-out.topic=prices
mp.messaging.outgoing.price-out.key.serializer=org.apache.kafka.common.serialization.StringSerializer
mp.messaging.outgoing.price-out.value.serializer=io.apicurio.registry.utils.serde.AvroKafkaSerializer
 
mp.messaging.outgoing.price-out.apicurio.registry.url=http://localhost:8081/api
mp.messaging.outgoing.price-out.apicurio.registry.artifact-id=io.apicurio.registry.utils.serde.strategy.TopicIdStrategy

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

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

著者:飯尾 淳

前回に引き続き、筆者らの研究グループが開発したTwitterのトレンド分析システムについて解説します。前回は、そもそもTwitterのトレンドとは何かから始まり、開発者登録をしてトレンドAPIを叩き、そのリストを収集する方法まで説明しました。今回は、トレンドをキーにしてツイートを取得する方法と、ツイート群を対象として共起ネットワークグラフを作る方法を紹介します。

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

図4 トレンドを収集して分析するプログラムのコード(その1)

#!/usr/bin/env python
###########################################################
# ヘルパーファンクション
###########################################################
import re
# 特殊文字をエスケープする
def escape(s, quoted=u'\'"\\', escape=u'\\'):
  return re.sub(u'[%s]' % re.escape(quoted),
                lambda mo: escape + mo.group(),
                s)
# ノードのプリティプリンタ
def pp_node(ary_of_nodes):
  for node in ary_of_nodes:
    keyword = escape(node[0])
    frequency = node[1]
    print("node_hash['{0}'] = 
      trend.nodes.create(word: '{1}', freq: {2:6.2f})"
        .format(keyword, keyword, frequency))
# リンクのプリティプリンタ
def pp_link(ary_of_links):
  for link in ary_of_links:
    (kw1,kw2) = link[0].split(',')
    print("link = Link.create(corr: {0:6.2f})".format(link[1]))
    print("node_hash['{0}'].links << link".format(escape(kw1)))
    print("node_hash['{0}'].links << link".format(escape(kw2)))
# 値を正規化する。p_fragは百分率とするときTrue
def normalize(hash_obj, p_flag):
  maxval = max(hash_obj, key=lambda x: x[1])[1]
  factor = 100 if p_flag else 1
  return [(x[0], x[1] / maxval * factor) for x in hash_obj]

図5 トレンドを収集して分析するプログラムのコード(その2)

###########################################################
# ワードグラフの作成
###########################################################
import MeCab
# 「PATH_TO_MECAB_NEOLOGD」の部分は環境に合わせて修正
m = MeCab.Tagger("-d PATH_TO_MECAB_NEOLOGD")
stopwords = {'*', 'HTTPS', 'HTTP', 'WWW', 'の', 'ん', 'ン', 'ω', '???'}
def create_graph(keyword, collected, tweets):
  ary_of_ary = []
  for tweet in tweets:
    ary = []
    lines = m.parse(tweet).split('\n')
    for line in lines:
      if line.count('\t') == 0: continue
      (kw, prop) = line.split('\t')
      props = prop.split(',')
      if len({props[-3]} & stopwords) > 0: continue
      if props[1] == '固有名詞': ary.append(props[-3])
    ary_of_ary.append(ary)
  KW_THRESHOLD = 20
  kw_dict = {}
  counter = 0
  for ary in ary_of_ary:
    for kw in ary:
      if kw in kw_dict: kw_dict[kw] = kw_dict[kw] + 1.0
      else: kw_dict[kw] = 1.0
      counter = counter + 1
  for kw,val in kw_dict.items(): kw_dict[kw] = val / counter * 100
  if len(kw_dict) > 0:
    kw_dict = sorted(kw_dict.items(), 
                     key = lambda x: x[1], 
                     reverse = True)[0:KW_THRESHOLD-1]
    kw_dict = normalize(kw_dict, True)
    print("node_hash = {}")
    print("trend = Trend.create(label: '{0}', collected:'{1}')"
            .format(escape(keyword), collected))
    pp_node(kw_dict)
  corr_dict = {}
  for src in kw_dict:
    for dst in kw_dict:
      if src == dst: continue
      src_w = src[0]
      dst_w = dst[0]
      sd_pair = src_w + "," + dst_w
      if sd_pair in corr_dict: continue
      prob = 0.0
      for ary in ary_of_ary:
        if ((src_w in ary) & (dst_w in ary)): prob = prob + 1.0
      prob = 100 * prob / len(ary_of_ary)
      if prob > 0: corr_dict[dst_w + "," + src_w] = prob
  if len(corr_dict) > 0:
    lk_dict = sorted(corr_dict.items(), 
                     key = lambda x: x[1], reverse = True)
    # 後半3/4をカットして短くする
    lk_dict = lk_dict[0:int(len(lk_dict)*1/4)]
    if len(lk_dict) > 0:
      lk_dict = normalize(lk_dict, True)
      pp_link(lk_dict)

図6 トレンドを収集して分析するプログラムのコード(その3)

###########################################################
# すでに登録済みか否かのチェック
###########################################################
import sqlite3
from contextlib import closing
def has_been_registered(keyword, collected):
  # 「PATH_TO_THE_DATABASE_FILE」の部分は環境に合わせて修正
  dbfile = "PATH_TO_THE_DATABASE_FILE(development.sqlite3)"
  with closing(sqlite3.connect(dbfile)) as conn:
    c = conn.cursor()
    sql = 'select label, collected from trends \
           where label=? and collected=?'
    res = (keyword, collected)
    c.execute(sql, res)
    return (len(c.fetchall()) > 0)
###########################################################
# メイン関数
###########################################################
from twitter import *
from datetime import date
def main():
  # WOEID を希望の場所に合わせて指定。「23424856」は日本
  woeid = 23424856
  # アプリ登録時に取得した情報を下記に設定
  CK = 'ADD_HERE_YOUR_CONSUMER_KEY'
  CS = 'ADD_HERE_YOUR_CONSUMER_SECRET'
  AT = 'ADD_HERE_YOUR_ACCESS_TOKEN'
  AS = 'ADD_HERE_YOUR_ACCESS_TOKEN_SECRET'
  twitter = Twitter(auth = OAuth(AT,AS,CK,CS))
  results = twitter.trends.place(_id = woeid, exclude="hashtags")
  d = date.today()
  collected = d.strftime('%Y-%m-%d')
  for location in results:
    for trend in location["trends"]:
      keyword = trend["name"]
      if has_been_registered(keyword, collected): continue
      query_kw = keyword + " exclude:retweets exclude:nativeretweets"
      tw_rslts = twitter.search.tweets(q=query_kw, 
                                       lang='ja', locale='ja', count=100)
      tw_ary = []
      for tweet in tw_rslts["statuses"]: tw_ary.append(tweet["text"])
      create_graph(keyword, collected, tw_ary)
if __name__ == "__main__": main()

図7 ワードグラフ作成部で出力されるRubyスクリプトの例

node_hash = {}
trend = Trend.create(label: 'ホシガリス', collected:'2020-06-07')
node_hash['アニポケ'] = trend.nodes.create(word: 'アニポケ', freq: 100.00)
node_hash['ポケモン'] = trend.nodes.create(word: 'ポケモン', freq:  56.76)
node_hash['ゴルーグ'] = trend.nodes.create(word: 'ゴルーグ', freq:  29.73)
node_hash['思'] = trend.nodes.create(word: '思', freq:  18.92)
(略)
node_hash['ピカチュウ'] = trend.nodes.create(word: 'ピカチュウ', freq:  5.41)
node_hash['パチリス'] = trend.nodes.create(word: 'パチリス', freq:  5.41)
node_hash['スピアー'] = trend.nodes.create(word: 'スピアー', freq:  5.41)
link = Link.create(corr: 100.00)
node_hash['ポケモン'].links << link
node_hash['アニポケ'].links << link
link = Link.create(corr:  75.00)
node_hash['ゴルーグ'].links << link
node_hash['アニポケ'].links << link
(略)
link = Link.create(corr:  25.00)
node_hash['マユルド'].links << link
node_hash['ドクケイル'].links << link
node_hash = {}
trend = Trend.create(label: '孤独のグルメ', collected:'2020-06-07')
(略)

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

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

著者:しょっさん

ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならないでしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。第7回は、JavaScriptフレームワーク「Vue.js」でアプリを作成し、テストとリリースの方法を紹介します。

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

図2 Vue.js および各ライブラリを利用するための定義(simple.htmlから抜粋)

<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script src="https://unpkg.com/vue-router@3.3.2/dist/vue-router.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.19.2/dist/axios.min.js"></script>
<script src="./jwt-decode.min.js"></script>

図3 Vue RouterのHTMLファイル側の定義

<div id="app">
  <h1>経費精算アプリケーション(Vue.js)</h1>
  <router-link to="/expense">経費登録</router-link>
  <router-link to="/payment">経費精算</router-link>
  <router-link to="/login">ログイン</router-link>
  <router-link to="/logout">ログアウト</router-link>
  <router-view />
</div>

図4 Vue RouterのJavaScript側の定義

const router = new VueRouter({
  routes: [
    // 精算処理
    {
      path: "/expense",
    },
    // 支払処理
    {
      path: "/payment",
    },
    // 認証 - ログイン
    {
      path: "/login",
    },
    // 認証 - ログアウト
    {
      path: "/logout",
    },
    // どのURLにも該当しなかった場合
    {
      path: "*",
      redirect: "/expense",
    },
  ],
});

図5 認証部分のVueコンポーネントとVue Router定義

const Login = {
  template: "#login",
  data: function () {
    return {
      user: localStorage.user || "",
      password: localStorage.password || "",
      remember: localStorage.remember || false,
      error: false,
    };
  },
  methods: {
    login: function () {
      axios
        .post(${baseUrl}/api/auth, {
          user: this.user,
          password: this.password,
        })
        .then((response) => {
          if (response.status === 200) {
            // ログインが成功した場合は、ローカルストレージにトークンを保管する(ログインが成功した状態とする)
            this.error = false;
            localStorage.token = response.data.token;
            // "remember me" チェックボックスが付いていたら、各々の入力項目を保管する
            if (this.remember) {
              localStorage.user = this.user;
              localStorage.password = this.password;
              localStorage.remember = true;
            } else {
              // 逆にオフであれば入力項目の内容を破棄する
              delete localStorage.user;
              delete localStorage.password;
              delete localStorage.remember;
            }
            // ログイン成功したら /expense へ切り替える
            this.$router.replace("/expense");
          } else {
            this.error = true;
          }
        })
        .catch((response) => {
          this.error = true;
          this.remember = false;
          console.log(response);
        });
    },
  },
};

const router = new VueRouter({
  routes: [
(略)
    // ログイン画面
    {
      path: "/login",
      component: Login,
    },
    // ログアウト処理
    {
      path: "/logout",
      beforeEnter: (to, from, next) => {
        delete localStorage.token;
        next("/login");
      },
    },
  ],
});

図6 認証部分のHTMLテンプレート

<script type="text/x-template" id="login">
  <form @submit.prevent="login">
    <input v-model="user" type="email" placeholder="Your Email" autofocus="" />
    <input v-model="password" type="password" placeholder="Your Password" />
    <button type="submit">ログイン</button>
  </form>
  <div v-show="error">
    <p>ログイン失敗</p>
  </div>
</script>

図7 請求処理ののVueコンポーネントとVue Router定義

const Expense = {
  template: "#expense",
  data: function () {
    let decoded = {};
    if (localStorage.token) {
      // トークン内のユーザー情報を基に変数へ配置
      decoded = jwt_decode(localStorage.token);
    }
    return {
      user: decoded.email || "",
      id: decoded.id || "",
      user_name: decoded.user_name || "",
      date: "",
      type: "",
      amount: "",
      description: "",
      error: false,
    };
  },
  // 経費を登録するメソッド
  methods: {
    expense: function () {
      axios
        .post(
          ${baseUrl}/api/expense,
          {
            user: this.user,
            user_id: this.id,
            user_name: this.user_name,
            date: this.date,
            type: this.type,
            amount: this.amount,
            description: this.description,
          },
          {
            headers: {
              Authorization: Bearer ${localStorage.token},
            },
          }
        )
        .then((response) => {
          if (response.status === 200) {
            // 正常に登録できた場合は、変更が伴うフィールドをクリアーして、再度入力可能な状態にする
            this.error = false;
            console.log(response);
            this.date = "";
            this.type = "";
            this.amount = "";
            this.description = "";
          }
        })
        .catch((response) => {
          this.error = true;
          console.log(response);
        });
    },
  },
};

const router = new VueRouter({
  routes: [
    // 経費登録
    {
      path: "/expense",
      component: Expense,
      beforeEnter: (to, from, next) => {
        // 認証前の場合は /login ページへ遷移する
        if (!localStorage.token) {
          next({
            path: "/login",
            query: { redirect: to.fullPath },
          });
        } else {
          next();
        }
      },
    },
(略)
});

図8 請求処理のHTMLテンプレート

<script type="text/x-template" id="expense">
  <div>
    <form @submit.prevent="expense">
      <input v-model="user_name" type="text" placeholder="Your Name" />
      <input v-model="user" type="email" placeholder="Your Email" />
      <input v-model="id" type="hidden" placeholder="Your User ID" />
      <input v-model="date" type="datetime-local" placeholder="経費利用日" autofocus="" />
      <input v-model="type" type="text" placeholder="費目" />
      <input v-model="amount" type="number" placeholder="金額" />
      <input v-model="description" type="text" placeholder="費用詳細" />
      <button type="submit">経費申請</button>
    </form>
    <p v-if="error" class="error">経費登録失敗</p>
  </div>
</script>

図9 支払処理のVueコンポーネントとVue Router定義

// 経費リストを axios を利用して取得する関数
const getPayments = function (callback) {
  axios
    .get(${baseUrl}/api/payment, {
      // JWTの認可ヘッダー
      headers: {
        Authorization: Bearer ${localStorage.token},
      },
    })
    .then((response) => {
      if (response.status === 200) {
        // "response.data" 配下に経費リストが含まれる
        callback(null, response.data);
      } else {
        callback(true, response);
      }
    })
    .catch((response) => {
      callback(true, response);
    });
};

// 経費リスト用の Vueコンポーネント
const Payment = {
  template: "#payment",
  data: function () {
    return {
      loading: false,
      error: false,
      payments: function () {
        return [];
      },
    };
  },
  // 初期化されたときにデータを取得する
  created: function () {
    this.fetchData();
  },
  // ルーティングが変更されてきたときに再度データを取得する
  watch: {
    $route: "fetchData",
  },
  // 経費データを取得するメソッドのメイン部分
  methods: {
    fetchData: function () {
      this.loading = true;
      getPayments(
        function (err, payments) {
          this.loading = false;
          if (!err) this.payments = payments;
          else this.error = true;
        }.bind(this)
      );
    },
  },
};

const router = new VueRouter({
  routes: [
(略)
    {
      path: "/payment",
      component: Payment,
      beforeEnter: (to, from, next) => {
        // 認証前の場合は /login ページへ遷移する
        if (!localStorage.token) {
          next({
            path: "/login",
            query: { redirect: to.fullPath },
          });
        } else {
          next();
        }
      },
    },
(略)
  ],
});

図10 支払処理のHTMLテンプレート

<script type="text/x-template" id="payment">
  <div>
    <table>
      <tr>
        <th>ユーザー名</th>
        <th>発生日</th>
        <th>費目</th>
        <th>経費</th>
        <th>詳細</th>
      </tr>
      <tr v-for="payment in payments">
        <td>{{payment.user_name}}</td>
        <td>{{payment.date}}</td>
        <td>{{payment.type}}</td>
        <td>{{payment.amount}}</td>
        <td>{{payment.description}}</td>
      </tr>
    </table>
    <div class="loading" v-if="loading">アクセス中...</div>
    <p v-if="error" class="error">経費取得失敗</p>
  </div>
</script>

図14 controllers/auth/authentication.tsファイルの追加・改修部分

import jwt from "jsonwebtoken";

export class Authentication {
(略)
  static verifyLocal(username: string, password: string, done: any) {
    User.findOne({
      where: {
        email: username,
        deleted_at: null,
      },
    })
      .then((user) => {
        if (user && bcrypt.compareSync(password, user.hash)) {
          const opts = {
            issuer: process.env.ISSUER,
            audience: process.env.AUDIENCE,
            expiresIn: process.env.EXPIRES,
          };
          const secret: string = process.env.SECRET || "secret";
          const token: string = jwt.sign(
            {
              email: user.email,
              id: user.id,
              user_name: user.last_name + " " + user.first_name,
            },
            secret,
            opts
          );
          return done(null, token);
        }
        return done(true, "authentication error");
      })
      .catch((err) => done(true, err));
  }
(略)
}

図15 controllers/auth/authorization.tsファイル

import { Request, Response, NextFunction } from "express";
import { User } from "../../models/user";
import passport from "passport";
import { Strategy as JWTStrategy, ExtractJwt } from "passport-jwt";

export class Authorization {
  // JWTトークンで該当するユーザーの有無をチェック
  static verifyJWT(req: Request, jwt_payload: any, done: any) {
    User.findOne({
      where: {
        email: jwt_payload.email,
        deleted_at: null,
      },
    }).then((user) => {
      if (!user) return done(null, false);

      return done(null, user.get());
    });
  }

  // JWT Strategyの定義
  static setJWTStrategy() {
    const field = {
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      issuer: process.env.ISSUER,
      audience: process.env.AUDIENCE,
      secretOrKey: process.env.SECRET || "secret",
      passReqToCallback: true,
    };
    passport.use(new JWTStrategy(field, this.verifyJWT));
  }

  // 認可チェック
  static isAuthorized(req: Request, res: Response, next: NextFunction) {
    passport.authenticate("jwt", { session: false }, (err, user) => {
      if (err) {
        res.status(401).json({ status: "10001" });
      }
      if (!user) {
        res.status(401).json({ status: "10002" });
      } else {
        return next();
      }
    })(req, res, next);
  }
}

図16 API用のモジュールとルーティング定義

// API用ルーティング先のモジュール
import auth from "./api/auth";
import payment from "./api/payment";
import expense from "./api/expense";

// APIルーティング
app.use("/api/auth", auth);
app.use("/api/expense", Authorization.isAuthorized, expense);
app.use("/api/payment", Authorization.isAuthorized, payment);

図18 請求処理(/api/expense.ts)

import { Request, Response, NextFunction } from "express";
import Express from "express";
import { Expense } from "../models/expense";
const router = Express.Router();

// POST 経費の入力
router.post("/", (req: Request, res: Response, next: NextFunction) => {
  Expense.create(req.body)
    .then((result) => {
      res.status(200).json(result);
    })
    .catch((err) => {
      console.log(err);
      res.status(400).json({ id: 20002, message: err });
    });
});

export default router;

図19 支払処理(/api/payment.ts)

import { Request, Response, NextFunction } from "express";
import Express from "express";
import { Expense } from "../models/expense";
const router = Express.Router();

// POST / ユーザーの認証処理
router.get("/", (req: Request, res: Response, next: NextFunction) => {
  Expense.findAll()
    .then((results) => {
      res.status(200).json(results);
    })
    .catch((err) => {
      res.status(400).json({ id: 20011, message: err });
    });
});

export default router;

図35 修正した/src/index.tsファイル

import { Request, Response, NextFunction } from "express";
import Express from "express";
const app = Express();
import logger from "morgan";
import { Authentication } from "./controllers/auth/authentication";
import { Authorization } from "./controllers/auth/authorization";

app.use(logger("dev"));
app.use(Express.json());
app.use(Express.urlencoded({ extended: true }));

Authentication.initialize(app);
Authentication.setLocalStrategy();
Authorization.setJWTStrategy();

app.use(Express.static("htdocs"));

// API用ルーティング
import auth from "./api/auth";
import payment from "./api/payment";
import expense from "./api/expense";

// API
app.use("/api/auth", auth);
app.use("/api/expense", Authorization.isAuthorized, expense);
app.use("/api/payment", Authorization.isAuthorized, payment);

app.use((req: Request, res: Response, next: NextFunction) => {
  var err: any = new Error("Not Found");
  err.status = 404;
  next(err);
});

// error handler
app.use((err: any, req: Request, res: Response, next: NextFunction) => {
  res.locals.message = err.message;
  res.locals.error = req.app.get("env") === "development" ? err : {};

  res.status(err.status || 500);
  res.json(err);
});

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

export default app;

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

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

著者:山内 真仁

以前、ゲームエンジン「Unity」を使ったレースゲームをSLPのチーム活動で
作成しました。その経験を生かして今回は、Unityとプログラミング言語「C#」を使って、簡単なレースゲームを作成する方法を紹介します。Unityの物理エンジンを使用することで、複雑なコードを書かなくてもリアルな挙動のゲームを作成できます。

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

図12 「CarController.cs」ファイルに記述する内容

using UnityEngine;

public class CarController : MonoBehaviour
{
  public GameObject[] wheelMeshes = new GameObject[4];  // ホイールオブジェクトを格納する配列
  public WheelCollider[] wheelColliders = new WheelCollider[4];  // Wheel Colliderを格納する配列
  public float maxMotorTorque = 300f;     // 車輪に加える最大トルク
  public float brakeTorque = 500f;        // ブレーキのトルク
  public float maxSteerAngle = 25f;       // ステアリングの最大舵角
  float accel, steer;                     // アクセルとステアリングの入力値
  bool brake;                             // ブレーキをかけているかどうか
  // 画面を描画するたびに実行されるメソッド(実行間隔はフレームレートに依存)
  void Update()
  {
    steer = Input.GetAxis("Horizontal");    // ←→で旋回
    accel = Input.GetAxis("Vertical");      // ↑↓でアクセル
    brake = Input.GetKey(KeyCode.Space);    // スペースでブレーキ
    // Wheel Colliderの動きを見た目に反映する
    for (int i = 0; i < 4; i++) {
      wheelColliders[i].GetWorldPose(out Vector3 position, out Quaternion rotation);
      wheelMeshes[i].transform.position = position;
      wheelMeshes[i].transform.rotation = rotation;
    }
  }
  // フレームレートに依存せず、定期的に実行されるメソッド(0.02秒に1回)
  void FixedUpdate()
  {
    // Wheel Colliderに各パラメータを代入
    for(int i = 0; i < 4; i++) {
      if (i < 2) wheelColliders[i].steerAngle = steer * maxSteerAngle;  // ステアリング(前輪)
      wheelColliders[i].motorTorque = accel * maxMotorTorque;           // アクセル
      wheelColliders[i].brakeTorque = brake ? brakeTorque : 0f;         // ブレーキ
    }
  }
}

図14 CarController.csファイルに追加する記述

  GameObject brakeLight, headLight;  // ランプ類のオブジェクトを格納する変数
  // ゲーム開始時に1回のみ実行されるメソッド
  void Start()
  {
    // ランプ類のオブジェクトを探して取得
    brakeLight = GameObject.Find("SkyCarBrakeLightsGlow");
    headLight = GameObject.Find("SkyCarHeadLightsGlow");
  }
  void Update()
  {
    // ランプ類の点灯・消灯
    brakeLight.SetActive(brake);
    if (Input.GetKeyDown(KeyCode.H)) {
      headLight.SetActive(!headLight.activeSelf);
    }

図16 「Timer.cs」ファイルに記述する内容

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class Timer : MonoBehaviour {
  public Text timeText;     // タイム表示用のテキスト
  float startTime;          // 計測開始の時刻
  bool start, check, goal;  // 各地点の通過フラグ
  void Start() {
    // オブジェクトのコンポーネントを取得
    timeText = GameObject.Find("TimeText").GetComponent<Text>();
    timeText.text = "TIME  00.000";  // テキストの初期化
  }
  void Update() {
    // スタートしてからゴールするまでタイムを表示
    if (!goal && start)
      timeText.text = "TIME  " + (Time.time - startTime).ToString("00.000");
    // ゴール後に[R]キーを押すとリスタート
    if (goal && Input.GetKey(KeyCode.R))
      SceneManager.LoadScene(SceneManager.GetActiveScene().name);
  }
  // トリガーオブジェクトに侵入した時に呼び出されるメソッド
  void OnTriggerEnter(Collider other) {
    if (other.gameObject.name == "StartPoint") {
      if (check) {
        goal = true;  // チェックポイント通過済みならゴール
        timeText.color = Color.red;
      } else if (!start && !check) {
        start = true; // チェックポイントを通過していない場合、タイム計測開始
        startTime = Time.time;
      }
    } else if (start && other.gameObject.name == "CheckPoint")
      check = true;   // チェックポイントを通過
  }
}

Vol.67 補足情報

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

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

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

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

004 レポート Microsoft社製BASICのソースコード公開
005 NEWS FLASH
008 特集1 まんがで学ぶ自宅ネットワーク入門/麻生二郎
014 特集2 Cisco Webexが実現するテレワーク環境/粕谷一範
022 特集3 PythonとSeleniumを活用 自動操作とデータ分析/川嶋宏彰 コード掲載
035 Hello Nogyo!
036 特別企画 Microsoft Power Platform(後編)/清水優吾
050 ラズパイセンサーボードで学ぶ 電子回路の制御/米田聡 コード掲載
056 レッドハットのプロダクト/杉本拓 コード掲載
064 法林浩之のFIGHTING TALKS/法林浩之
066 バーティカルバーの極意/飯尾淳 コード掲載
072 tele-/桑原滝弥、イケヤシロウ
074 中小企業手作りIT化奮戦記/菅雄一
078 Webアプリの正しい作り方/しょっさん コード掲載
092 円滑コミュニケーションが世界を救う!/濱口誠一
094 香川大学SLPからお届け!/山内真仁 コード掲載
102 シェルスクリプトの書き方入門/大津真 コード掲載
108 Techパズル/gori.sh
110 コラム「ユニケージの本領発揮」/シェル魔人

特集3 PythonとSeleniumを活用 自動操作とデータ分析(Vol.67記載)

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

著者:川嶋 宏彰

最近、プログラミング言語「Python」による自動化やデータ分析が注目されています。本特集では、Pythonと、Webブラウザを自動操作するためのライブラリ「Selenium WebDriver」を用いて、インターネットから取得できるオープンデータを例に、Webブラウザの自動操作方法およびデータ分析方法を分かりやすく紹介します。

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

図4 非headlessモードのサンプルコード(sample.py)

import time
from selenium import webdriver

driver = webdriver.Chrome()
driver.get('https://www.google.com/')
time.sleep(5)
search_box = driver.find_element_by_name('q')
search_box.send_keys('ChromeDriver')
search_box.submit()
time.sleep(5)
driver.quit()

図7 headlessモードのサンプルコード

from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_argument('--headless')
options.add_argument('--disable-gpu')
driver = webdriver.Chrome(options=options)

driver.get('https://www.google.com/')
print(driver.title)

search_box = driver.find_element_by_name('q')
search_box.send_keys('ChromeDriver')
search_box.submit()
print(driver.title)

driver.save_screenshot('search_results.png')
driver.quit()

図24 Seleniumを用いた気温データの自動取得プログラム

import time
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.select import Select

# ダウンロード先フォルダの指定
dldir_path = Path('csv')  # csv という名前のフォルダとする
dldir_path.mkdir(exist_ok=True)  # なければ作成
download_dir = str(dldir_path.resolve())  # 絶対パスを取得
print("download_dir: " + download_dir)

options = webdriver.ChromeOptions()
options.add_experimental_option('prefs', {    # Chrome のオプションに
  'download.default_directory': download_dir  # 絶対パスで指定  
})

driver = webdriver.Chrome(options=options)

wait = WebDriverWait(driver, 10)  # 明示的待機用 (Timeout 10秒)

# 自動操作開始
driver.get('https://www.data.jma.go.jp/gmd/risk/obsdl/index.php')

# 「地点を選ぶ」
xpath = '//div[@class="prefecture" and text()="東京"]'
time.sleep(2)
driver.find_element_by_xpath(xpath).click()

xpath = '//div[@class="station" and contains(@title, "地点名:東京")]'
time.sleep(2)                                # (★)
driver.find_element_by_xpath(xpath).click()  # (★)

# 「項目を選ぶ」
driver.find_element_by_id('elementButton').click()

xpath = '//span[text()="月別値"]/preceding-sibling::input'
time.sleep(2)
driver.find_element_by_xpath(xpath).click()

css = '#日最高気温の平均'
time.sleep(2)
driver.find_element_by_css_selector(css).click()

# 「期間を選ぶ」
driver.find_element_by_id('periodButton').click()

time.sleep(2)
# <select>内の<option>要素を選択
Select(driver.find_element_by_name('iniy')).select_by_value('2010')
Select(driver.find_element_by_name('inim')).select_by_value('1')
time.sleep(2)  # いったん止めてみる
Select(driver.find_element_by_name('endy')).select_by_value('2019')
Select(driver.find_element_by_name('endm')).select_by_value('12')
time.sleep(2)

# 「CSVファイルをダウンロード」
driver.find_element_by_id('csvdl').click()
time.sleep(2)

driver.quit()

図27 e-StatのAPI機能を利用した家計調査データを取得するプログラム

import sys
import urllib
import urllib.request
import json
import calendar
import matplotlib.pyplot as plt
import japanize_matplotlib  # japanize-matplotlib を使う場合
import pandas as pd
from scipy import stats 

url = 'https://api.e-stat.go.jp/rest/3.0/app/json/getStatsData?'

app_id = '<e-Statマイページで取得したアプリケーションIDを挿入>'

cat01 = '010800150' # アイスクリーム・シャーベット
# cat01 = '010800130' # チョコレート
# cat01 = '011100030' # ビール

remove_month = 0  # 特定月を除く場合は1-12のいずれかを指定

# 指定する数字の桁数は決まっているので注意
keys = {
    'appId' : app_id,
    'lang' : 'J',
    'statsDataId' : '0003343671',  # 家計調査データ
    'metaGetFlg' : 'Y',
    'cntGetFlg' : 'N',
    'cdTab' : '01',  # 金額
    'cdTimeFrom' : '2010000101',  # 2010年1月から
    'cdTimeTo' : '2019001212',  # 2019年12月まで
    'cdArea' : '00000',  # 全国
    'cdCat01' : cat01,
    'cdCat02' : '03'  # 二人以上世帯
}

params = urllib.parse.urlencode(keys)
r_obj = urllib.request.urlopen(url + params)  # データを取得
r_str = r_obj.read()
res = json.loads(r_str)  # Pythonの辞書へ
stats_data = res['GET_STATS_DATA']['STATISTICAL_DATA']

class_obj = stats_data['CLASS_INF']['CLASS_OBJ']  # メタ情報

if 'DATA_INF' not in stats_data:  # ['DATA_INF']が入らないときのチェック用
    for co in class_obj:
        if 'CLASS' not in co:
            print("ERROR: Check params @id= {}, @name= {}" \
                .format(co['@id'], co['@name']))
    sys.exit(1)

values = stats_data['DATA_INF']['VALUE']  # 統計データの数値情報を取得

# メタ情報(CLASS_INF)から取得した品目名称を図のタイトルに使う
title = [co['CLASS']['@name'] for co in class_obj if co['@id'] == 'cat01'][0]
print(title)

# 各要素が [年, 月, 支出金額] の2次元リストにする
data = [[int(v['@time'][0:4]), int(v['@time'][6:8]), int(v['$'])] for v in values]
print("n =", len(data))  # 120 = 10年 x 12カ月

# Pandasデータフレームの準備
df = pd.DataFrame(data, columns=['year', 'month', '支出(円)'])
df['days'] = [calendar.monthrange(df.loc[i, 'year'], df.loc[i, 'month'])[1] for i in df.index]  # 各月の日数
df['支出(円/日)'] = df['支出(円)'] / df['days']  # 1日あたりの支出金額
df['y/m'] = df['year'].astype(str) + '/' + df['month'].astype(str)  # 結合用

# 気象庁の気温データとマージ
df_jma = pd.read_csv('csv/data.csv', skiprows=5, header=None, usecols=[0,1], encoding='Shift_JIS')
df_jma.columns = ['y/m', '平均最高気温(℃)']
df = pd.merge(df, df_jma, on='y/m')  # データフレームの結合

if remove_month > 0:
    df = df.query('month != @remove_month')  # 特定月を除く場合

# 相関係数を計算
corr, _ = stats.pearsonr(df['平均最高気温(℃)'], df['支出(円/日)'])
corr_str = "相関係数: {:2f}".format(corr)
print(corr_str)

# 散布図をプロット
ax = df.plot(kind='scatter', x='平均最高気温(℃)', y='支出(円/日)')
ax.set_title(title + ', ' + corr_str)
plt.show()

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

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

著者:大津 真

本連載ではシェルスクリプトの書き方をやさしく紹介します。対象とするシェルは、多くのLinuxディストリビューションが標準シェルとして採用する「Bash」です。第3回は、シェルスクリプトにおける条件分岐の使用方法を中心に解説します。

図1 シェルスクリプト「ping.sh」の内容

#!/bin/bash
if ping -c1 192.168.0.2 > /dev/null
then
  echo " 応答あり"
fi

図2 シェルスクリプト「secret1.sh」の内容

#!/bin/bash
secret=" ひらけごま"
echo -n " 合言葉は?: "
read aikotoba
if test $secret = $aikotoba; then
  echo " 正しい合言葉です!"
fi

図3 シェルスクリプト「secret2.sh」の内容

#!/bin/bash
secret=" ひらけごま"
echo -n " 合言葉は?: "
read aikotoba
if [[ $secret = $aikotoba ]]; then
  echo " 正しい合言葉です!"
fi

図4 シェルスクリプト「secret3.sh」の内容

#!/bin/bash
secret=" ひらけごま"
echo -n " 合言葉は?: "
read aikotoba
if [[ $secret = $aikotoba ]]; then
  echo " 正しい合言葉です!"
else
  echo " 合言葉が間違っています"
fi

図5 平成年を西暦年に変換するシェルスクリプト「heiseiToSeireki.sh」

#!/bin/bash
if [[ $# -eq 0 ]]; then
  echo " 平成年を指定してください"
  exit 1
fi
if [[ "$1" -lt 1 || "$1" -gt 31 ]]; then
  echo "1〜31 までの数値を入力してください"
  exit 1
fi
echo " 平成$1 年は西暦$(($1+1988)) 年"

図6 指定したファイル中のコロンをカンマに変換するシェルスクリプト「colon_to_comma.sh」

#!/bin/bash
fname=$1
cp "$fname" "${fname}~"
tr ":" "," < "${fname}~" > "$fname"

図7 引数チェック用のコードを追加した「colon_to_comma2.sh」

#!/bin/bash
if [[ $# -eq 0 ]]; then
  echo " 引数でファイルを指定してください"
  exit 1
fi
if [[ ! -f $1 ]]; then
  echo "$1 が見つかりません"
  exit 1
fi
fname=$1
cp "$fname" "${fname}~"
tr ":" "," < "${fname}~" > "$fname"

Vol.66 補足情報

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

Techパズル

p.104の左下にある図は、前号(Vol.65)の解答です。

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

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

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

004 レポート Linuxの新版「Ubuntu 20.04 LTS」リリース
005 レポート VS Codeライクな「Eclipse Theia」登場
006 NEWS FLASH
008 特集1 1000円から始めるIoT/麻生二郎 コード掲載
023 Hello Nogyo!
026 特集2 OneDriveを有効活用しよう/三沢友治
042 特別企画 Microsoft Power Platform(前編)/清水優吾
050 ラズパイセンサーボードで学ぶ 電子回路の制御/米田聡 コード掲載
054 レッドハットのプロダクト/平田千浩 コード掲載
056 バーティカルバーの極意/飯尾淳 コード掲載
062 μ(マイクロ)/桑原滝弥、イケヤシロウ
064 Webアプリの正しい作り方/しょっさん コード掲載
070 円滑コミュニケーションが世界を救う!/濱口誠一
072 MySQL Shellを使おう/梶山隆輔
079 中小企業手作りIT化奮戦記/菅雄一
084 法林浩之のFIGHTING TALKS/法林浩之
086 香川大学SLPからお届け!/檜垣龍德 コード掲載
092 ユニケージ開発手法入門/石崎博之、掛本健一 コード掲載
098 シェルスクリプトの書き方入門/大津真 コード掲載
104 Techパズル/gori.sh
106 コラム「コロナ禍の中で」/シェル魔人

特集1 1000円から始めるIoT(Vol.66記載)

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

著者:麻生 二郎

センサーをつないで状態を監視するだけのIoT(モノのインターネット)を
始めるには、小型コンピュータボード「Raspberry Pi」は高機能かつ高価で
す。そこで1000円以下で購入できるマイコンボード「ESP32 ESP-32S」を使
って、簡単なプログラムと共にI oTを始めてみましょう。

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

Part2 ESP32で電子回路を制御する

図2 ネットワーク接続プログラム(wlan.py)

import network
wlan_if = network.WLAN(network.STA_IF)
wlan_if.active(True)
wlan_if.ifconfig(('192.168.1.100', '255.255.255.0', '192.168.1.1', '192.168.1.1'))
wlan_if.connect('SSID', 'パスワード')
wlan_if.inconfig()

図3 簡易サーバーを立ち上げるプログラム(webserver_test.py)

import picoweb
 
app = picoweb.WebApp(__name__)
 
@app.route("/")
def index(req, resp):
  yield from picoweb.start_response(resp)
  yield from resp.awrite("こんにちは")
 
app.run(debug=True, host = "192.168.1.100")

図12 Webブラウザから湿温度・気圧を取得するプログラム(web_bme280.py)

import picoweb, machine, bme280
  
i2cpin = machine.I2C(scl=machine.Pin(22), sda=machine.Pin(21))
bme = bme280.BME280(i2c=i2cpin)
 
app = picoweb.WebApp(__name__)
 
@app.route("/")
def index(req, resp):
  bme280values = bme.values
  yield from picoweb.start_response(resp)
  yield from resp.awrite("気温:" + bme280values[0])
  yield from resp.awrite("|湿度:" + bme280values[2])
  yield from resp.awrite("|気圧:" + bme280values[1])
 
app.run(debug=True, host = "192.168.1.100")

図16 明るさによってライトの点灯を促すプログラム(web_cds.py)

import picoweb, machine
 
adc = machine.ADC(machine.Pin(36))
adc.atten(machine.ADC.ATTN_11DB)
 
app = picoweb.WebApp(__name__)
 
@app.route("/")
def index(req, resp):
  csdvalue = adc.read()
  yield from picoweb.start_response(resp)
  if csdvalue < 2500:
    yield from resp.awrite("明かりをつけましょう")
  else:
    yield from resp.awrite("十分な明るさです")
 
app.run(debug=True, host = "192.168.1.100")

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

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

著者:米田 聡

シェルスクリプトマガジンでは、小型コンピュータボード「Raspberry Pi」(ラズパイ)向けのセンサー搭載拡張ボード「ラズパイセンサーボード」を制作しました。第13 回では、同ボードに搭載された湿温度・気圧センサー「BME280」から取得したデータをグラフ化する準備をします。

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

図2 PythonからSQLiteを操作するサンプルプログラム(test_sqlite3.py)

import sqlite3
 
DBNAME = "test.sqlite3"         # データベースファイル名
conn = sqlite3.connect(DBNAME)  # SQLite 3に接続
cur = conn.cursor()             # カーソルを得る
cur.execute("SQL文")            # SQLを発行
conn.commit()                   # コミット
data = cur.fetchall()           # 結果をdataに格納
conn.close()                    # データベースを閉じる

図3 温度、湿度、気圧のデータをSQLiteに保存するプログラム(storebme.py)

import time, os, sys, signal
import smbus2
import bme280
import sqlite3
 
BME280_ADDR = 0x76
BUS_NO = 1
DBNAME = 'weather.sqlite3'
 
def store_values(values):
  conn = sqlite3.connect(DBNAME)
  cur = conn.cursor()
  sql = "INSERT INTO bme(dt,temp,hum,press) VALUES(datetime('now', '+9 hours'),?,?,?)"
  cur.execute(sql,(values[0],values[1],values[2]))
  conn.commit()
  conn.close()
 
def signal_handler(sig, handler):
  sys.exit()
 
# テーブル作成
if not os.path.exists(DBNAME):
  conn = sqlite3.connect(DBNAME)
  cur = conn.cursor()
  cur.execute("CREATE TABLE bme(id INTEGER PRIMARY KEY AUTOINCREMENT, dt TEXT,temp REAL, hum REAL, press REAL)")
  conn.commit()
  conn.close()
 
# BME280
i2c = smbus2.SMBus(BUS_NO)
bme280.load_calibration_params(i2c, BME280_ADDR)
 
# signal
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
 
# メインループ
while True:
  data = bme280.sample(i2c, BME280_ADDR)
  store_values([data.temperature,data.humidity,data.pressure ])
  time.sleep(60)

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

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

著者:平田 千浩

「Red Hat Ansible Automation Platform」は、OSS のAnsibleとAWXを基とした企業向け自動化の基盤です。RHELを含むさまざまなOS、さまざまなアプリケーション、さまざまな機器に対応し、局所的な作業からバージョン管理システムと連携した構成管理までを自動化できます。

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

図2 インベントリファイルの例(hosts)

[all:vars]
ansible_ssh_private_key_file=/home/student1/.ssh/aws-private.pem

[ios]
ios1 ansible_host=XX.XX.XX.XX

[ios:vars]
ansible_user=ec2-user
ansible_network_os=ios
ansible_connection=network_cli

図3 Cisco IOS向けの簡単なPlaybookの例(snmp.yml)

---
- name: snmp ro / rw string configuration
  hosts: ios
  gather_facts: no

  tasks:
  - name: ensure that the desired snmp strings are present
    ios_config:
      commands:
        - snmp-server community ansible-public RO
        - snmp-server community ansible-private RW

図6 Arista EOSを追加したインベントリファイル例(hosts)

[all:vars]
ansible_ssh_private_key_file=/home/student1/.ssh/aws-private.pem

[ios]
ios1 ansible_host=XX.XX.XX.XX

[ios:vars]
ansible_user=ec2-user
ansible_network_os=ios
ansible_connection=network_cli

[eos]
eos1 ansible_host=YY.YY.YY.YY

[eos:vars]
ansible_user=ec2-user
ansible_network_os=eos
ansible_connection=network_cli
ansible_become=true
ansible_become_method=enable

[control]
ansible ansible_host=AA.AA.AA.AA ansible_user=student1 ansible_password=PASSWORD

図7 Arista EOSのコンフィグをバックアップするPlaybookの例(eos_backup.yml)

---
- name: retrieve router configurations
  hosts: eos
  gather_facts: no

  tasks:
  - name: BACKUP THE CONFIG
    eos_config:
      backup: yes
    register: config_output

  - name: Save THE CONFIG
    vars:
      ansible_connection: ssh
    copy:
      src: "{{config_output.backup_path}}"
      dest: "/backup/{{inventory_hostname}}"
    delegate_to: ansible
    become: yes

図10 CISCO IOSのコンフィグをバックアップするPlaybookの例

- name: retrieve router configurations
  hosts: ios
  gather_facts: no

  tasks:
  - name: BACKUP THE CONFIG
    ios_config:
      backup: yes
    register: config_output

  - name: REMOVE NON CONFIG LINES - REGEXP
    lineinfile:
      path: "{{config_output.backup_path}}"
      line: "Building configuration..."
      state: absent

  - name: Save THE CONFIG
    vars:
      ansible_connection: ssh
    copy:
      src: "{{config_output.backup_path}}"
      dest: "/backup/{{inventory_hostname}}"
    delegate_to: ansible
    become: yes

図11 保存したArista EOSのコンフィグからリストアするPlaybookの例(eos_restore.yml)

---
- name: Restore the EOS Config
  hosts: eos
  gather_facts: no

  tasks:
  - name: RESTORE THE CONFIG
    eos_config:
      replace: config
      src: "/backup/{{inventory_hostname}}"

図12 保存したCisco IOSのコンフィグからリストアするPlaybookの例

---
- name: Restore the IOS Config
  hosts: ios
  gather_facts: no

  tasks:
  - name: RESTORE THE CONFIG
    ios_config:
      src: "/backup/{{inventory_hostname}}"

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

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

著者:飯尾 淳

 計量社会学や数理社会学という学問分野があります。人々の行動や社会活動から生み出される多様なデータを定量的に分析することによって、その背景となる社会的な構造や原理を見いだそうという社会学です。筆者はそれらの専門家ではありませんが、社会情報学の文脈で似たような研究に従事しています。今回は、筆者らの研究成果の一つであるTwitterのトレンド分析について、その概要を紹介します。

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

図6 トレンドのキーワードを取得するPythonコード

#!/usr/bin/env python

from twitter import *

woeid = 23424856 # Japan
CK = 'ADD_YOUR_KEY_HERE'            # Consumer Key
CS = 'ADD_YOUR_KEY_SECRET_HERE'     # Consumer Key Secret
AT = 'ADD_YOUR_TOKEN_HERE'          # Access Token
AS = 'ADD_YOUR_TOKEN_SECRET_HERE'   # Accesss Token Secert

twitter = Twitter(auth = OAuth(AT,AS,CK,CS))
results = twitter.trends.place(_id = woeid, exclude="hashtags")

for location in results:
  for trend in location["trends"]:
    print (trend["name"])

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

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

著者:しょっさん

ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならないでしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。第6回は、アプリケーションの開発やテストを実施する環境について詳しく考えます。

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

図2 モックのコード(swagger.json)

{
  "openapi" : "3.0.0",
  "servers" : [ {
    "description" : "Expense Reporter Sample API",
    "url" : "https://virtserver.swaggerhub.com/sho7650/ExpenseMockServices/0.5.0"
  } ],
  "info" : {
    "description" : "シェルスクリプトマガジン用サンプル",
    "version" : "0.5.0",
    "title" : "Expense API",
    "contact" : {
      "email" : "sho@oshiire.to"
    },
    "license" : {
      "name" : "Apache 2.0",
      "url" : "http://www.apache.org/licenses/LICENSE-2.0.html"
    }
  },
  "tags" : [ {
    "name" : "users",
    "description" : "一般利用者"
  }, {
    "name" : "approvers",
    "description" : "承認者"
  } ],
  "paths" : {
		(略)
    "/payment" : {
      "get" : {
        "tags" : [ "approvers" ],
        "summary" : "支払い待ち一覧",
        "operationId" : "findPayments",
        "description" : "一般利用者からの請求一覧を表示",
        "security" : [ {
          "bearerAuth" : [ ]
        } ],
		(略)
        "responses" : {
          "200" : {
            "description" : "success",
            "content" : {
              "application/json" : {
                "schema" : {
                  "type" : "array",
                  "items" : {
                    "$ref" : "#/components/schemas/ExpenseItem"
                  }
                }
              }
            }
          },
          "400" : {
            "description" : "bad input parameter"
          }
        }
      }
    }
  },
  "components" : {
    "securitySchemes" : {
      "bearerAuth" : {
        "type" : "http",
        "scheme" : "bearer",
        "bearerFormat" : "JWT"
      }
    },
    "schemas" : {
      "id" : {
        "type" : "integer",
        "format" : "int32",
        "example" : 120
      },
	(略)
    }
  }
}

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

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

著者:檜垣 龍德

 今回は、Pythonとそのライブラリである「slacker」「python-crontab」を用いて、チャットサービス「Slack」にメッセージを自動投稿するbo(t Slackbot)を開発する方法を解説します。例として開発するのは、参加者からメッセージが投稿されるまで「Get Up !!」というメッセージを連続投稿するbotです。

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

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

ACCESS_TOKEN=アクセストークン
CHANNEL_ID=チャンネルID

図8 「settings.py」ファイルに記述する内容

import os
from os.path import join, dirname
from dotenv import load_dotenv

dotenv_path = join(dirname(__file__), '.env')
load_dotenv(dotenv_path)

ACCESS_TOKEN = os.environ.get("ACCESS_TOKEN")
CHANNEL_ID = os.environ.get("CHANNEL_ID")

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

from slacker import Slacker
import settings

slack = Slacker(settings.ACCESS_TOKEN)
slack.chat.post_message("チャンネル名", "Get Up !!")

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

from crontab import CronTab

cron = CronTab()
job = cron.new('python main.py')
job.setall('00 00 * * *')
for result in cron.run_scheduler():
  print("Done Job"

図12 書き換えたmain.pyファイルの内容

from slacker import Slacker
from time import sleep
import settings

slack = Slacker(settings.ACCESS_TOKEN)
channel_id = settings.CHANNEL_ID
slack.chat.post_message("チャンネル名", "Get Up !!")
history = slack.channels.history(channel_id)
start_ts = history.body["messages"][0]["ts"]
while True:
    sleep(1)
    history = slack.channels.history(channel_id, count=10000, oldest=start_ts)
    not_bot_users = [message["user"] for message in history.body["messages"] if "bot_id" not in message]
    if len(not_bot_users):
        break
     else:
        slack.chat.post_message("チャンネル名", "Get Up !!")

図14 書き換えたmain.pyファイルに追記する内容

history = slack.channels.history(channel_id, count=10000, oldest=start_ts)
ts_list = [message["ts"] for message in history.body["messages"] if 'bot_id' in message]
ts_list.append(start_ts)
for ts in ts_list:
    slack.chat.delete(channel_id, ts=ts, as_user=True)

ユニケージ開発手法入門(Vol.66掲載)

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

著者:石崎 博之、掛本 健一

ユニケージ開発手法によるシステムの設計・実装・保守を数年以上経験したメンバーが、業務システム構築のための「ユニケージ開発手法」を解説します。我々の経験に基づきながら、具体的なシステム構築の流れを示していきます。第1回は、ユニケージ開発手法の特徴をつかむです。

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

図2 ユニケージのシェルスクリプトの例

#!/bin/bash
join0 key=1 MASTER TRAN   |
self 2 3 4 5              | 
msort key=1/2             | 
sm2 1 2 3 4               | 
sm4 1 1 2 2 3 4           | 
self 1 2 3 4              | 
sm5 1 3 4 4               | 
map num=1                 | 
sed 's/A/Sales/g'         | 
sed 's/B/Profit/g         | 
keta 4 6@NF-1             | 
comma 3/NF                | 
cat header -              | 
tocsv > result
exit 0

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

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

著者:大津 真

本連載ではシェルスクリプトの書き方をやさしく紹介します。対象とするシェルは、多くのLinuxディストリビューションが標準シェルとして採用する「Bash」です。第2回では、変数の概要と、シェルスクリプト内でコマンドライン引数を扱う方法について解説します。

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

図4 シェルスクリプト「 permtest1.sh 」の内容

#!/bin/bash
echo " スクリプト名: $0"
echo " 引数の数: $#"
echo " 引数1: $1"
echo " 引数2: $2"
echo " 引数3: $3"

図5 シェルスクリプト「 colon_to_comma.sh 」の内容

#!/bin/bash
fname=$1
cp "$fname" "${fname}~"
tr ":" "," < "${fname}~" > "$fname"

図6 加工対象のファイル「 customer.txt 」の内容

010: 白戸二郎: 男:39: 北海道
011: 小山田花子: 女:24: 福岡
012: 三村美子: 女:29: 東京

シェルスクリプトの書き方入門(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 | カテゴリー: コード

筆者:飯尾 淳

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

シェルスクリプトマガジン 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)

Vol.65 補足情報

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

目次

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

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

シェルスクリプトマガジン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.64 補足情報

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

姐のNOGYO

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

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

シェルスクリプトマガジン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 | カテゴリー: コード

著者:飯尾 淳

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

シェルスクリプトマガジン 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]
}

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 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.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 | カテゴリー: コード

著者:米田 聡

シェルスクリプトマガジンでは、小型コンピュータボード「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()

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)

ラズパイセンサーボード向けソースコード集

投稿日: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 | カテゴリー: コード

著者:飯尾淳

 本連載の第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

もっと見る

新しい記事一覧へ戻る

  • カテゴリー コード のアーカイブを表示しています。

  • -->