ログの情報を分析して、管理者に警告メッセージを送信するのは、サーバーの運用管理ではよく使う手法です。インターネット側からアクセスできるようにしたサーバーの場合は、特にセキュリティ関連の通知ができると便利です。
そこで、暗号化通信の「SSH」(Secure SHell)による、Linuxサーバーへのログイン認証のログを監視して、リモートログインや、不正(らしき)アクセスを検知したときにSNSに通知するシェルスクリプトを作成します(図1)。

test
著者:後藤 大地
今回も引き続き、プログラミング言語の「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」
|
1 2 3 |
fn main() { println!("Hello, world!"); } |
図4 コンパイルに必要な構成情報や依存関係を記したファイル「Cargo.toml」
|
1 2 3 4 5 6 7 8 9 |
[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)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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)
|
1 2 3 4 5 6 7 |
fn main() { let v; v = 1; v = 2; } |
図10 mutを指定して値を変更できる変数と宣言したサンプルコード(src/main.rs)
|
1 2 3 4 5 6 7 |
fn main() { let mut v; v = 1; v = 2; } |
図12 read_line()関数の戻り値を表示させるコード
|
1 2 3 4 5 6 7 8 |
use std::io; fn main() { let mut s = String::new(); println!("戻り値: {}", io::stdin().read_line(&mut s).expect("エラー")); } |
著者:しょっさん
ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならないでしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。第4回は、3回目のイテレーションを実施し、システムに必要な機能を実装していきます。
シェルスクリプトマガジン Vol.64は以下のリンク先でご購入できます。![]()
![]()
図3 マイグレーションファイルのテンプレート
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
'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のカラムを追加するように修正した
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
'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ファイル
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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 権限テーブルのモデルファイル
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
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 ユーザーマスターテーブルのモデルファイル
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
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への追加)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
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 への追加)
|
1 2 3 4 5 6 7 8 |
// ログインの強制 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ファイル(ログインスクリプト)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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ファイルの修正
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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 用意した型定義ファイル
|
1 2 3 4 5 6 7 8 9 10 11 |
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」ファイル
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
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」ファイル
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
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)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
'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' ] }); } }; |
著者:清水赳
前回に引き続き、OSSのシステム監視ツール「Prometheus」を「Itamae」というプロビジョニングツールを使って、サーバー監視システムを構築する方法を紹介します。今回は、Prometheusでノード情報を取得・計算する方法や、外形監視、データの可視化方法について解説します。
シェルスクリプトマガジン Vol.64は以下のリンク先でご購入できます。![]()
![]()
図5 「./cookbooks/blackbox_exporter/default.yml」ファイルに記述する内容
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
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」ファイルに記述する内容
|
1 2 3 4 5 6 7 8 |
[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」ファイルの編集内容
|
1 2 |
include_recipe "../cookbooks/node_exporter" # 前回追加 include_recipe "../cookbooks/blackbox_exporter" # 今回追加 |
図8 「./cookbooks/blackbox_exporter/files/etc/blackbox_exporter/blackbox.yml」ファイルに記述する内容
|
1 2 3 4 5 6 7 8 9 10 |
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」ファイルに追加する内容
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
- 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 |
著者 :河野 達也
最近、Rustというプログラミング言語の名前をよく見かけるようになりました。米Amazon Web Services社、米Dropbox社、米Facebook社、米Mozilla財団などは、Rustを使ってミッションクリティカルなソフトウエアを開発しています。Rust とはどんな言語でしょうか。シンプルなプログラムの開発を通してRustの世界に飛び込みましょう。
シェルスクリプトマガジン Vol.64は以下のリンク先でご購入できます。![]()
![]()
図1 Rust のプログラムの例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// 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のプログラムの例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 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()関数の定義コード
|
1 2 3 4 5 |
/// この関数はニュートン法で平方根を求めます。 fn sqrt(a: f64) -> f64 { // 未実装を表す。実行するとエラーになりプログラムが終了する unimplemented!() } |
図23 let文とif式を追加したsqrt()関数の定義コード
|
1 2 3 4 5 6 7 8 |
fn sqrt(a: f64) -> f64 { // 変数x0を導入し、探索の初期値に設定する let x0 = if a > 1.0 { a } else { 1.0 }; } |
図24 loop式を追加したsqrt()関数の定義コード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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()関数の定義コード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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()関数の定義コード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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()関数のコード
|
1 2 3 4 5 |
fn main() { let a = 2.0; // aの値とニュートン法で求めた平方根を表示 println!("sqrt({}) = {}", a, sqrt(a)); } |
図2 Cargo.tomlファイルに追加する設定
|
1 2 3 4 5 6 7 |
[dependencies] chrono = "0.4" clap = "2.33" csv = "1.1" hdrhistogram = "6.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" |
図3 コマンドライン引数を処理するプログラム
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// 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型の定義(抜粋)
|
1 2 3 4 |
enum Option<T> { None, // Noneバリアント Some(T), // Someバリアント。T型の値を持つ } |
図6 Result型の定義(抜粋)
|
1 2 3 4 |
Result<T, E> { Ok(T), // 処理成功を示すバリアント。T型の成功時の値を持つ Err(E), // 処理失敗を示すバリアント。E型のエラーを示す値を持つ } |
図7 match式とif let式の使用例
■match式の使用例
|
1 2 3 4 5 6 7 |
match arg_matches.value_of("INFILE") { // 値がパターンSome(..)にマッチするなら、 // 包んでいる&strをinfile変数にセットし、=>以降の節を実行 Some(infile) => println!("INFILE is {}", infile), // 値がパターンNoneにマッチするなら、=>以降の節を実行 None => eprintln!("Please specify INFILE"), } |
■if let式の使用例
|
1 2 3 4 5 6 7 8 |
// 値がパターンSome(..)にマッチするなら、 // 包んでいる&strをinfile変数にセットし、true節を実行 if let Some(infile) = arg_matches.value_of("INFILE") { println!("INFILE is {}", infile); } else { // そうでなければelse節を実行 eprintln!("Please specify INFILE"); } |
図8 コマンドライン引数を必須にする変更
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
(略) .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トレイトの定義(抜粋)
|
1 2 3 |
trait Debug { fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error>; } |
図10 analyze()関数の定義コードを追加
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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()関数の定義コードを変更
|
1 2 3 4 5 6 7 8 9 10 11 |
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()関数の定義コードを変更
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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の定義コードを追加
|
1 2 3 4 5 6 7 8 |
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)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// 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()関数の定義コードを変更
|
1 2 3 4 5 6 7 8 9 10 11 |
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構造体の定義を追加
|
1 2 3 4 5 6 7 8 |
use serde::{Deserialize, Serialize}; // Serializeを追加 // serde_jsonでJSON文字列を生成するためにSerializeを自動導出する #[derive(Debug, Serialize)] struct RecordCounts { read: u32, // CSVファイルから読み込んだ総レコード数 matched: u32, // 乗車地や降車地などの条件を満たしたレコードの数 skipped: u32, // 条件は満たしたが異常値により除外したレコードの数 } |
図20 RecordCountsのデフォルト値をつくる関数を定義
|
1 2 3 4 5 6 7 8 9 |
impl Default for RecordCounts { fn default() -> Self { Self { read: 0, // read: u32::default(), としてもよい matched: 0, skipped: 0, } } } |
図21 analyze()関数の定義部分のrec_counts変数が使われている行を変更
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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()関数の定義コード
|
1 2 3 4 5 6 7 8 9 10 11 |
// 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)
|
1 2 3 4 5 6 7 8 9 10 11 12 |
// 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)
|
1 2 3 4 5 |
// 月曜から金曜ならtrueを返す fn is_weekday(datetime: DT) -> bool { // 月:1, 火:2, .. 金:5, 土:6, 日:7 datetime.weekday().number_from_monday() <= 5 } |
図25 分析レコードを絞り込むためにanalyze()関数の定義コードを変更
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 戻り値型を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 統計的な処理をするためのコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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)
|
1 2 3 4 5 6 7 8 |
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)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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()関数の定義コードを変更
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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構造体の定義コード
|
1 2 3 4 5 |
#[derive(Serialize)] struct DisplayStats { record_counts: RecordCounts, stats: Vec<StatsEntry>, } |
図33 DisplayStats構造体の定義コード
|
1 2 3 4 5 6 7 8 |
#[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()関連関数を定義するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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()関数の定義コードを書き換える
|
1 2 3 |
let display_stats = DisplayStats::new(rec_counts, hist); let json = serde_json::to_string_pretty(&display_stats)?; Ok(json) |

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 コラム「新しい風が吹いてくる」/シェル魔人
「C」や「C++」のような高性能や高効率なソフトウエア開発に適しており、なおかつ、安全性を重視した次世代のプログラミング言語「Rust」が最近注目されています。特集1では、Rustの特徴、使いどころ、開発環境構築方法、プログラミングのやり方などを、初心者にも分かりやすいように、紹介しています。また、CSV 形式のデータを分析するという実用的なプログラムを扱っています。
特集2では、ビジュアル開発ツール「Viscuit」(ビスケット)を使ったプログラミングを紹介しています。「メガネ」というツールだけで、驚くようなプログラムが、子供でも作成できます。Viscuitでコンピュータサイエンスを体験してみましょう。
このほか、連載「ユニケージ新コードレビュー」ではプログラミング言語「AWK」の使いどころを、連載「センサーボードで学ぶ電子回路の制御」ではリアルタイムクロック(RTC)を実装する方法を紹介しています。
今回も読み応え十分のシェルスクリプトマガジン Vol.64。お見逃しなく!
※読者アンケートはこちら
p.31にあるUSPファームのサイトの「http://www.uspeace.jp/」は「https://farm.usp-lab.com/」の誤りです。お詫びして訂正いたします。
情報は随時更新致します。
著者:岡田 健
ユニケージでは、小さな道具の「コマンド」をシェルスクリプトで組み合わせて、さまざまな業務システムを構築しています。本連載では、毎回あるテーマに従ってユニケージによるシェルスクリプトの記述例を分かりやすく紹介します。第10回は、グラフ可視化ソフト「Graphviz」を用いたグラフィカルなコード設計書の作成方法について解説します。
シェルスクリプトマガジン Vol.63は以下のリンク先でご購入できます。![]()
![]()
図1 粗利を計算するシェルスクリプト(Source1.sh)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
###################################### #| 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)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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] } |
著者:しょっさん
ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならないでしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。第3回は、前回のプロジェクトの計画や方針に基づいて2回の開発サイクル(イテレーション)を回します。
シェルスクリプトマガジン Vol.63は以下のリンク先でご購入できます。![]()
![]()
図4 TypeScriptに書き換えたメインのプログラム(index.ts)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
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)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
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)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
{ "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ファイルの内容
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
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の内容
|
1 2 3 4 5 |
module.exports = { coverageDirectory: "coverage", preset: 'ts-jest', testEnvironment: "node", }; |
図9 index.test.tsの内容
|
1 2 3 |
test('1 adds 2 is equal 3', () => { expect(1 + 2).toBe(3); }) |
図11 super.test.tsファイルの内容
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
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ファイルの内容
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# 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の内容
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
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ファイルの内容
|
1 2 3 4 5 6 7 8 9 10 |
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ファイルの内容
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
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ファイルの内容
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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ファイルの内容
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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ファイルの内容
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
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ファイルの内容
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
{ "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" } } |
著者:菅 雄一
WordやExcel、PDFビューアなどで外字を含む文書を表示したり、外字を含む文章をメールで受信して閲覧したりすると、文字化けが起きる場合がある。今回は、私が長年、外字と向き合って格闘してきた話を書くことにする。
シェルスクリプトマガジン Vol.63は以下のリンク先でご購入できます。![]()
![]()
図4 CSSファイルに追加する記述
|
1 2 3 4 5 6 7 |
@font-face { font-family: 'gaiji'; src:url('./gaiji.woff') format('woff'); } div.gaiji { font-family: 'gaiji'; } |
図5 独自外字を表示するためのHTMLファイルの記述例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<!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> |
著者:後藤 大地
Rustは、Mozilla Foundationが開発を支援している比較的新しいプログラミング言語だ。「マルチパラダイムシステムプログラミング言語」と呼ばれており、C/C++の代わりに利用できるプログラミング言語と言われている。C/C++のように見えるが、関数型プログラミングのパラダイムも織り込まれていて、かなり厳密なコーディングができるようになっている。本連載では、しばらく、このRustを取り上げていく。
シェルスクリプトマガジン Vol.63は以下のリンク先でご購入できます。![]()
![]()
図20 Hello Worldのコード
|
1 2 3 |
fn main() { println!("Hello, world!"); } |
著者:飯尾 淳
本連載は、「縦棒」(バーティカルバー)をキーワードにデータ処理や効果的なアルゴリズムについて考えるものです。ここ何回かバーティカルバーから離れて自由なテーマで論じ過ぎていたような気がするので、今回は初心に戻ってヒストグラムを用いたデータ分析の議論を紹介してみることにしましょう。
テーマは入力効率の問題です。普段、皆さんがお使いのキーボードによるタイピングとスマートフォンのフリック入力、はたしてどちらが効率的に入力できるのでしょうか?
シェルスクリプトマガジン Vol.63は以下のリンク先でご購入できます。![]()
![]()
図3 hoge hogeタイピングにおけるデータ送信部のコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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 }, }); } } |
著者:清水 赳
こんにちは、香川大学修士1年の清水です。2年ぶりの登板です。
今回から2回にわたり、OSSのシステム監視ツール「Prometheus」と「Itamae」というプロビジョニングツールを使って、サーバー監視システムを構築する方法を紹介します。今回は、Prometheusの配備とサーバー稼働状況の簡単な可視化について解説します。
シェルスクリプトマガジン Vol.63は以下のリンク先でご購入できます。![]()
![]()
図5 「./cookbooks/prometheus/default.rb」ファイルに記述する内容
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
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」ファイルに記述する内容
|
1 2 3 4 5 6 7 8 9 10 11 |
[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」ファイルに記述する内容
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
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」ファイルに記述する内容
|
1 2 3 4 5 6 7 8 |
[Unit] Description=NodeExporter [Service] ExecStart=/usr/local/bin/node_exporter [Install] WantedBy=multi-user.target |
図10 「./cookbooks/prometheus/files/etc/prometheus/prome
theus.yml」ファイルの編集内容
|
1 2 3 4 5 6 7 8 9 |
scrape_configs: - job_name: 'prometheus' static_configs: - targets: ['localhost:9090'] # ここから下を追加 - job_name: 'node' static_configs: - targets: ['192.168.100.12:9100'] |
著者:米田 聡
シェルスクリプトマガジンでは、小型コンピュータボード「Raspberry Pi」(ラズパイ)向けのセンサー搭載拡張ボード「ラズパイセンサーボード」を制作しました。第10 回では、I2C 接続のモノクロ有機ELディスプレイをGroveコネクタにつなぎ、ラズパイセンサーボードだけでセンサーから情報をディスプレイ上に表示します。
シェルスクリプトマガジン Vol.63は以下のリンク先でご購入できます。![]()
![]()
図6 有機ELディスプレイに日本語を表示するためのライブラリ(OLED.py)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
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)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
#!/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 |
著者:岡本 秀高
米Amazon Web Services(AWS)社が2019年8月に発表した「PartiQL」は、RDBだけでなくKVSやJSONデータ、CSVデータに対しても問い合わせが可能な便利なクエリー言語です。文法はSQLのサブセットになっていて、SQLを知っている人であればすぐに使えます。PartiQL対応の実サービスも提供され始めた今、この新しいクエリー言語を始めてみましょう。
シェルスクリプトマガジン Vol.63は以下のリンク先でご購入できます。![]()
![]()
図13 「sample.csv」ファイルの内容
|
1 2 3 |
3,Bob Smith 4,Susan Smith 6,Jane Smith |
図15 項目名を追加したCSVファイル「with_header.csv」の内容
|
1 2 3 4 |
id,name 3,Bob Smith 4,Susan Smith 6,Jane Smith |
図20 生成されたCSVファイル「out.csv」の内容
|
1 2 3 4 |
name,securityProjectsNum Bob Smith,2 Susan Smith,0 Jane Smith,1 |
図22 生成されたAmazon Ion形式ファイル「out.ion」の内容
|
1 2 3 4 5 6 7 8 9 10 11 12 |
{ name:"Bob Smith", securityProjectsNum:2 } { name:"Susan Smith", securityProjectsNum:0 } { name:"Jane Smith", securityProjectsNum:1 } |
著者:麻生 二郎
小型コンピュータボードの最新機種「Raspberry Pi 4 Model B」が国内で発売できる状態になりました。Raspberry 4 Model Bは、高機能かつ高性能なハードウエアです。このラズパイが登場することでラズパイの適用範囲が広がりますが、同時に古いモデルが不要になります。いらなくなったラズパイを有効利用する三つの方法を紹介します。
シェルスクリプトマガジン Vol.63は以下のリンク先でご購入できます。![]()
![]()
図5 DMを一斉送信するシェルスクリプト(raspi_dm.sh)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#!/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)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
To: %distination_address% From: %source_address% Subject: 同窓会のご案内 Content-Type: text/plain; charset="UTF-8" %name% お元気ですか? ご無沙汰しております。 早速ですが、下記の日程で同窓会を開催いたします。お手数をおかけしますが、出欠をメールにてご返信ください。%name%にお会いできるのを楽しみにしています。 記 日時:2019年11月30日(土曜日) 16時から 場所:〇〇〇高等学校 体育館 会費:5000円 以上 |
図7 送信先のメールアドレスと、個別に書き換えたい情報を保存し
たタブ区切りテキストファイル(sendlist.txt)
|
1 2 3 |
taro@example.co.jp シェルマグ太郎先生 hanako@example.com シェルマグ花子さん jiro@example.com マガジン二郎君 |
図11 受信メッセージをLINEに転送するシェルスクリプト (raspi_mail_line.sh)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
#!/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 |
図5 エアコン制御のシェルスクリプト(raspi_aircon)
|
1 2 3 4 5 6 7 8 |
#!/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 |

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 新しい風が吹いてくる/シェル魔人
人気の小型コンピュータボードの最新版「Raspberry Pi 4 Model B」の国内販売開始が間近にせまっています。Raspberry Pi 4 Model Bは高機能、高性能で、ラズパイの用途が広がることは間違えありません。Raspberry Pi 4 Model Bを購入すると、古いラズパイが不要になるでしょう。そこで、特集1では、シェルスクリプトを用いて、不要になったラズパイを活用する方法を紹介します。メールの一斉配信や転送、エアコン制御を自動化できます。
2020年に5G(第5世代移動通信方式)の本格サービスが開始します。この5Gによって、無線でつながるあらゆるモバイル環境が大幅に刷新されます。特集2では、5Gの概要や仕組み、5Gが実現する世界について解説します。
特別企画では、米Amazon Web Services(AWS)社がオープンソースで公開した、データベース問い合わせ言語「SQL」互換の「PartiQL」を紹介します。PartiSQLは、RDB(リレーショナルデータベース)のみならず、JSON(JavaScript Object Notation)データやCSV(カンマ区切りテキスト)ファイルなどに対しても問い合わせが可能です。
このほか、緊急レポートでは、小型PC-8001の「PasocomMini PC-8001 PCGセット」なども扱っています。
今回も読み応え十分のシェルスクリプトマガジン Vol.63。お見逃しなく!
※読者アンケートはこちら
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日」の誤りです。お詫びして訂正いたします。
p.72の記事タイトル「第3回 MySQL Shellのサーバー運用管理ユーティリティ」は、「第3回 MySQL X DevAPIとドキュメントストア(その1)」の誤りです。お詫びして訂正いたします。
情報は随時更新致します。
前回、「日本語の文字列をURLエンコーディング」できる適当なコマンドがなく、プログラミング言語「Python」でURLエンコーディングの処理を記述しました。シェルスクリプト内に別のプログラミング言語の記述があるのは、違和感があり、またコードも読みにくいでしょう。
そこで、URLエンコーディングの処理をコマンド化し、シェルスクリプトと別の言語のプログラムを混在させないようにします(図1)。このように、よく使う処理をコマンド化することで、別のシェルスクリプトからも利用できます。

著者:石井 一夫
最終回は、次世代データサイエンス言語として注目されている「Julia」を紹介します。高速な実行速度、並列分散処理の容易さ、数式記述の自然さなどを特徴とするJuliaは、今後急速に普及すると考えられます。
シェルスクリプトマガジン Vol.62は以下のリンク先でご購入できます。![]()
![]()
図4 必要パッケージを読み込むコード
|
1 2 3 4 5 6 |
using Pkg Pkg.add("Flux") using Flux using Statistics using Flux: onehotbatch, onecold, crossentropy, throttle using Base.Iterators: repeated |
図5 MNIST データを読み込むコード
|
1 2 |
imgs = Flux.Data.MNIST.images() labels = Flux.Data.MNIST.labels() |
図7 訓練用データの前処理用コード
|
1 2 |
X = hcat(float.(reshape.(imgs, :))...) Y = onehotbatch(labels, 0:9) |
図8 モデルの構築用コード
|
1 2 3 4 |
m = Chain( Dense(28^2, 32, relu), Dense(32, 10), softmax) |
図9 損失関数などの設定用コード
|
1 2 3 4 5 |
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 訓練データを用いた学習をするコード
|
1 |
Flux.train!(loss, params(m), dataset, opt, cb = throttle(evalcb, 10)) |
図13 テストデータの前処理用コード
|
1 2 |
test_X = hcat(float.(reshape.(Flux.Data.MNIST.images(:test), :))...) test_Y = onehotbatch(Flux.Data.MNIST.labels(:test), 0:9) |
図14 テスト画像の数字を推測するコード
|
1 |
onecold(m(test_X[:,5287])) - 1 |
著者:千田貴大
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」の内容
|
1 2 3 4 5 6 7 |
[ -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」ファイルの内容
|
1 2 3 4 5 6 7 |
# 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」ファイルの変更箇所
|
1 2 |
archive_mode = on archive_command = 'cp "%p" "/var/lib/pgsql/11/archive/%f"' |
図17 「recovery.conf」ファイルに記述する必要がある設定
|
1 |
restore_command = 'cp "/var/lib/pgsql/11/archive/%f" "%p"' |
著者:坂東 勝也
ユニケージでは、小さな道具の「コマンド」をシェルスクリプトで組み合わせて、さまざまな業務システムを構築しています。本連載では、毎回あるテーマに従ってユニケージによるシェルスクリプトの記述例を分かりやすく紹介します。第9回は、前回の続きとしてデータの扱い方について解説します。
シェルスクリプトマガジン Vol.62は以下のリンク先でご購入できます。![]()
![]()
図2 LV3データ作成処理のコード(一部抜粋)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
####################################### # 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 |

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 コラム「平凡で地味な人生を幸せに送る」/シェル魔人
オープンソースでは人気が高いデータベース管理システム「PostgreSQL」。特集1では、このPostgreSQLの概要、WindowsやCentOSへのインストール方法、初期設定と基本操作、知っておきたい機能を解説しました。PsotgreSQLを初めて利用する人にも分かりやすい内容になっています。
特集2では、本格的なAI(人工知能)を安価に試せるGPU「Jetson Nano」を扱いました。米NVIDIA社の無料オンライントレーニングを基にして、開発者キットの立ち上げ方からAIによる画像分類までを日本語で分かりやすく紹介しています。
特別企画では、無料の基本ソフト(OS)であるLinuxについて、やさしく解説しました。この企画で「Linuxとは何か」「どう使うのか」を基本からしっかり理解してください。
このほか、ラズパイセンサーボードに接続した4桁の7セグLEDの制御、Pythonの制御構文、次世代データサイエンス言語「Julia」、MySQL Shellの運用管理ユーティリティなどを連載で紹介しています。
今回も読み応え十分のシェルスクリプトマガジン Vol.62。お見逃しなく!
※読者アンケートはこちら
著者:米田 聡
シェルスクリプトマガジンでは、小型コンピュータボード「Raspberry Pi」(ラズパイ)向けのセンサー搭載拡張ボード「ラズパイセンサーボード」を制作しました。第9 回では、I/Oエキスパンダに7セグメントLEDを四つ接続してセンターからの値を表示します。
シェルスクリプトマガジン Vol.62は以下のリンク先でご購入できます。![]()
![]()
図5 LEDを表示するライブラリ(led4digits.py)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
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)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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() |
筆者:宇野 光純
前回に引き続き、Windowsアプリケーションとして動く簡単な2Dゲームの開発を紹介します。汎用プログラミング言語の「C++」と、オープンソースのパソコンゲーム開発用ライブラリの「DXライブラリ」を組み合わせることで、時間と労力は必要ですが、Unityなどのゲームエンジンよりも自由度の高いゲーム開発ができます。
シェルスクリプトマガジン Vol.62は以下のリンク先でご購入できます。![]()
![]()
図2 「Shot.h」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#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」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#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」ファイルに追加するコード
|
1 2 3 4 |
class Object { public: bool flag; // 有効無効を示すフラグ }; |
図5 「Player.h」ファイルに追加するコード
|
1 2 3 4 5 6 7 8 9 10 11 |
#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」ファイルに追加するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
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」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#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」ファイルに追加するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#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」ファイルに追加するコード
|
1 2 3 4 |
#include "Object.h" // 2 オブジェクトの当たり判定用関数 void Collision(Object *obj1, Object *obj2); |
図11 「Info.cpp」ファイルに追加するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#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」ファイルに追加するコード
|
1 2 3 4 5 6 |
class Object { public: int hit_size; // 当たり判定エリアの半径 // 当たり判定後の処理用関数 virtual void CollisionResult() {} }; |
図13 「Player.h」ファイルに追加するコード
|
1 2 3 4 5 |
class Player : public Object { public: int hp_now, hp_max; // 体力の現在値、最大値 void CollisionResult(); // 当たり判定後の処理用関数 }; |
図14 「Player.cpp」ファイルに追加するコード
|
1 2 3 4 5 6 7 8 9 |
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」ファイルに追加するコード
|
1 2 3 4 5 |
class Enemy : public Object { public: int hp_now, hp_max; void CollisionResult(); }; |
図16 「Enemy.cpp」ファイルに追加するコード
|
1 2 3 4 5 6 7 8 9 10 |
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」ファイルに追加するコード
|
1 2 3 4 5 |
class Shot : public Object { public: // 当たり判定後の処理用関数 void CollisionResult(); }; |
図18 「Shot.cpp」ファイルに追加するコード
|
1 2 3 4 |
void Shot::SetImage() { hit_size = 8; } void Shot::CollisionResult() { flag = false; } |
図19 「MainScene.cpp」ファイルに追加するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 |
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」ファイルに追加する コード
|
1 2 3 4 |
// ゲームシーンの取得用関数 int GetGameScene(); // ゲームシーンの設定用関数 void SetGameScene(int val); |
図21 「Info.cpp」ファイルに追加するコード
|
1 2 3 |
int g_GameScene; // シーン管理用変数 int GetGameScene() { return g_GameScene; } void SetGameScene(int val) { g_GameScene = val; } |
図22 「TitleScene.h」ファイルに 記述するコード
|
1 2 3 4 5 6 7 8 9 |
#pragma once class TitleScene { private: int wait; // 待ち時間 public: TitleScene(); bool Update(); void Draw(); }; |
図23 「TitleScene.cpp」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#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」ファイルに記述するコード
|
1 2 3 4 5 6 |
#pragma once class ResultScene { public: bool Update(); void Draw(); }; |
図26 「ResultScene.cpp」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#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」ファイルに追加するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#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」ファイルに追加 するコード
|
1 2 3 4 5 |
class MainScene { public: // void Update(); 行は削除 int Update(); }; |
図31 「MainScene.cpp」ファイルに追加するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// 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 ); } } } } |
著者:飯尾 淳
しばらくデータ分析の話題から遠ざかっているような気がしますが、 ちょっとしたトレーニングをするつもりで前回、前々回と同様にプログラミングの話を続けましょう。今回は、状態遷移図に基づくシステムの動作原理を考えます。最初に簡単なケースを考え、その後で少しブラッシュアップして仕様をアップデートします。
ところで、状態遷移図は「GraphViz1」というツールで描画します。この連載のテーマである「バーティカルバー」(垂直棒または縦棒)を用いた表現も指定できますが、今回は縦横にこだわらず柔軟に表現してみましょう。
シェルスクリプトマガジン Vol.62は以下のリンク先でご購入できます。![]()
![]()
図2 メッセージ出力プログラム
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#!/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)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#!/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() |
スマートフォンで撮影した写真が増えてくると、いつどこで撮影したものなのかが分からなくなることもあるでしょう。ただし、心配は無用です。スマートフォンの多くは「GPS」(全地球測位システム)の半導体(チップ)を内蔵しています。この半導体から取得した位置(緯度・経度)および、撮影日などの情報を、写真データと一緒に保存しています。その情報を調べれば、いつどこで撮影されたものなのかがすぐに分かります。
そこで今回は、写真データから撮影の日時や場所の情報を取り出して表示するシェルスクリプトを作成します(図1)。

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

自宅のネットワークにリモートからアクセスしたい人もいるでしょう。その場合、自宅ネットワークの入り口に配置しているルーターに割り振られた「グローバルIPアドレス」を知らなくてはいけません。
グローバルIPアドレスは、自宅内から次のコマンドを実行すれば確認できます。
|
1 |
$ curl ifconfig.io |
表示されたIPアドレスでアクセスすればよいのですが、常に同じIPアドレスとは限りません。通常、このIPアドレスは動的に変更されます。
「ダイナミックDNS」というサービスを利用すれば、動的にIPアドレスが変わっても自宅ネットワークへ「ドメイン名」(ドメイン付きホスト名)でアクセスできます。ダイナミックDNSに関しては、Web連載「UbuntuではじめるLinuxサーバー」の「第10回 インターネットに公開する」を参照してください。ダイナミックDNSを使えば解決しますが、作業が面倒です。また、サーバーをインターネットに公開しているわけではないので特定のドメイン名を付ける必要はありません。
そこで、ダイナミックDNSを使わずに自宅内からグローバルIPアドレスを監視して更新されたら通知するシェルスクリプトを作成します(図1)。

約5年ぶりに企業向け有償Linuxディストリビューションのメジャーアップデート版「Red Hat Enterprise Linux 8」(RHEL8)が2019年5月リリースされました。RHELは、サーバーOSとして小規模なシステムから基幹系のような大規模なシステムまで、幅広く利用されています。
特集1では、このRHEL8に関する概要や特徴、旧版利用者への注意点、情報入手方法をまとめました。開発元のレッドハットの技術者が自ら執筆していますので、RHELユーザーなら必ず読むべき特集記事です。
2020年にプログラミング教育が小学校で必修となります。「何をすべきか分からない」と感じている人も多いようです。そのような人にお薦めなのが、教育向けシングルボードコンピュータ「BBC micro:bit」です。
特集2では、このBBC micro:bitを利用したプログラミングとサンプルを紹介しています。ブロックを組み合わせるだけでプログラムが書けるので、プログラムが苦手な人、初めてのプログラミングする人にピッタリです。
このほか、特別企画では700円くらいで買えるGPSモジュールの電子基板をパソコンから制御します。また、データベース管理システム「MySQL」の新クライアント「MySQL Shell」の入門連載を開始しました。
今回も読み応え十分のシェルスクリプトマガジン Vol.61。お見逃しなく!
※読者アンケートはこちら
著者:しょっさん
ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならない でしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。第2回は、プロジェクトを開始するまでの準備を解説します。
シェルスクリプトマガジン Vol.61は以下のリンク先でご購入できます。![]()
![]()
図1 注釈を入れたプログラム
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# 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}`); }) |
著者:宇野 光純
今回は、Windowsアプリケーションとして動く簡単な2Dゲームの開発を紹介します。汎用プログラミング言語の「C++」と、オープンソースのパソコンゲーム開発用ライブラリの「DXライブラリ」を組み合わせることで、時間と労力は必要ですが、Unityなどのゲームエンジンよりも自由度の高いゲーム開発ができます。
シェルスクリプトマガジン Vol.61は以下のリンク先でご購入できます。![]()
![]()
図1 DXライブラリを使用する際の基本コード(Main.cpp)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#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」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#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」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 |
#pragma once void InfoInitialize(); void InfoUpdate(); int GetWidth(); int GetHeight(); int GetStageTime(); void KeyUpdate(); bool GetKey(int key_input); |
図4 「Object.h」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 |
#pragma once class Object { protected: int size; // 画像サイズ public: double x, y; // X座標、Y座標 virtual void Update() {} // 更新 virtual void Draw() {} // 描画 }; |
図5 「Player.cpp」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
#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」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#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」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#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」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#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」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#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」ファイルに 記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#pragma once #include "Player.h" #include "Enemy.h" class MainScene { private: Player player; Enemy enemy; public: MainScene(); void Update(); // 更新 void Draw(); // 描画 }; |
図11 コードを追加した「Main.cpp」ファイルの内容
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#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; } |
著者:石井 一夫
今回はディープラーニングのバリエーションとして、画像認識によく用いられる「CNN」(Convolutional Neural Network)と、自然言語処理によく用いられる「RNN」(Recurrent Neural Network)について紹介します。
シェルスクリプトマガジン Vol.61は以下のリンク先でご購入できます。![]()
![]()
図3 サンプルコードのCNN 定義部分(抜粋)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
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 定義部分
|
1 2 3 4 5 6 7 8 9 10 11 |
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 |

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 コラム「近未来に起こってほしいこと」/シェル魔人
著者:麻生 二郎
「NEO-6M」というGPS(地理情報システム)モジュールを搭載したアンテナ付き基板が数百円に 購入できます。この基板にUART-USB変換ケーブルを接続するだけで、パソコンから位置の情報を取得できます。最新のLinuxディストリビューションとシェルスクリプトで遊んでみましょう。
シェルスクリプトマガジン Vol.61は以下のリンク先でご購入できます。![]()
![]()
図13 GPSモジュールを利用するシェルスクリプト(gps_data.sh)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
#!/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 |
著者:飯尾 淳
前回から、「GDHP」(Gniibe Distributed HanoiProtocol)というプロトコルでハノイの塔パズルを解き、それを可視化して確認しようという試みに挑戦しています。プログラムはJavaScript で記述し、「p5.js」というグラフィックスライブラリを利用します。
前回は、初期状態の塔を積み上げるところまで完成させました。今回はアニメーションで実際に動作させ、GDHPでパズルが解けることを確認しましょう。
シェルスクリプトマガジン Vol.61は以下のリンク先でご購入できます。![]()
![]()
図1 向かい合う2本の塔を点滅させるコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
(略) 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()関数を修正する
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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 円盤を移動させる修正
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
(略) 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()関数
|
1 2 3 4 5 6 7 |
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 終了条件を追加
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
(略) 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); } (略) |
著者:米田 聡
シルスクリプトマガジンでは、小型コンピュータボード「Raspberry Pi」(ラズパイ)向けのセンサー搭載拡張ボード「ラズパイセンサーボード」を制作しました。第8回では、前回I/Oエキスパンダ「MCP23017」で増やしたGPIO 端子に7セグメントLEDを接続して制御します。
シェルスクリプトマガジン Vol.61は以下のリンク先でご購入できます。![]()
![]()
図5 7セグメントLEDに数字を出力するクラスライブラリ(ssegled.py)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
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)
|
1 2 3 4 5 6 7 8 |
from ssegled import LED7SEG import time led = LED7SEG() for i in range(10): led.print(i) time.sleep(1) |
第6回では、「ダイレクトメール」(DM)を送信するときにメールサーバーへ接続するプロトコルとして「SMTPS」(SMTP over SSL/TLS)を用いました。もう一つ、安全にメールサーバーにメッセージを送信する著名なプロトコルとして「SMTP STARTTLS」があります。これは、完全に通信が暗号化されたSMTPSと違い、最初は平文で途中から暗号化するという通信方法が可能です。ちなみに、SMTPSでは「465」番のTCPポートを、SMTP STARTTLSでは「587」番のTCPポートを使用します。
SMTP STARTTLSの場合、SMTPSとはアクセス方法が異なるので、シェルスクリプトの書き方が違います。そこで今回は、SMTP STARTTLSを対応したメールサーバーからDMを自動送信するシェルスクリプトを作成します(図1)。

Microsoft OfficeやLibreOfficeなどのオフィスソフトで作成した文書を皆で閲覧可能にする場合、「PDF」(Portable Document Format)形式に変換しておけばよいでしょう。誤って書き換えてしまったりすることがなく、コメントなども付与できて便利です。最近のオフィスソフトには、PDFファイルへ変更する機能があります。また、印刷するときにPDFファイルに保存することも可能です。ただし、これらは自ら操作しなくてはいけません。
そこで今回は、オフィス文書をPDF形式のファイルに自動変換するシェルスクリプトを作成します(図1)。

シェルスクリプトマガジンとビット・トレード・ワンで共同制作した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)連載「ラズパイセンサーボードで学ぶ電子回路の制御」最終回
※関連記事掲載時に追加していきます。
※ラズパイ入門ボード向けソースコード集はこちら。
著者:あかね
米Microsoft社発のオープンソースエディタ「Visual Studio Code」には「Extension」と呼ばれる機能拡張用のソフトウエアが多数提供されています。本連載では便利なExtensionの使い方を中心に紹介します。第3回は、Windowsの OS標準のCL(I シェル環境)「PowerShell」で記述したスクリプトをデバックするためのExtensionを紹介します。
シェルスクリプトマガジン Vol.60は以下のリンク先でご購入できます。![]()
![]()
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# 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'です!' |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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は以下のリンク先でご購入できます。![]()
![]()
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
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}`); }) |
著者:麻生二郎
電子回路を触ってみたい、作ってみたい、制御してみたいと思ったときに、人気の小型コンピュータボード「Raspberry Pi」(ラズパイ)と組み合わせるのが意外と簡単です。本特集では、市販のモジュールと、ラズパイを使って電子回路
の作成や制御を素早く実現する方法を紹介します。
シェルスクリプトマガジン Vol.60は以下のリンク先でご購入できます。![]()
![]()
|
1 2 3 4 5 6 7 8 9 10 |
#!/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') |
|
1 2 3 4 5 6 7 8 |
#!/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]) |
|
1 2 3 4 5 6 7 8 9 10 11 |
#!/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) |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#!/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() |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#!/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() |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#!/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() |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#!/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() |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#!/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() |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#!/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() |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#!/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() |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#!/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() |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#!/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() |
ラズパイのおかげで電子工作が身近になってきています。とはいえ、誰でもすぐに始められるわけではありません。特集1では、市販されている12種類の電子回路モジュールを使って、やさしく、そして簡単に、ラズパイで電子回路の作成と制御ができる方法を紹介しています。こんなセンサーがほしい、液晶パネルに文字を表示したい人も満足できる内容です。
特集2では、Webアプリケーションのプログラミング言語「PHP」をやさしく解説しています。短い、簡単なコードを書きながら、データベース管理システムを利用したWebアプリケーションを作成していきます。
特集3では、本格的な業務システムをシェルスクリプトで構築できる「ユニケージ開発手法」を紹介しています。なぜシェルスクリプトで業務システムが作れるのか、ユニケージ開発手法の専用コマンドのusp Tukubaiとは何か、テキストファイルを利用したデータ管理の仕組みなど、入門者にはぴったりの内容となっています。
特別企画では、ソフトウエア開発における「コーディング」「ビルド」「実装」「テスト」という一連の作業を楽にする「ツールチェーン」を取り上げました。IBM Cloudにおけるツールチェーンの構築・利用方法を具体的に分かりやすく紹介しています。
このほか、新連載「Webアプリケーションの正しい作り方」、人気連載の「ラズパイセンサーボードで学ぶ電子回路の制御」なども掲載しています。
今回も読み応え十分のシェルスクリプトマガジン Vol.60。お見逃しなく!
※読者アンケートはこちら
著者:飯尾淳
本連載の第5 回(Vol.51、2017 年12 月号)で、バーティカルバー(垂直棒)が3 本立っているという理由から「ハノイの塔」というパズルを取り上げました。今回は、そのときの考察を思い出しつつ、ハノイの塔を解くプログラムを再び考えます。
ただし、今回は三つの塔を「上から見下ろした」状態、俯瞰(ふかん)で考えます。至ってシンプルなルールで解ける面白さを、動作の可視化プログラムを用いて確認してみましょう。
シェルスクリプトマガジン Vol.60は以下のリンク先でご購入できます。![]()
![]()
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
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 } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
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(); }) } |

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 コラム「令和はサバイバルの時代」/シェル魔人
著者:小薗井康志、川副博、古川正宏
「コーディング」「ビルド」「実装」「テスト」という一連のソフトウエア開発作業を、「ツールチェーン」を使って、効率良く、そして楽にしてみませんか。クラウドサービス「IBM Cloud」が提供するツールチェーンで、その便利さを味わってみましょう。
シェルスクリプトマガジン Vol.60は以下のリンク先でご購入できます。![]()
![]()
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#!/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 |
著者:柏岡秀男
「PHP」(PHP: Hypertext Preprocessor)は、Webアプリケーションの開発によく使われているプログラミング言語です。本特集では、WindowsやmacOSにPHPプログラムの実行環境やWebサーバー、DBMSサーバーをインストールできる「MAMP」というソフトウエアを使って、PHPプログラミングを手軽に体験する方法を紹介します。これを機会にぜひPHPプログラミングを始めてみてください。
シェルスクリプトマガジン Vol.60は以下のリンク先でご購入できます。![]()
![]()
|
1 2 3 4 5 6 7 8 9 10 |
<!DOCTYPE html> <head> <meta charset="UTF-8"> <title> 現在時刻</title> </head> 現在時刻は <?php echo date("H:i:s") ?> です |
|
1 2 3 4 5 6 |
<!DOCTYPE html> <head> <meta charset="UTF-8"> <title>Hello world</title> </head> <?php echo "Hello, World!"; ?> |
|
1 2 3 4 5 6 |
<!DOCTYPE html> <head> <meta charset="UTF-8"> <title>Hello world</title> </head> Hello, World! |
|
1 2 3 4 5 |
<?php $a = 1; $b = 2; echo $a + $b; ?> |
|
1 2 3 4 5 |
<!DOCTYPE html> <head> <meta charset="UTF-8"> <title><?php echo __FILE__; ?></title> </head> |
|
1 2 3 4 5 |
<?php $a = " シェルスクリプト"; $b = " マガジン"; echo $a . $b; ?> |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<pre> <?php $a = array(1,2,3,4,5); $b = array("a" => 1,"b" => "abc","c" => "123"); $c[0] = 123; $c[1] = $b; var_dump($a); var_dump($b); var_dump($c); echo $a[1]; echo "\n"; echo $b["c"]; echo "\n"; ?> </pre> |
|
1 2 3 4 5 6 |
<?php $a = 1; if($a > 10) { echo "a は10 より大きい"; } ?> |
|
1 2 3 4 5 6 7 8 |
<?php $a = 1; if($a > 10) { echo "a は10 より大きい"; } else { echo "a は10 以下"; } ?> |
|
1 2 3 4 5 6 7 |
<form method="post" action="input.php"> <input type=text name="a"> <input type=submit> </form> <?php var_dump($_POST); ?> |
|
1 2 3 4 5 6 7 |
<form method="post" action="sample_input.php"> <input type=text name="a"> <input type=submit> </form> <?php if($_POST["a"] > 10 ) echo "10 より大きい"; ?> |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<form method="post" action="sample_input2.php"> <input type=text name="a" > <input type=submit> </form> <?php if (isset($_POST["a"])) { if($_POST["a"] > 10 ) { echo "10 より大きい"; } else { echo "10 以下"; } } else { echo " 数字を入力してください"; } ?> |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?php try { $dbh = new PDO( "mysql:host=localhost;dbname=sampledatabase;charset=utf8", "phpuser","testpassword" ); $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch (Exception $e) { die(" データベース接続失敗 ".$e->getMessage()); } echo " 接続できました"; ?> |
|
1 2 3 4 5 6 7 8 9 |
<!DOCTYPE html> <head> <title>TODO データの入力画面</title> </head> <form method="post" action="add.php"> <input type=text name="todo"> <input type=submit> </form> TODO データを入力してください |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?php var_dump($_POST); try { $dbh = new PDO( "mysql:host=localhost;dbname=sampledatabase;charset=utf8", "phpuser","testpassword" ); $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $sql = "INSERT INTO sample (todo) VALUES (:todo_value)"; $stmt = $dbh->prepare($sql); $todo = urldecode($_POST["todo"]); $stmt->bindValue(":todo_value", $todo, PDO::PARAM_STR); $stmt->execute(); echo " データを登録しました"; } catch (Exception $e) { die(" データベース接続失敗 ".$e->getMessage()); } ?> |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?php try { $dbh = new PDO( "mysql:host=localhost;dbname=sampledatabase;charset=utf8", "phpuser","testpassword" ); $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $sql = "SELECT * FROM sample WHERE status IS NULL"; $res = $dbh->query($sql); foreach($res as $row) { echo htmlspecialchars($row["todo"], ENT_QUOTES); echo "<br />"; } } catch (Exception $e) { die(" データベース接続失敗 ".$e->getMessage()); } ?> |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?php try { $dbh = new PDO( "mysql:host=localhost;dbname=sampledatabase;charset=utf8", "phpuser","testpassword" ); $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $sql = "SELECT * FROM sample WHERE status IS NULL"; $res = $dbh->query($sql); foreach($res as $row) { $id = htmlspecialchars($row["id"], ENT_QUOTES); echo htmlspecialchars($row["todo"], ENT_QUOTES); echo "<a href='update.php?id=" . $id . "'> 完了</a><br />"; } } catch (Exception $e) { die(" データベース接続失敗 ".$e->getMessage()); } ?> |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?php try { $dbh = new PDO( "mysql:host=localhost;dbname=sampledatabase;charset=utf8", "phpuser","testpassword" ); $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $sql = "UPDATE sample SET status = 1 WHERE id = :id_value"; $stmt = $dbh->prepare($sql); $id = (int) $_GET["id"]; $stmt->bindValue(":id_value", $id, PDO::PARAM_INT); $stmt->execute(); echo " データを更新しました"; } catch (Exception $e) { die(" データベース接続失敗 ".$e->getMessage()); } ?> |
著者:山下賢治
今回は、Webブラウザでアクセスできる予定共有アプリの作成方法を紹介します。Webアプリケーションフレームワークの「Ruby on Rails」と、JavaScriptのライブラリである「FullCalendar」を組み合わせることで、マウス操作で予定の追加や変更が可能な予定共有アプリを手軽に作成できます。
シェルスクリプトマガジン Vol.60は以下のリンク先でご購入できます。![]()
![]()
|
1 2 3 |
Rails.application.routes.draw do resources :calendar, only: [:index] end |
|
1 |
<div id="calendar"></div> |
|
1 2 3 4 5 6 |
$(document).on 'turbolinks:load',-> $('#calendar').fullCalendar({}) return $(document).on 'turbolinks:before-cache',-> $('#calendar').empty() return |
|
1 2 3 4 5 |
/* *= require_tree . *= require_self *= require fullcalendar */ |
|
1 2 3 4 5 6 7 8 |
//= require moment //= require jquery //= require fullcalendar //= require fullcalendar/locale-all //= require fullcalendar/lang/ja $(function(){ $('#calendar').fullCalendar({}); }) |
|
1 2 3 4 |
Rails.application.routes.draw do resources :calendar, only: [:index] resources :schedules, only: [:index, :create] end |
|
1 2 3 4 5 6 7 8 9 10 11 |
class SchedulesController < ApplicationController def index schedules = Schedule.all respond_to do |format| format.json { ender json: schedules.to_json() } end end end |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//= require rails-ujs //= require activestorage //= require turbolinks //= require_tree . //= require moment //= require jquery //= require fullcalendar //= require fullcalendar/locale-all //= require fullcalendar/lang/ja $(function(){ $('#calendar').fullCalendar({ events: '/schedules.json' }); }) |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
$('#calendar').fullCalendar({ selectable: true, selectHelper: true, draggable: true, select: function(start, end) { var title = prompt("予定名"); var scheduleData; if ( title ) { scheduleData = { title: title, start: start, end: end }; $('#calendar').fullCalendar('renderEvent', scheduleData, true); createSchedule(scheduleData); } $('#calendar').fullCalendar('unselect') }, events: '/schedules.json' }); |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
createSchedule = function(scheduleData) { $.ajax({ type: 'POST', url: "/schedules", data: { title: scheduleData.title, start: String(scheduleData.start), end: String(scheduleData.end), authenticity_token: $("#authenticity_token").val() } }).done(function(data) { alert("登録しました"); }).fail(function(data) { alert("登録失敗しました"); }); }; |
|
1 |
<%= hidden_field_tag "authenticity_token", form_authenticity_token %> |
|
1 2 3 4 5 6 7 |
def create Schedule.create( title: params[:title], start: DateTime.parse(params[:start]), end: DateTime.parse(params[:end]) ) end |
著者:岡田健
ユニケージでは、小さな道具の「コマンド」をシェルスクリプトで組み合わせて、さまざまな業務システムを構築しています。本連載では、毎回あるテーマに従ってユニケージによるシェルスクリプトの記述例を分かりやすく紹介します。第7回は、書き方のルールとなるお作法と分かりやすいコードの書き方について解説します。
シェルスクリプトマガジン Vol.60は以下のリンク先でご購入できます。![]()
![]()
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
cat SALES | # 1:店舗 2:商品No 3:日付 4:売数 # 5:売上 6:割引 join1 key=2 PRICE | # 原価 / 売価を連結 # 1:店舗 2:商品No 3:原価 4:売価 # 5:日付 6:売数 7:売上 8:割引 join1 key=2 CATEGORY | # 部門を連結 # 1:店舗 2:商品No 3:部門 4:原価 # 5:売価 6:日付 7:売数 8:売上 # 9:割引 lcalc '$3,$7,$8,$8-$7*$4' | # 売数 / 売上 / 荒利計算 # 1:部門 2:売数 3:売上 4:粗利 msort key=1 | # 部門でソート sm2 1 1 2 4 | # 売数 / 売上 / 荒利集計 sm5 1 1 2 4 | # 合計行の付加 divsen 2 3 4 | # 千で除算 divsen 3 4 | # 千で再除算 lcalc '$1,$2,$3,$4,100*$4/$3' | # 荒利率を求める # 1:部門 2:売数 3:売上 4:粗利 # 5:粗利率 marume 5.1 | # 四捨五入 join2 key=1 CATEGORY_NAME | # カテゴリ名の連結 # 1:部門 2:部門名 3:売数 4:売上 # 5:粗利 6:粗利率 comma 3 4 5 | # カンマ編集 keta | # 桁そろえ keisen +e | # 罫線を引く cat header - # 出力 |
|
1 2 3 4 5 |
cat ZAIKO | # 1:商品CD 2:規格CD 3:拠点CD 4:サイズCD # 5:産地CD 6:ブランドCD 7:仕入先CD 8:発注先CD # 9:在庫発生日 10:製造日 11:販売日 12:数量 (略) |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
cat ZAIKO | # 1:商品CD 2:規格CD 3:拠点CD 4:サイズCD # 5:産地CD 6:ブランドCD 7:仕入先CD 8:発注先CD # 9:在庫発生日 10:製造日 11:販売日 12:数量 cjoin2 key=1 SHOHIN - | # 1:商品CD 2:商品名 3:規格CD 4:拠点CD # 5:サイズCD 6:産地CD 7:ブランドCD 8:仕入先CD # 9:発注先CD 10:在庫発生日 11:製造日 12:販売日 # 13:数量 cjoin2 key=3 KIKAKU - | # 1:商品CD 2:商品名 3:規格CD 4:規格名 # 5:拠点CD 6:サイズCD 7:産地CD 8:ブランドCD # 9:仕入先CD 10:発注先CD 11:在庫発生日 12:製造日 # 13:販売日 14:数量 (略) |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
cat ZAIKO | # 1:商品CD 2:規格CD 3:拠点CD 4:サイズCD # 5:産地CD 6:ブランドCD 7:仕入先CD 8:発注先CD # 9:在庫発生日 10:製造日 11:販売日 12:数量 cjoin2 key=1 SHOHIN - | # 1:商品CD 2:商品名 3:規格CD 4:拠点CD # 5:サイズCD 6:産地CD 7:ブランドCD 8:仕入先CD # 9:発注先CD 10:在庫発生日 11:製造日 12:販売日 # 13:数量 cjoin2 key=3 KIKAKU - | # 1:商品CD 2:商品名 3:規格CD 4:規格名 # 5:拠点CD 6:サイズCD 7:産地CD 8:ブランドCD # 9:仕入先CD 10:発注先CD 11:在庫発生日 12:製造日 # 13:販売日 14:数量 (略) |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
cat ZAIKO | # 1:商品CD 2:規格CD 3:拠点CD 4:サイズCD # 5:産地CD 6:ブランドCD 7:仕入先CD 8:発注先CD # 9:在庫発生日 10:製造日 11:販売日 12:数量 self 0 1 | cjoin2 key=NF SHOHIN - | delf NF-1 | # 1:商品CD 2:規格CD 3:拠点CD 4:サイズCD # 5:産地CD 6:ブランドCD 7:仕入先CD 8:発注先CD # 9:在庫発生日 10:製造日 11:販売日 12:数量 # 13:商品名 self 0 3 | cjoin2 key=NF KIKAKU - | delf NF-1 | # 1:商品CD 2:規格CD 3:拠点CD 4:サイズCD # 5:産地CD 6:ブランドCD 7:仕入先CD 8:発注先CD # 9:在庫発生日 10:製造日 11:販売日 12:数量 # 13:商品名 14:規格名 (略) |
著者:當仲寛哲
業務システムを開発するときの選択肢として「ユニケージ
開発手法」があります。ユニケージ開発手法は、Linux/UNIX
のコマンドを基盤としたものです。「シェル魔人」と「りな」
の会話を通して、ユニケージ開発手法で業務システムがど
う作られるのかを理解しましょう。
シェルスクリプトマガジン Vol.60は以下のリンク先でご購入できます。![]()
![]()
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#!/bin/bash -vx # # JIKANTAI.CGI 時間帯別販売情報参照システムのCGI スクリプト (略) # POST データの受け取り if [ ! -z "$CONTENT_LENGTH" ] ; then dd bs=$CONTENT_LENGTH | cgi-name -n_ -s_ > $tmp-name ERROR_CHECK else (略) # はめ込み用日付情報の作成 echo $day $cday | # 1: 日付 2: 比較日コード join1 key=2 $tmp-cdaynum - | # 1: 日付 2: 比較日コード 3: 比較日 delf 2 | # 1: 日付 2: 比較日 dayslash --output "yyyy 年_mm 月_dd 日" 1 2 | cat > $tmp-day ERROR_CHECK # 現在時刻 curtime="$(dayslash -d $todayhms --output 'yyyy 年 mm 月 dd 日 HH:MM:SS')" ################################################## # HTML 作成 cat $apld/HTML/JIKANTAI.HTML | mojihame -lSHOP_RECORDS - $tmp-data | mojihame -lSYOHIN_INFO - $tmp-info | mojihame -lSEARCH_DATE - $tmp-day | formhame -s_ -n_ - $tmp-name | calsed '###CURRENT_TIME###' "$curtime" | calsed '###USER###' "$USER" | cat > $tmp-html |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#!/bin/bash -x join1 key=2 PRICE SALES | # 原価/ 売価を連結 join1 key=2 CATEGORY | # カテゴリを連結 lcalc '$3,$7,$8,$8-$7*$4' | # 売数/ 売上/ 荒利計算 msort -p4 key=1 | # カテゴリでソート sm2 1 1 2 4 | # 売数/ 売上/ 荒利集計 sm5 1 1 2 4 | # 合計行の付加 divsen 2 3 4 | # 千で除算 divsen 3 4 | # 千で再除算 lcalc '$1,$2,$3,$4,100*$4/$3' | # 荒利率を求める marume 5.1 | # 四捨五入 join2 key=1 CATEGORY_NAME | # カテゴリ名の連結 comma 3 4 5 | # カンマ編集 keta | # 桁そろえ keisen +e | # 罫線を引く cat header - # 出力 exit 0 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#!/bin/bash # 商品コードに、原価/売価を連結 join1 key=2 PRICE SALES | # 商品コードにカテゴリーコードを連結 join1 key=2 CATEGORY | # [ レイアウト ] # 1:店コード 2:商品コード 3:カテゴリコード 4:仕入価格 5:販売価格 # 6:販売日付 7:販売個数 8:販売金額 9:値引金額 # カテゴリコード、売数、売上、粗利(売上-売数x仕入価格)を計算 lcalc '$3,$7,$8,$8-$7*$4' | # [ レイアウト ] # 1:カテゴリコード 2:販売個数 3:販売金額 3:粗利金額 msort -p4 key=1 | # カテゴリでソート sm2 1 1 2 4 | # 売数 / 売上 / 粗利集計 sm5 1 1 2 4 | # 合計行の付加 divsen 2 3 4 | # 千で除算 divsen 3 4 | # 千で再除算 (売上・粗利は百万円単位) # 粗利率を求める lcalc '$1,$2,$3,$4,100*$4/$3' | # [ レイアウト ] # 1:カテゴリコード 2:販売個数 3:販売金額 4:粗利金額 5:粗利率 # 粗利率を四捨五入 marume 5.1 | # カテゴリ名の連結 join2 key=1 CATEGORY_NAME | # [ レイアウト ] # 1:カテゴリコード 2:カテゴリ名 3:販売個数 4:販売金額 5:粗利金額 # 6:粗利率 comma 3 4 5 | # カンマ編集 keta | # 桁そろえ keisen +e | # 罫線を引く cat header - # 出力 exit 0 |
|
1 2 3 4 |
echo LV1.101.* | xargs cat | msort key=1@NF | upl key=1@NF LV4 - |
著者:米田聡
シェルスクリプトマガジンでは、小型コンピュータボード「Raspberry Pi」(ラズパイ)向けのセンサー搭載拡張ボード「ラズパイセンサーボード」を制作しました。第7 回では、I2C のインタフェースに接続するI/Oエキスパンダ「MCP23017」でGPIO 端子を増やす方法を紹介します。
シェルスクリプトマガジン Vol.60は以下のリンク先でご購入できます。![]()
![]()
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
from smbus import SMBus class MCPGPIO(): __IODIR = [0x00, 0x01] # レジスタ番号 __GPPU = [0x0C, 0x0D] __GPIO = [0x12, 0x13] __OLAT = [0x14, 0x15] INPUT = 1 OUTPUT = 0 INPUTPULLUP = 3 HIGH = 1 LOW = 0 def __init__(self,address = 0x20): self.bus = SMBus(1) self.addr = address def setup(self, pin, dir): if pin < 16: dir = self.bus.read_byte_data(self.addr,self.__IODIR[int(pin/8)]) dir &= ~(0x01 << int(pin % 8)) dir |= (dir & 1) << int(pin % 8) self.bus.write_byte_data(self.addr, self.__IODIR[int(pin/8)], dir) if (dir & 1) == 1: pu = self.bus.read_byte_data(self.addr,self.__GPPU[int(pin/8)]) pu &= ~(0x01 << int(pin % 8)) pu |= ((dir >> 1) & 1) << int(pin % 8) self.bus.write_byte_data(self.addr, self.__GPPU[int(pin/8)], pu) def input(self, pin): r = 0 if pin < 16: gp = self.bus.read_byte_data(self.addr, self.__GPIO[int(pin/8)]) r = (gp >> int(pin%8) & 1) return r def output(self, pin, val): if pin < 16: gp = self.bus.read_byte_data(self.addr, self.__GPIO[int(pin/8)]) gp &= ~(0x01 << int(pin % 8)) gp |= (val & 1) << int(pin % 8) self.bus.write_byte_data(self.addr, self.__GPIO[int(pin/8)], gp) @property def gpioa(self): return self.bus.read_byte_data(self.addr, self.__GPIO[0]) @gpioa.setter def gpioa(self, value): self.bus.write_byte_data(self.addr,self.__GPIO[0], value) @property def gpiob(self): return self.bus.read_byte_data(self.addr, self.__GPIO[1]) @gpiob.setter def gpiob(self, value): self.bus.write_byte_data(self.addr,self.__GPIO[1], value) |
著者:石井一夫
今回は、機械学習を実施する際に一番問題となる過学習の問題を取り上げます。過学習というのは、機械 学習のモデルが、ある特定の状況に過剰適合してしまい、新たなサンプルに対してうまく予測ができなくなるという現象です。
シェルスクリプトマガジン Vol.60は以下のリンク先でご購入できます。![]()
![]()
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
l1_model = keras.models.Sequential([ keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l1(0.001), activation=tf.nn.relu, input_shape=(NUM_WORDS,)), keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l1(0.001), activation=tf.nn.relu), keras.layers.Dense(1, activation=tf.nn.sigmoid) ]) l1_model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy', 'binary_crossentropy']) l1_model_history = l1_model.fit(train_data, train_labels, epochs=20, batch_size=512, validation_data=(test_data, test_labels), verbose=2) |
|
1 2 |
plot_history([('baseline', baseline_history), ('l1', l1_model_history)]) |
LinuxやUnix系OSの設定ファイルを見てみると、「#」で始まる行が説明が書かれた「コメント」になります。ドキュメントを見なくても設定が記述できるように、複数行にわたったコメントを使って詳細に説明が書かれていることもあります。
コメントはとても便利です。しかし、コメントとコメントの間にデフォルト(初期状態)の設定が書き込まれている場合、コメントが邪魔になることもあります。そこで、今回はデフォルトの設定をすぐに確認するための、コメント行を取り除くシェルスクリプトを作成します。
LinuxやUnix系OSの設定ファイルの多くは「/etc」ディレクトリの下に保存されています。いくつかの設定ファイルを開いてみると、前述したように先頭に「#」が付いた行がコメントになっています。よって、今回のシェルスクリプトでは、行頭が「#」を見つけて、その行を削除すればよいわけです。
また、設定ファイルによっては空白行も残さない方が見やすい場合もあります。そこで、空白行を残す場合と残さない場合の両方の処理ができるようにします。
シェルスクリプトなどのプログラムを作成する上で、とても重要なのは「ロジック」を考えることです。簡潔で最適なロジックを思いつくのは重要ですが、シェルスクリプトの場合は、いろいろ悩まずにLinux/Unix系OSのコマンドリファレンスなどを見ながらロジックが浮かんだら、すぐに書いてみることです。
自分が使えればよいので、知っているコマンドを組み合わせるだけで構いません。無駄が多かったり、不思議な処理になっていたりしても正しく動けば問題ありません。とりあえず、完成させることが重要です。
本連載で紹介するシェルスクリプトの例もその前提で記述しています。後からもっと簡単に書ける方法が思いついてもそのままにしています。

シェルスクリプトは、Linux/Unix系OSのコマンドのみで記述できるプログラムです。さまざまな処理を簡単に記述でき、LinuxやUnix系OSの環境があれば、すぐに実行して試せるのでとても便利です。本連載では、役立ちそうなシェルスクリプトを紹介しながら、シェルスクリプトの書き方を説明していきます(隔週更新予定)。
なお、シェルスクリプトを開発・実行するには、Linux/Unix系OSがインストールされたパソコンが必要です。例えば、「連載 UbuntuではじめるLinuxサーバー」の第1回~第4回で紹介した方法でUbuntu Serverをインストールしたパソコンを用意できます。
記事中で紹介したシェルスクリプトのコードは、以下のページから入手できます(WordPressのバグなのか、何らかのパターンで「$」などの文字が消えてしまうことがありますのでシェルスクリプト自体はこちらから入手してください)。
https://github.com/shellscript-magazine/rensai_shellscript1
目次
第1回 Amazonの商品ページURLをきれいにする
第2回 コメント行を削除する
第3回 写真を整理する
第4回 ファイルサーバーを構築する
第5回 文書をPDFファイルに変換する
第6回 DMを自動送信する(SMTPS編)
第7回 DMを自動送信する(STARTTLS編)
第8回 自動でアーカイビングする
第9回 グローバルIPアドレスを通知する
第10回 写真から場所を調べる
第11回 大切なメールだけをチャットルームに送る
第12回 コマンドを作る
最終回 不正アクセスを通知する

004 レポート Homebrew 2.0.0リリース
005 レポート runcの脆弱性問題
006 NEWS FLASH
008 特集1 Linuxパーフェクトカスタマイズ/麻生二郎 コード掲載
020 特集2 AWS Lambdaを好きな言語で使おう/岡本秀高 コード掲載
028 特別企画 リレー式コンピュータプログラミング/若菜魁、入口雄也、八木武尊
036 ラズパイセンサーボードで学ぶ 電子回路の制御/米田聡
040 試み/桑原滝弥・イケヤシロウ
042 バーティカルバーの極意/飯尾淳
046 円滑コミュニケーションが世界を救う!/濱口誠一
048 中小企業手作りIT化奮戦記/菅雄一
056 法林浩之のFIGHTING TALKS/法林浩之
058 漢のUNIX/後藤大地
064 人間とコンピュータの可能性/大岩元
066 香川大学SLPからお届け!/竹原一駿 コード掲載
071 姐のNOGYO
072 UNIXの歴史を振り返る/古寺雅弘
078 Node.js/Expressで楽々Webアプリ開発/しょっさん コード掲載
089 ユニケージ新コードレビュー/坂東勝也 コード掲載
096 Techパズル/gori.sh
098 コラム「ユニケージお作法のココロ」/シェル魔人
オープンソースのソフトウエアを組み合わせて、好みのOSが作れるのが「Linux」です。特に、好みに近いLinuxディストリビューションがあれば、それをカスタマイズするだけで自分だけのLinuxが作れます。特集1では、軽量で高速なLinuxディストリビューション「Lubuntu」をインストールメディアからカスタマイズして自分好みに仕立てる方法を紹介しています。
特集2では、AWSのサーバーレスサービス「AWS Lambda」に追加された新機能「AWS Lambda Custom Runtimes」を扱っています。AWS Lambda Custom Runtimesが登場する以前は、サービスとして実行される「Lambda」関数は、Node.jsのJavaScriptやPython、Rubyで記述する方法しかありませんでした。AWS Lambda Custom Runtimesの登場により、言語環境となるランタイムを用意すれば、その言語でLambda関数を記述できるようになりました。
特別企画では、1960年代に制作された国産リレー式コンピュータである「FACOM138A」のプログラミングに挑戦した大学生の奮闘記を紹介しています。
このほか、「センサーボードで学ぶ 電子回路の制御」「姐のNOGYO」などの人気連載も掲載しています。
今回も読み応え十分のシェルスクリプトマガジン Vol.59。お見逃しなく!
※読者アンケートはこちら
著者:坂東勝也
ユニケージでは、小さな道具の「コマンド」をシェルスクリプトで組み合わせて、さまざまな業務システムを構築しています。本連載では、毎回あるテーマに従ってユニケージによるシェルスクリプトの記述例を分かりやすく紹介します。第6回は、仕入伝票処理システムを例に最新マスターを取得するコードをレビューします。
シェルスクリプトマガジン Vol.59は以下のリンク先でご購入できます。![]()
図11 伝票番号で並べ替え
|
1 2 3 4 5 6 7 8 9 10 |
145 # L3取得 146 if [ -s $tmp-check_data.1 ] ; then 147 cat $lv3d/SIRE_HEAD/SIRE_HEAD.$ymonth | 148 # 同じ伝票番号の情報を古い順にソート(最も新しいデータが一番下に来るように) 149 msort key=1@NF@NF-1r > $tmp-sire_header_l3 150 ERROR_CHECK 151 else 152 :> $tmp-sire_header_l3 153 ERROR_CHECK 154 fi |
図12 更新された仕入伝票を調べる
|
1 2 3 4 5 |
130 # 仕入伝票ヘッダーのLV1データの取得 131 # LV1データがあるかチェック 132 ls $lv1d/$today/SIRE_HEAD_* | 133 gyo > $tmp-check_data.lv1 134 ERROR_CHECK |
図13 指定月の仕入伝票の取得
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
156 # INPUT(L1)取得 157 if [ -s $tmp-check_data.2 ] ; then 158 cat $lv1d/SIRE_HEAD_* | 159 #dayslash --input yyyy/mm/dd --output yyyymmdd 2 - | 160 # 指定の日付の範囲内のデータを抽出 161 uawk '$2>='$range_from' && $2<='$range_to'' | 162 # 同じ伝票番号の情報を古い順にソート(最も新しいデータが一番下に来るように) 163 msort key=1@NF@NF-1r > $tmp-sire_header_in 164 ERROR_CHECK 165 else 166 :> $tmp-sire-header_in 167 ERROR_CHECK 168 fi |
図14 L1とL3のデータをマージ
|
1 2 3 4 5 6 7 |
170 # L1とL3をマージ 171 # 同じ伝票番号の中で最も新しいもののみを残す 172 upl key=1 $tmp-sire_header_l3 $tmp-sire_header_in | 173 # 削除フラグの立っている伝票を削除 174 delr NF-1 1 | (略) 186 ERROR_CHECK |
著者:岡本秀高
カンファレンス「AWS re:invent 2018」にて発表された新サービス「AWS Lambda Custom Runtimes」。これまではサポートをアナウンスした言語でしか「Lambda関数」を作成できませんでした。このサービスの登場により好きな言語で作成可能となりました。本特集では、AWS Lambda Custom Runtimesの使い方から、ランタイム(実行環境)を実際に作って利用するところまでを分かりやすく紹介します。
シェルスクリプトマガジン Vol.59は以下のリンク先でご購入できます。![]()
図3 bootstrapファイルの内容
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#!/bin/sh set -euo pipefail # Initialization - load function handler source $LAMBDA_TASK_ROOT/"$(echo $_HANDLER | cut -d. -f1).sh" # Processing while true do HEADERS="$(mktemp)" # Get an event EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next") REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2) # Execute the handler function from the script RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA") # Send the response curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response" -d "$RESPONSE" done |
図5 function.shファイルの内容
|
1 2 3 4 5 6 7 8 |
#!/bin/sh function handler () { EVENT_DATA=$1 echo "$EVENT_DATA" 1>&2; RESPONSE="Request: '$EVENT_DATA'" echo $RESPONSE } |
図6 template.yamlファイルの内容
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
AWSTemplateFormatVersion: 2010-09-09 Description: My PHP Application Transform: AWS::Serverless-2016-10-31 Resources: myRuntime: Type: AWS::Serverless::Function Properties: FunctionName: !Sub ${AWS::StackName}-myRuntime Description: My fisrt custom runtime Runtime: provided Handler: function.handler MemorySize: 3008 Timeout: 30 Layers: - !Sub 作成したランタイムのLayerVersionArn |
著者:竹原一駿
USB接続のバーコードリーダーを用いてバーコード化されたデータを読み取り、その結果をサーバーに送って集計するデータ収集システムを開発しました。今回は、同システムについて紹介します。クライアントはGo、集計サーバーは主にPerlで記述しています。クライアントもサーバーもDocker環境で簡単に動かせますので、ぜひ試してみてください。
シェルスクリプトマガジン Vol.59は以下のリンク先でご購入できます。![]()
図8 バーコードリーダーで読み取ったデータを取得する関数
|
1 2 3 4 5 |
func ScanStdin() string { stdin := bufio.NewScanner(os.Stdin) stdin.Scan() return stdin.Text() } |
図9 意見を示す数値を文字列に変換するコード
|
1 2 3 4 5 6 7 8 9 10 11 |
const AGREE = "100" // 賛成 const OPPOSITE = "200" // 反対 const NEUTRAL = "300" // 中立 if sData == AGREE { sData = "agree" // 意見の文字列を代入 } else if sData == OPPOSITE { sData = "opposite" } else if sData == NEUTRAL { sData = "neutral" } |
図10 データを集計サーバーに送るためのコード
|
1 2 3 4 5 6 |
resp, err := http.Get(baseurl) if err != nil { fmt.Println(err) return err } defer resp.Body.Close() |
図11 HTTPサーバー機能を提供するMyWebServerパッケージの記述
|
1 2 3 4 5 6 7 8 9 10 |
#!/usr/bin/perl { package MyWebServer; use HTTP::Server::Simple::CGI; use base qw(HTTP::Server::Simple::CGI); #継承 use strict; use warnings; # 以降の処理はここに書く. } my $pid = MyWebServer->new(80)->run(); |
図12 CGIで日本語を表示するための記述
|
1 2 3 4 |
print $cgi->header(-charset=>"utf-8"), $cgi->start_html(-lang => 'ja','Not found'), $cgi->h1('Not found'), $cgi->end_html; |
図13 ハッシュ変数にサブルーチンのリファレンスを保存
|
1 2 3 4 5 6 |
my %dispatch = ( '/index' => \&resp_index, # index '/insert' => \&resp_insert, # データの挿入 '/operate' => \&resp_operate, # 質問番号の調整 '/analy' => \&resp_analy, # 解析 ); |
図14 insertページを表示するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
sub resp_insert { my $cgi = shift; return if !ref $cgi; my $user = $cgi->param('user'); # ユーザーID my $opi = $cgi->param('opi'); # 意見 my $dbh = DBI->connect( "dbi:mysql:database=opinidb;". "host=mysql;port=3306", 'user','password' ); #データベースの接続 my $sth = $dbh->prepare( "INSERT INTO opinidb.opinion(USER,NUM,OPI)". " VALUES (?,?,?);" ); $sth->execute($user,$ques_num,$opi); # SQL生成,実行 $sth->finish; $dbh->disconnect; #接続終了 print $cgi->header(-charset=>"utf-8"), $cgi->start_html("insert"), $cgi->h1("Question number is $ques_num"), "ユーザ:$user, 意見$opi", $cgi->end_html; } |
図15 analyページを表示するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
my @opilist = ('agree','opposite','neutral'); print $cgi->header(-charset=>"utf-8"); print $cgi->start_html(-lang => 'ja',"analy"); foreach(@opilist){ print $cgi->h2("count : ".$_); my $sth = $dbh->prepare( "SELECT NUM AS ques_num , ". "COUNT(*) AS countopi ". "FROM opinidb.opinion ". "WHERE OPI LIKE '\%".$_."\%' ". "GROUP BY ques_num" ); $sth->execute(); # SQL生成,実行 print '<table border=1>'; print '<tr><th>質問番号</th><th>選んだ人数</th></tr>'; while(my $datahash = $sth->fetchrow_hashref){ print '<tr><td>'. $datahash->{'ques_num'}.'</td><td>'. $datahash->{'countopi'}.'</td></tr>'; } print '</table>'; $sth->finish; } |
著者:麻生二郎
Linuxには、「ディストリビューション」と呼ばれるさまざまな種類があります。しかし、その中から自分の好みに合ったものを見つけるのは意外と困難です。そこで、軽量なLinuxディストリビューション「Lubuntu」のインストールメディアをカスタマイズして、自分好みのLinuxディストリビューションを手に入れましょう。
シェルスクリプトマガジン Vol.59は以下のリンク先でご購入できます。![]()
図27 スライドショーの設定を書き換える箇所
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
Slide { Image { anchors.centerIn: parent id: image1 x:0 y:0 width: 810 height: 485 fillMode: Image.PreserveAspectFit smooth: true source: "shmag1.png" } } Slide { Image { anchors.centerIn: parent id: image2 x: 0 y: 0 width: 810 height: 485 fillMode: Image.PreserveAspectFit smooth: true source: "shmag2.png" } } } |
著者:しょっさん
プログラミング言語「JavaScript」の実行環境「Node.js」と「Express」フレームワークを使って、基本となるWebアプリの開発手法を習得しましょう。最終回は、サンプルの「蔵書管理アプリケーション」の課題を解決し、「SPA」(Single Page Application)として実装します。
シェルスクリプトマガジン Vol.59は以下のリンク先でご購入できます。![]()
図2 認証時にアクセストークンを発行する部分のプログラム
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
var express = require('express'); var router = express.Router(); const jwt = require('jsonwebtoken'); const models = require('../models'), User = models.user; var app = express(); // パスワードハッシュ化 const hashPassword = (password, salt) => { if (password) { var bcrypt = require('bcrypt'); return bcrypt.hashSync(password, salt); } else { return null; } }; // ログイン処理 router.post('/', (req, res) => { const username = req.body.email; const password = req.body.password; User.findOne({ where: { email: username } }) .then(user => { if (!user) { res .status(401) .json({ errors: { message: 'Incorrect email.' } }); } else if (hashPassword(password, user.salt) !== user.password) { res .status(401) .json({ errors: { message: 'Incorrect password.' } }); } else { const opts = { issuer: process.env.ISSUER, audience: process.env.AUDIENCE, expiresIn: process.env.EXPIRES, }; const secret = process.env.SECRET; res .status(200) .json({ 'token': jwt.sign({ id: user.id }, secret, opts) }); } }); }); |
図3 URLへのアクセスしたときのアクセストークンの検査部分のプログラム
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
const passport = require('passport'); const passportJWT = require('passport-jwt'); const ExtractJWT = passportJWT.ExtractJwt; const JWTStrategy = passportJWT.Strategy; // using authentication strategy passport.use(new JWTStrategy( { jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken(), issuer: process.env.ISSUER, audience: process.env.AUDIENCE, secretOrKey: process.env.SECRET }, (jwt_payload, done) => { User.findOne({ where: { id: jwt_payload.id } }) .then(user => { if (user) { done(null, user); } else { done(null, false); } }) .catch(err => { return done(err, false); }); } )); |
図4 カスタムコールバックを作成している部分のプログラム
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// passport-jwt: custom callback // Tokenを持っていなかったり、Invarid Tokenの場合、Json形式で返答しなかったりするので、カスタムコールバックで準備する必要がある const jwt = (req, res, next) => { passport.authenticate('jwt', { session: false }, (err, user, info) => { if (err) { return next(err); } if (!user) { return res.status(401).json({ 'errors': { 'message': info || 'user unknown' } }).end(); } req.user = user; next(); })(req, res, next); }; // ルーティング: 認証が必要なURIには、jwt関数コールを追記するだけでよい app.use('/api/auth', auth); app.use('/api/books', jwt, books); |
図5 表現に合わせてJSON形式で返却する
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// 1冊の本の情報を取得する _get_book(book_id) { return libraries.findOne({ where: { user_id: this._user_id, id: book_id }, include: [{ model: comments, required: false }] }); } // 1冊の本の詳細を表示する find(req, res) { this._get_book(req.params.id) .then(result => { res .status(200) .json(result.get()); }).catch(() => { res .status(200) .json({}); }); } |
図6 エラー画面をJSON形式に変更する
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// catch 404 and forward to error handler app.use((req, res, next) => { var err = new Error('Not Found'); err.status = 404; next(err); }); // error handler app.use((err, req, res, next) => { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; // render the error page res.status(err.status || 500); res.json({ success: false, errors: { message: err.message } }); }); |
図7 テストコードを変更する
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
describe('with Login', () => { // store a jwt token let jwt_token; beforeAll((done) => { request(app) .post('/api/auth/') .send(userCredentials) .end((err, res) => { jwt_token = res.body.token; done(); }); }); describe('GET /api/books/', () => { it('respond with REST', (done) => { request(app) .get('/api/books/') .set('Accept', 'application/json') .set('Authorization', `Bearer ${jwt_token}`) .expect('Content-Type', /json/) .expect(200, done); }); }); }); |
インターネット上のサイトで欠かせないサーバーソフトウエアが、データを格納・管理する「データベース管理システム」です。特集1では、オープンソースのデータベース管理システムとして人気が高い「MySQL」を紹介します。入手方法、Linuxディストリビューション「CentOS」へのインストール方法だけでなく、データベースの核となる「ストレージエンジン」、運用管理やトラブルシューティングなどで重要な「ログ」、データベース処理で知っておきたい「トランザクション」や「ロック」に触れます。
特集2では、小型コンピュータボード「Raspberry Pi」上で「IchigoJam BASIC RPi」による「BASIC」プログラミングを紹介します。BASICは、古くからある言語で、プログラミングの基本が学べます。子供から大人まで楽しめますので、親子で一緒にプログラミングを始めましょう。
特別企画では、サイボウズの業務改善プラットフォーム「kintone」上で実用的な業務システムの「交通費申請システム」を作成します。入力フォームにテキストボックスやボタンなどの部品を配置し、簡単なワークフローを設定するだけなので、プログラムを一切書く必要はありません。無料のアカウントを登録すれば、すぐに試せます。
このほか、「中小企業手作りIT化奮戦記」「センサーボードで学ぶ 電子回路の制御」などの人気連載も掲載しています。
今月も読み応え十分のシェルスクリプトマガジン Vol.58。お見逃しなく!
※読者アンケートはこちら
著者:竹原 一駿
2018年秋に広島と香川で「オープンソースカンファレンス」(OSC)が開催されました。SLPは、両OSCでブースを出展し、OSC香川では3人の学生がライトニングトーク(LT)を行いました。今回は、これらの活動の内容や、それによって得られた体験などについて報告します。OSCへの一般参加や出展などを考えている読者の方の参考になれば幸いです。
シェルスクリプトマガジン Vol.58は以下のリンク先でご購入できます。![]()
|
1 2 3 4 5 6 |
unshare( CloneFlags::CLONE_NEWPID | CloneFlags::CLONE_NEWIPC | CloneFlags::CLONE_NEWUTS | CloneFlags::CLONE_NEWNS | CloneFlags::CLONE_NEWUSER,) |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
match fork() { Ok(ForkResult::Parent { child, .. }) => { match waitpid(child, None).expect("waitpid faild") { WaitStatus::Exited(_, _) => {} WaitStatus::Signaled(_, _, _) => {} _ => eprintln!("Unexpected exit."), } } Ok(ForkResult::Child) => { sethostname(&self.name).expect("Could not set hostname"); fs::create_dir_all("proc").unwrap_or_else(|why| { eprintln!("{:?}", why.kind()); }); println!("Mount procfs ... "); mounts::mount_proc().expect("mount procfs failed."); let cmd = CString::new(self.command.clone()).unwrap(); let default_shell = CString::new("/bin/bash").unwrap(); let shell_opt = CString::new("-c").unwrap(); execv(&default_shell, &[default_shell.clone(), shell_opt, cmd]).expect("execution faild."); } Err(_) => eprintln!("Fork failed"), } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function add_1(number, result_text, c) { var num = []; if (parseInt(number / 10) == 0 && (number != 6 || number != 9)) { return 0; } // 1桁になって悪魔の数字じゃなければ失敗 num = num_split_onedigit(number); // 数値を1桁ずつ分解 sum = num_add_onedigit(num); // すべて加算 result_text[c] = array_value_text(num, sum, "+"); // 計算過程の文字列を作成 var check = akumanumber_check(sum, result_text); // 悪魔の数字かどうかチェック if (check != 0) { return check; } // 悪魔の数字なら終了 var result = add_1(sum, result_text, c + 1); // 処理1を実行(再帰) if (result != 0) { return result; } else { result_t ext.splice(c, 1); } var result = mult_1(sum, result_text, c + 1); // 処理2を実行(相互再帰) if (result != 0) { return result; } else { result_text.splice(c, 1); } return 0; } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function mult_1(number, result_text, c) { var num = []; if (parseInt(number / 10) == 0 && (number != 6 || number != 9)) { return 0; } // 1桁になって悪魔の数字じゃなければ失敗 num = num_split_onedigit(number); // 数値を1桁ずつ分解 sum = num_mult_onedigit(num); // すべて乗算 result_text[c] = array_value_text(num, sum, "×"); // 計算過程の文字列を作成 var check = akumanumber_check(sum, result_text); // 悪魔の数字かどうかチェック if (check != 0) { return check; } // 悪魔の数字なら終了 var result = add_1(sum, result_text, c + 1); // 処理1を実行(相互再帰) if (result != 0) { return result; } else { result_text.splice(c, 1); } var result = mult_1(sum, result_text, c + 1); // 処理2を実行(再帰) if (result != 0) { return result; } else { result_text.splice(c, 1); } return 0; } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
int SystemAnalyzer::GetPreTick_(void) { // 演算に使用されたTick値を取得 FILE *infile = fopen("/src/proc/stat", "r"); if (NULL == infile) { cout << "[GetCPUUsage]<<Cannot open /src/proc/stat" << endl; return 0; } int usr, nice, sys; char buf[1024]; // 文字列"cpu"の部分の入力用 int result = fscanf(infile, "%s %d %d %d", buf, &usr, &nice, &sys); if (result == -1) { cout << "[GetCPUUsage]<<Cannot read fscanf" << endl; return 0; } fclose(infile); return usr + nice + sys; } |
著者:しょっさん
プログラミング言語「JavaScript」の実行環境「Node.js」と「Express」フレームワークを使って、基本となるWebアプリの開発手法を習得しましょう。第4回は「蔵書管理アプリケーション」のサンプルプログラムで認証機能を実現す
る方法を解説します。
シェルスクリプトマガジン Vol.58は以下のリンク先でご購入できます。![]()
|
1 2 3 4 5 |
user.associate = function (models) { // associations can be defined here user.hasMany(models.Library, { foreignKey: 'user_id' }); }; return user;return user; |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
'use strict'; module.exports = (sequelize, DataTypes) => { var Library = sequelize.define('Library', { book_title: DataTypes.STRING, author: DataTypes.STRING, publisher: DataTypes.STRING, image_url: DataTypes.STRING(2048), user_id: DataTypes.INTEGER }, { underscored: true }); Library.associate = function (models) { // associations can be defined here Library.hasMany(models.Comment, { foreignKey: 'book_id'}); }; return Library; }; |
|
1 2 3 4 5 6 7 8 9 10 11 |
user_id: { type: Sequelize.INTEGER, allowNull: false, foreignKey: true, references: { model: 'users', key: 'id', }, onUpdate: 'RESTRICT', onDelete: 'RESTRICT', }, |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
'use strict'; module.exports = { up: (queryInterface, Sequelize) => { const models = require('../models'); return models.user.bulkCreate([ { id: 1, email: 'tak@oshiire.to', password: '$2b$10$t73WMpPlvyhkWuL.ALWe..OKbU1q1ssR4K5ezVTXLlvaDMtUuAqve', salt: '$2b$10$t73WMpPlvyhkWuL.ALWe..' }, { id: 2, email: 'sho@oshiire.to', password: '$2b$10$t73WMpPlvyhkWuL.ALWe..OKbU1q1ssR4K5ezVTXLlvaDMtUuAqve', salt: '$2b$10$t73WMpPlvyhkWuL.ALWe..' }, { id: 3, email: 'shosan@oshiire.to', password: '$2b$10$t73WMpPlvyhkWuL.ALWe..OKbU1q1ssR4K5ezVTXLlvaDMtUuAqve', salt: '$2b$10$t73WMpPlvyhkWuL.ALWe..' } ]); }, down: (queryInterface, Sequelize) => { return queryInterface.bulkDelete('users', null, {}); } }; |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
var passport = require('passport'); var LocalStrategy = require('passport-local').Strategy; const hashPassword = (password, salt) => { var bcrypt = require('bcrypt'); var hashed = bcrypt.hashSync(password, salt); return hashed; }; passport.use(new LocalStrategy( { usernameField: 'email', passwordField: 'password' }, (username, password, done) => { models.user.findOne({ where: { email: username } }).then(user => { if (!user) return done(null, false, { message: 'Incorrect email.' }); if (hashPassword(password, user.salt) !== user.password) return done(null, false, { message: 'Incorrect password.' }); return done(null, user.get()); }); } )); |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var session = require('express-session'); passport.serializeUser((user, done) => { done(null, user.id); }); passport.deserializeUser((id, done) => { models.user.findById(id).then(user => { if (user) { done(null, user.get()); } else { done(user.errors, null); } }); }); |
|
1 2 3 4 5 6 7 8 9 10 |
app.use((req, res, next) => { if (req.isAuthenticated()) return next(); if (req.url === '/' || req.url === '/login') return next(); res.redirect('/'); }); app.use('/', index); app.use('/books', books); |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
/* eslint-env jasmine */ // routing テスト const request = require('supertest-session'); const app = require('../app'); //let's set up the data we need to pass to the login method const userCredentials = { email: 'tak@oshiire.to', password: 'password' }; const wrongEmailCredentials = { email: 'foo', password: 'password' }; const wrongPasswordCredentials = { email: 'tak@oshiire.to', password: 'foo' }; //now let's login the user before we run any tests const authenticatedUser = request(app); describe('POST /login', () => { it('should redirect to / with unloggined user', (done) => { request(app) .get('/books/') .expect(302) .expect('Location', '/', done); }); it('should success login with correct user', (done) => { request(app) .post('/login') .send(userCredentials) .expect(302) .expect('Location', '/books/', done); }); it('should deny login with wrong email', (done) => { request(app) .post('/login') .send(wrongEmailCredentials) .expect(302) .expect('Location', '/', done); }); it('should deny login with wrong password', (done) => { request(app) .post('/login') .send(wrongPasswordCredentials) .expect(302) .expect('Location', '/', done); }); }); describe('with Login', () => { beforeAll((done) => { authenticatedUser .post('/login') .send(userCredentials) .expect(302, done); }); describe('GET /', () => { it('respond with http', (done) => { authenticatedUser .get('/') .set('Accept', 'text/html') .expect(200, done); }); }); : (中略) : }); describe('GET /logout', () => { beforeAll((done) => { authenticatedUser .post('/login') .send(userCredentials) .expect(302, done); }); it('should go back to login', (done) => { authenticatedUser .get('/logout') .set('Accept', 'text/html') .expect(302) .expect('Location', '/', done); }); }); |

シェルスクリプトマガジン Vol.58で掲載しているコードをまとめています。
プレゼント&アンケートページはこちら!
シェルスクリプトマガジン Vol.58は以下のリンク先でご購入できます。![]()
004 レポート Linuxカーネル5.0
005 レポート Microsoft社のProject Mu
006 NEWS FLASH
008 特集1 オープンソースのデータベース管理システム MySQL入門/梶山隆輔
030 特集2 ラズパイでBASIC/土肥毅大、岡優樹、納富志津
040 特別企画 kintoneで作る交通費申請システム/佐山ウィリアム 明裕、ぺそ、檀原由香子
050 ラズパイセンサーボードで学ぶ 電子回路の制御/米田聡 コード掲載
055 姐のNOGYO
056 中小企業手作りIT化奮戦記/菅雄一
062 円滑コミュニケーションが世界を救う!/濱口誠一
064 「Visual Studio Code」を便利に使う/山本美穂
068 新年号/桑原滝弥・イケヤシロウ
070 バーティカルバーの極意/飯尾淳
076 法林浩之のFIGHTING TALKS/法林浩之
078 漢のUNIX/後藤大地
084 人間とコンピュータの可能性/大岩元
086 機械学習のココロ/石井一夫 コード掲載
092 Node.js/Expressで楽々Webアプリ開発/しょっさん コード掲載
100 香川大学SLPからお届け!/竹原一駿 コード掲載
106 UNIXの歴史を振り返る/古寺雅弘
114 ユニケージ新コードレビュー/技術研究員
120 Techパズル/gori.sh
122 コラム「仕事座右の銘」/シェル魔人
著者:石井 一夫
今回は、機械学習による2値分類の方法を紹介します。映画評論のテキストを好評価な内容か悪い評価の 内容かに分類するという例題のTensorFlowのチュートリアルマニュアルに沿って、2値分類のモデリングの定番である「ロジスティック回帰分析」について解説します。
シェルスクリプトマガジン Vol.58は以下のリンク先でご購入できます。![]()
|
1 2 3 4 5 |
import tensorflow as tf from tensorflow import keras import numpy as np imdb = keras.datasets.imdb (train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000) |
|
1 2 |
print(train_labels[0]) print(train_data[0]) |
|
1 2 3 4 5 6 7 8 9 10 11 |
# 整数の索引に単語を割り当てる辞書を検索する関数 word_index = imdb.get_word_index() # 最初の索引で変換 word_index = {k:(v+3) for k,v in word_index.items()} word_index["<PAD>"] = 0 word_index["<START>"] = 1 word_index["<UNK>"] = 2 # 不明 word_index["<UNUSED>"] = 3 reverse_word_index = dict([(value, key) for (key, value) in word_index.items()]) def decode_review(text): return ' '.join([reverse_word_index.get(i, '?') for i in text]) |
|
1 2 3 4 5 6 |
train_data = keras.preprocessing.sequence.pad_sequences( train_data, value=word_index["<PAD>"], padding='post', maxlen=256) test_data = keras.preprocessing.sequence.pad_sequences( test_data, value=word_index["<PAD>"], padding='post', maxlen=256) |
|
1 2 3 4 5 6 7 |
# 入力データの指定形式は、映画評論に用いられた単語数(10,000 単語)をとる vocab_size = 10000 model = keras.Sequential() model.add(keras.layers.Embedding(vocab_size, 16)) # 入力層 model.add(keras.layers.GlobalAveragePooling1D()) # 中間層I model.add(keras.layers.Dense(16, activation=tf.nn.relu)) # 中間層II model.add(keras.layers.Dense(1, activation=tf.nn.sigmoid)) # 出力層 |
|
1 2 3 |
model.compile(optimizer=tf.train.AdamOptimizer(), loss='binary_crossentropy', metrics=['accuracy']) |
|
1 2 3 4 |
x_val = train_data[:10000] partial_x_train = train_data[10000:] y_val = train_labels[:10000] partial_y_train = train_labels[10000:] |
|
1 2 3 4 5 6 |
history = model.fit(partial_x_train, partial_y_train, epochs=40, batch_size=512, validation_data=(x_val, y_val), verbose=1) |
|
1 2 |
results = model.evaluate(test_data, test_labels) print(results) |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
%matplotlib inline import matplotlib.pyplot as plt acc = history.history['acc'] val_acc = history.history['val_acc'] loss = history.history['loss'] val_loss = history.history['val_loss'] epochs = range(1, len(acc) + 1) # 青点グラフ plt.plot(epochs, loss, 'bo', label='Training loss') # 青線グラフ plt.plot(epochs, val_loss, 'b', label='Validation loss') plt.title('Training and validation loss') plt.xlabel('Epochs') plt.ylabel('Loss') plt.legend() plt.show() |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
plt.clf() # 図を初期化 acc_values = history_dict['acc'] val_acc_values = history_dict['val_acc'] plt.plot(epochs, acc, 'bo', label='Training acc') plt.plot(epochs, val_acc, 'b', label='Validation acc') plt.title('Training and validation accuracy') plt.xlabel('Epochs') plt.ylabel('Accuracy') plt.legend() plt.show() |
著者:米田 聡
シェルスクリプトマガジンでは、小型コンピュータボード「Raspberry Pi」(ラズパイ)向けのセンサー搭載拡張ボード「ラズパイセンサーボード」を制作しました。第5回では、このボードを使った電子回路制御を取り上げます。具体的には、明るさ・近隣センサーに近づく物体の検出です。
シェルスクリプトマガジン Vol.58は以下のリンク先でご購入できます。![]()
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
import smbus import time import RPi.GPIO as GPIO from threading import BoundedSemaphore class VCNL4020(): _ALS_OD = 0b00010000 # オンデマンド明るさ計測スタート _PROX_OD = 0b00001000 # オンデマンド近接計測スタート _ALS_EN = 0b00000100 # 明るさ繰り返し計測有効 _PROX_EN = 0b00000010 # 近接繰り返し計測有効 _SELFTIMED_EN = 0b00000001 # 内蔵タイマー有効 _CONT_CONV = 0b10000000 # Continue Conversion有効 _AMBIENT_RATE = 0b00010000 # 明るさの計測レート(default:2sample/s) _AUTO_OFFSET = 0b00001000 # 自動オフセットモード有効 _AVERAGING = 0b00000101 # 平均化(default:32conv) _COMMAND_REG = 0x80 # コマンドレジスタ _PID_REG = 0x81 # プロダクトIDレジスタ _PROX_RATE_REG = 0x82 # 近接測定レートジスタ _IR_CURRENT_REG = 0x83 # 近接測定用赤外線LED電流設定レジスタ(default=20mA) _AMBIENT_PARAM_REG = 0x84 # 明るさセンサーパラメータレジスタ _AMBIENT_MSB = 0x85 # 明るさ上位バイト _AMBIENT_LSB = 0x86 # 明るさ下位バイト _PROX_MSB = 0x87 # 近接上位バイト _PROX_LSB = 0x88 # 近接下位バイト _INT_CONTROL_REG = 0x89 # 割り込み制御レジスタ _LOW_TH_MSB = 0x8A # Lowしきい値(MSB) _LOW_TH_LSB = 0x8B # Lowしきい値(LSB) _HIGH_TH_MSB = 0x8C # Highしきい値(MSB) _HIGH_TH_LSB = 0x8D # Highしきい値(LSB) _INT_STATUS_REG = 0x8E # 割り込みステータス _INT_NO = 0x06 # int = GPIO6 # コールバック __callbackfunc = None def __init__(self, i2c_addr = 0x13, busno = 1): self.addr = i2c_addr self.i2c = smbus.SMBus(busno) self._write_reg(self._COMMAND_REG, self._ALS_OD |\ self._PROX_OD |\ self._ALS_EN |\ self._PROX_EN |\ self._SELFTIMED_EN ) self._write_reg(self._IR_CURRENT_REG, 2 ) # 20mA self._write_reg(self._AMBIENT_PARAM_REG, self._CONT_CONV |\ self._AMBIENT_RATE |\ self._AUTO_OFFSET |\ self._AVERAGING ) self.semaphore = BoundedSemaphore() # GPIO設定 GPIO.setmode(GPIO.BCM) GPIO.setup(self._INT_NO, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.add_event_detect(self._INT_NO, GPIO.FALLING, callback=self.__interruptfunc) time.sleep(0.6) # 初回測定まで待つ def _write_reg(self, reg, value): self.i2c.write_byte_data(self.addr, reg, value) def _read_reg(self, reg): return self.i2c.read_byte_data(self.addr, reg) # 高値用レジスタ設定 def set_high_threshold(self, value): self.semaphore.acquire() h = (value & 0xFF00) >> 8 l = value & 0x00FF self._write_reg(self._HIGH_TH_MSB, h) self._write_reg(self._HIGH_TH_LSB, l) self.semaphore.release() # 低値用レジスタ設定 def set_low_threshold(self, value): self.semaphore.acquire() h = (value & 0xFF00) >> 8 l = value & 0x00FF self._write_reg(self._LOW_TH_MSB, h) self._write_reg(self._LOW_TH_LSB, l) self.semaphore.release() # 割り込み有効化 def enable_interrupt(self, callbackfunc=None, prox=True, samples=1): self.semaphore.acquire() self.__callbackfunc = callbackfunc value = self._read_reg(self._INT_CONTROL_REG) if callbackfunc is not None: if prox: value |= 0b00000010 else: value |= 0b00000011 else: value &= 0b11111100 # samples samples &= 0b00000111 samples = samples << 5 value &= 0b00011111 value |= samples self._write_reg( self._INT_CONTROL_REG, value) self.semaphore.release() # 割り込み関数 def __interruptfunc(self, ch): if ch != self._INT_NO: return if self.__callbackfunc is not None: self.__callbackfunc(self.luminance, self.proximity) @property def luminance(self): self.semaphore.acquire() d = self.i2c.read_i2c_block_data(self.addr, self._AMBIENT_MSB, 2) self.semaphore.release() return (d[0] * 256 + d[1]) / 4 @property def proximity(self): self.semaphore.acquire() d = self.i2c.read_i2c_block_data(self.addr, self._PROX_MSB, 2) self.semaphore.release() return (d[0] * 256 + d[1]) |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#!/usr/bin/env python3 import time from VCNL4020 import VCNL4020 sensor = VCNL4020() # コールバック関数 def callback(lux, prox): print('センサーに何かが接近しています') print('現在の明るさ:'+str(lux) ) print('近接センサー:'+str(prox) ) # 割り込み再設定 sensor.enable_interrupt(callback) # しきい値高を設定 sensor.set_high_threshold(2500) # しきい値低を設定 sensor.set_low_threshold(0) # 割り込み有効化 sensor.enable_interrupt(callback) try: while True: time.sleep(1) except KeyboardInterrupt: term = True |
社内サーバーなどの運用管理で見落としがちなのが「電源」に対する保護です。安定した電圧や電流を常に供給しなければ、サーバーが不安定になって停止や故障しかねません。
安定した電源を確保するための重要なハードウエアが「無停電電源装置」(UPS:Uninterruptible Power Supply)です。UPSを用いると、電圧値や電流値が低下したときに補えたり、電源断が発生したときに自動シャットダウンでサーバーを安全に停止させたり、雷などによる異常な高電圧を防いだりできます(図1)。

図1 電源を保護する無停電電源装置(UPS)
今回は、このUPSをサーバーにつないでサーバーの電源を制御する方法を紹介します。管理するサーバーには、Ubuntu Server 16.04 LTSがインストールされているものとします。
コンピュータを守るセキュリティの仕組みとして「強制アクセス制御」(Mandatory Access Control)があります。強制アクセス制御では、「パーミッション」などで定められているOSの標準的な制限ではなく、ユーザーや実行プログラムに対してより厳しい制限を設定できます。
例えば、あるプログラムに対して、参照しかしないファイルへの書き込み、利用するはずがないファイルへのアクセス、関係がない別のプログラムの呼び出しを禁止できます(図1)。

図1 強制アクセス制御による制限
このような強制アクセス制御をLinuxで実現できるソフトウエアには、いくつかあります。Ubuntu Serverでは「AppArmor」というソフトウエアが標準で導入されていて、有効になっています。このAppArmorは、Linux OSの核となる「カーネル」のセキュリティフレームワーク「Linux Security Modules」(LSM)を使って実装しています。
著者:降籏洋行、西川公一朗
Pythonには、豊富な機能を備える標準ライブラリが用意されています。しかし機能豊富すぎるが故に、初心者にとっては「この標準ライブラリモジュールは、実際の開発において、こういう時に役に立つ」という情報を探しにくかったり、リファレンスの説明だけだと使用方法をイメージしづらかったりします。本特集では、知っておくと役立つ便利な標準ライブラリモジュール9個を厳選して、その活用法を紹介します。
シェルスクリプトマガジン Vol.57は以下のリンク先でご購入できます。
|
1 2 3 4 5 |
from pathlib import Path for p in Path('.').rglob('*.txt'): # パターンマッチ print(f'ディレクトリ: {p.parent.resolve()}') # ディレクトリの絶対パス print(f'ファイル: {p.name}') # ファイル名 print(p.read_text()) # ファイルの内容 |
|
1 2 3 4 |
def is_sushi(name): """お寿司かどうかチェックして真偽値を返す""" sushi = ['マグロ', 'イクラ', 'エビ'] return name in sushi |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def is_sushi(name): """お寿司かどうかチェックして真偽値を返す >>> is_sushi('マグロ') True >>> is_sushi('タマゴ') # わざとdoctest実行が失敗するように記述 True # (1) """ sushi = ['マグロ', 'イクラ', 'エビ'] return name in sushi if __name__ == "__main__": from doctest import testmod testmod() |
|
1 2 3 4 5 6 7 8 9 10 |
def is_sushi(name): """お寿司かどうかチェックして真偽値を返す >>> is_sushi(x) Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'x' is not defined """ sushi = ['マグロ', 'イクラ', 'エビ'] return name in sushi |
|
1 2 3 4 5 6 7 8 9 10 |
def is_sushi(name): """お寿司かどうかチェックして真偽値を返す >>> is_sushi(x) Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'x' is not defined """ sushi = ['マグロ', 'イクラ', 'エビ'] return name in sushi |
|
1 2 3 4 5 6 7 8 9 10 |
def is_sushi(name): """お寿司かどうかチェックして真偽値を返す >>> is_sushi(x) Traceback (most recent call last): ... NameError: name 'x' is not defined """ sushi = ['マグロ', 'イクラ', 'エビ'] return name in sushi |
|
1 2 3 4 5 6 7 8 9 10 11 |
import doctest import unittest import menu suite = unittest.TestSuite() # unittestのテストスイートに追加する suite.addTest(doctest.DocTestSuite(menu)) runner = unittest.TextTestRunner(verbosity=2) runner.run(suite) |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# kaikei.py import sys # ネタと価格 menu = { 'マグロ': 200, 'はまち': 100, 'サーモン': 150, } def get_amount(*orders): amount = 0 # 合計金額 ordered = set() # 注文済みのネタ for order in orders: if order in ordered: # 注文済みのネタは割引 amount += menu[order] * 0.9 else: amount += menu[order] # 注文済みにする ordered = {order} return int(amount) if __name__ == '__main__': orders = sys.argv[1:] amount = get_amount(*orders) print(f'合計金額: {amount}円') |
|
1 2 3 4 5 6 7 8 9 10 |
ORDER_DICT = { "マグロの鉄火丼": 1300, "ウニとイクラのパスタ": 1500, "ドリンク": 550, } if __name__ == "__main__": for name, price in ORDER_DICT.items(): # f-stringを利用し、25文字幅で左揃えする print(f"{name:<25}:{price}") |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
from unicodedata import east_asian_width ORDER_DICT = { "マグロの鉄火丼": 1300, "ウニとイクラのパスタ": 1500, "ドリンク": 550, } def get_text(name, max_count): count = 0 for char in name: if east_asian_width(char) in ['F', 'W', 'A']: count += 2 else: count += 1 # 文字最大幅から指定した文字列の幅を引いた分だけ半角スペースを付与 text = f'{name + (" " * (max_count - count))}' return text if __name__ == "__main__": for name, price in ORDER_DICT.items(): text = get_text(name, 25) print(f"{text}:{price}") |
|
1 2 3 4 |
from http.server import HTTPServer, SimpleHTTPRequestHandler httpd = HTTPServer(("localhost", 8888), SimpleHTTPRequestHandler) httpd.serve_forever() |
|
1 2 3 4 5 6 |
from http.server import HTTPServer, SimpleHTTPRequestHandler def run(): """ エントリポイントとして指定する関数 """ httpd = HTTPServer(("localhost", 8888), SimpleHTTPRequestHandler) httpd.serve_forever() |
著者:米田聡、麻生二郎
シェルスクリプトマガジンでは、小型コンピュータボード「Raspberry Pi」のオリジナル拡張ボード第2弾として「ラズパイセンサーボード」を製作しました。サンプルプログラムで、搭載されている湿温度・気圧センサー、
ガスセンサー、照度センサーの使い方を紹介します。
シェルスクリプトマガジン Vol.57は以下のリンク先でご購入できます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
1 #!/usr/bin/env python3 2 # 3 # apt install python3-pip 4 # sudo pip3 install RPi.BME280 5 # 6 7 import time 8 import smbus2 9 import bme280 10 11 from EbOled import EbOled 12 13 BME280_ADDR = 0x76 14 BUS_NO = 1 15 16 # BME280 17 i2c = smbus2.SMBus(BUS_NO) 18 bme280.load_calibration_params(i2c, BME280_ADDR) 19 20 # OLEDパネル 21 oled = EbOled() 22 oled.begin() 23 oled.clear() 24 oled.display() 25 26 try: 27 while True: 28 data = bme280.sample(i2c, BME280_ADDR) 29 oled.drawString('気温 :' + str(round(data.temperature,1)) + '℃', 0) 30 oled.drawString('湿度 :' + str(round(data.humidity,1)) + '%', 1) 31 oled.drawString('気圧 :' + str(round(data.pressure,1)) + 'hPa', 2) 32 oled.display() 33 34 time.sleep(1) 35 except KeyboardInterrupt: 36 pass |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
1 import time 2 import Adafruit_GPIO.SPI as SPI 3 import Adafruit_SSD1306 4 5 from PIL import Image 6 from PIL import ImageDraw 7 from PIL import ImageFont 8 9 10 class EbOled(Adafruit_SSD1306.SSD1306_128_64): 11 12 WIDTH = 128 13 HEIGHT = 64 14 15 __RST = 24 16 __DC = 23 17 SPI_PORT = 0 18 SPI_DEVICE = 0 19 20 DEFAULT_FONT = '/usr/share/fonts/truetype/fonts-japanese-gothic.ttf' 21 FONT_SIZE = 14 22 _LINE_HEIGHT = 16 23 24 def __init__(self): 25 self.__spi = SPI.SpiDev(self.SPI_PORT, self.SPI_DEVICE, max_speed_hz=8000000) 26 super().__init__(rst=self.__RST, dc=self.__DC, spi=self.__spi) 27 self._image = Image.new('1', (self.WIDTH, self.HEIGHT) ,0) 28 self._draw = ImageDraw.Draw(self._image) 29 self._font = ImageFont.truetype(self.DEFAULT_FONT, self.FONT_SIZE, encoding='unic') 30 31 def image(self, image): 32 self._image = image 33 super().image(self._image) 34 35 def drawString(self, str, line=0): 36 self._draw.rectangle((0, line*self._LINE_HEIGHT, self.WIDTH,line*self._LINE_HEIGHT+self._LINE_HEIGHT), fill=(0)) 37 self._draw.text((0, line*self._LINE_HEIGHT), str, font=self._font, fill=1) 38 self.image(self._image) |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
1 #!/usr/bin/env python3 2 import time 3 import RPi.GPIO as GPIO 4 5 from EbOled import EbOled 6 from TP401T import TP401T 7 8 BUZZER = 18 9 10 sensor = TP401T() 11 oled = EbOled() 12 oled.begin() 13 oled.clear() 14 oled.display() 15 16 # BUZZER 17 GPIO.setmode(GPIO.BCM) 18 GPIO.setup(BUZZER, GPIO.OUT) 19 bz = GPIO.PWM(BUZZER, 1000) 20 bz.stop() 21 22 try: 23 sensor.start() 24 oled.drawString('待機中です') 25 oled.display() 26 while sensor.state == TP401T.WAITING: # 測定開始待ち 27 time.sleep(3) 28 29 while True: 30 if sensor.state == TP401T.NORMAL: 31 oled.drawString('空気は正常です') 32 else: 33 oled.drawString('汚染されています!!') 34 35 if sensor.state == TP401T.ALERT: 36 bz.start(50) 37 else: 38 bz.stop() 39 40 oled.display() 41 time.sleep(3) 42 43 except KeyboardInterrupt: 44 sensor.stop() # センサー停止 45 46 GPIO.cleanup() |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
1 import threading 2 import time 3 from MCP3424 import MCP3424 4 5 class TP401T(MCP3424): 6 7 WAITING = -1 # 測定開始待ち 8 NORMAL = 0 # 空気に問題はない 9 ALERT = 1 # 汚染されている 10 WARNING = 2 # 汚染されているが減少中 11 12 term = False 13 __current_state = -1 14 15 def __init__(self, ch = 0): 16 super().__init__() 17 self.tp401_ch = ch 18 self.term = False 19 self.prev_value = self.getVoltage(self.tp401_ch) 20 self.normal_value = self.prev_value 21 22 def start(self): 23 self.worker = threading.Thread(target=self.__measure) 24 self.term = False 25 self.worker.start() 26 27 def stop(self): 28 self.term = True 29 self.worker.join() 30 31 def __sleep(self, sec): 32 for i in range(sec * 100): 33 if self.term == True: 34 return False 35 time.sleep(1/100) 36 37 return True 38 39 def __measure(self): 40 self.__current_state = self.WAITING 41 42 sum = 0.0 43 for i in range(10): 44 sum += self.getVoltage(self.tp401_ch) 45 if self.__sleep(3) == False: 46 return 47 # 30秒間の平均値を平時の値として採用する 48 self.normal_value = sum / 10 49 self.prev_value = self.normal_value 50 self.__current_state = self.NORMAL 51 52 while self.__sleep(3): 53 value = self.getVoltage(self.tp401_ch) 54 if value < self.normal_value * 1.5: 55 self.__current_state = self.NORMAL 56 else: 57 if (self.prev_value - value) < 0: # 汚染が増加中 58 self.__current_state = self.ALERT 59 else: 60 self.__current_state = self.WARNING 61 62 self.prev_value = value 63 64 @property 65 def state(self): 66 return self.__current_state 67 68 def __del__(self): 69 self.term = True |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
1 import os 2 3 class MCP3424(): 4 5 SYSFS_PATH = '/sys/bus/i2c/devices/i2c-1/1-0068' 6 SYSFS_IIO = '/iio:device0/' 7 __enable = False 8 9 def __init__(self): 10 if not os.path.exists(self.SYSFS_PATH): 11 os.system('sudo /bin/bash -c "echo \'mcp3424 0x68\' > /sys/bus/i2c/devices/i2c-1/new_device"') 12 13 if not os.path.exists(self.SYSFS_PATH): 14 raise Exception('sysfs error') 15 16 self.__enable = True 17 18 def getVoltage(self, ch): 19 if not self.__enable: 20 return 0 21 22 raw = 0.0 23 scale = 0.0 24 25 with open(self.SYSFS_PATH+self.SYSFS_IIO+'in_voltage{}_raw'.format(ch), "r") as f: 26 raw = float(f.read()) 27 with open(self.SYSFS_PATH+self.SYSFS_IIO+'in_voltage{}_scale'.format(ch), "r") as f: 28 scale = float(f.read()) 29 30 return (raw * scale) 31 32 @property 33 def ch0(self): 34 return self.getVoltage(0) 35 36 @property 37 def ch1(self): 38 return self.getVoltage(1) 39 40 @property 41 def ch2(self): 42 return self.getVoltage(2) 43 44 @property 45 def ch3(self): 46 return self.getVoltage(3) |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
1 #!/usr/bin/env python3 2 import time 3 import RPi.GPIO as GPIO 4 import threading 5 6 from EbOled import EbOled 7 from VCNL4020 import VCNL4020 8 9 BUZZER = 18 10 11 term = False 12 sensor = VCNL4020() 13 oled = EbOled() 14 oled.begin() 15 oled.clear() 16 oled.display() 17 18 def prox(): 19 global term 20 21 GPIO.setmode(GPIO.BCM) 22 GPIO.setup(BUZZER, GPIO.OUT) 23 bz = GPIO.PWM(BUZZER, 1000) 24 25 while term == False: 26 if sensor.proximity > 4000: 27 bz.start(50) 28 else: 29 bz.stop() 30 time.sleep(0.1) 31 32 GPIO.cleanup(BUZZER) 33 34 35 t = threading.Thread(target=prox) 36 t.start() 37 38 try: 39 while True: 40 oled.drawString('明るさ: ' + str(sensor.luminance) +' lux') 41 oled.display() 42 time.sleep(0.2) 43 except KeyboardInterrupt: 44 term = True |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
1 # https://www.vishay.com/docs/83476/vcnl4020.pdf 2 3 import smbus 4 import time 5 from threading import BoundedSemaphore 6 7 class VCNL4020(): 8 9 _ALS_OD = 0b00010000 # オンデマンド明るさ計測スタート 10 _PROX_OD = 0b00001000 # オンデマンド近接計測スタート 11 _ALS_EN = 0b00000100 # 明るさ繰り返し計測有効 12 _PROX_EN = 0b00000010 # 近接繰り返し計測有効 13 _SELFTIMED_EN = 0b00000001 # 内蔵タイマー有効 14 15 _CONT_CONV = 0b10000000 # Continue Conversion有効 16 _AMBIENT_RATE = 0b00010000 # 明るさの計測レート(default:2sample/s) 17 _AUTO_OFFSET = 0b00001000 # 自動オフセットモード有効 18 _AVERAGING = 0b00000101 # 平均化(default:32conv) 19 20 _COMMAND_REG = 0x80 # コマンドレジスタ 21 _PID_REG = 0x81 # プロダクトIDレジスタ 22 _PROX_RATE_REG = 0x82 # 近接測定レートジスタ 23 _IR_CURRENT_REG = 0x83 # 近接測定用赤外線LED電流設定レジスタ(default=20mA) 24 _AMBIENT_PARAM_REG = 0x84 # 明るさセンサーパラメータレジスタ 25 26 _AMBIENT_MSB = 0x85 # 明るさ上位バイト 27 _AMBIENT_LSB = 0x86 # 明るさ下位バイト 28 29 _PROX_MSB = 0x87 # 近接上位バイト 30 _PROX_LSB = 0x88 # 近接下位バイト 31 32 def __init__(self, i2c_addr = 0x13, busno = 1): 33 self.addr = i2c_addr 34 self.i2c = smbus.SMBus(busno) 35 36 self._write_reg(self._COMMAND_REG, self._ALS_OD |\ 37 self._PROX_OD |\ 38 self._ALS_EN |\ 39 self._PROX_EN |\ 40 self._SELFTIMED_EN ) 41 42 self._write_reg(self._IR_CURRENT_REG, 2 ) # 20mA 43 44 self._write_reg(self._AMBIENT_PARAM_REG, self._CONT_CONV |\ 45 self._AMBIENT_RATE |\ 46 self._AUTO_OFFSET |\ 47 self._AVERAGING ) 48 self.semaphore = BoundedSemaphore() 49 time.sleep(0.6) # 初回測定まで待つ 50 51 def _write_reg(self, reg, value): 52 self.i2c.write_byte_data(self.addr, reg, value) 53 54 @property 55 def luminance(self): 56 self.semaphore.acquire() 57 d = self.i2c.read_i2c_block_data(self.addr, self._AMBIENT_MSB, 2) 58 self.semaphore.release() 59 return (d[0] * 256 + d[1]) 60 61 @property 62 def proximity(self): 63 self.semaphore.acquire() 64 d = self.i2c.read_i2c_block_data(self.addr, self._PROX_MSB, 2) 65 self.semaphore.release() 66 return (d[0] * 256 + d[1]) |
シェルスクリプトマガジンでは、小型CPUボード「Raspberry Pi」のオリジナル拡張ボード第2弾として「ラズパイセンサーボード」を制作しました。このセンサーボードには湿温度・気圧センサー、ガスセンサー、明るさセンサーを搭載しています。特集1では、サンプルプログラムを基にしてラズパイセンサーボードの使い方を紹介します。
特集2では、プログラミング言語「Python」の標準ライブラリに含まれる便利で役立つモジュールを9個紹介します。機能が豊富なため、初心者には使い方が分からないものもあります。実際の活用方法を含めながら、分かりやすく解説します。
特集3では、注目のデザインアプリ「Adobe XD CC」を紹介します。Adobe XD CCは、UI/UXデザイン、プロトタイプ、共同作業ツールとして動作します。一部の制限はありますが、無料でも使えます。Adobe XD CCで次世代のデザインアプリを体験してみてください。
特別企画では、ユーザー企業向けにセキュリティ人材不足を解消する方法を解説します。どのような人材が必要なのか、どうすれば人材を育てられるか、アウトソースをどう活用すべきかなど、有益な情報を紹介します。
このほか、「1960年代から現在まで UNIXの歴史を振り返る」「Extensionで機能追加 オープンソースエディタ「Visual Studio Code」を便利に使う」という二つの新連載が始まりました。
今月も読み応え十分のシェルスクリプトマガジン Vol.57。お見逃しなく!
※読者アンケートはこちら
著者:後藤大地
これまで、JSONデータを読み込んで処理する方法を取り上げてきた。JSONデータをパースしながら処理する方法や、メモリー上に展開したJSONデータを、指定したフォーマットで出力する方法を解説した。今回も引き続き同じ「Jansson」ライブラリを使うが、JSONデータを読み込んで処理するのではなく、生成する方法を紹介する。
シェルスクリプトマガジン Vol.57は以下のリンク先でご購入できます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <jansson.h> int main(int argc, char *argv[]) { // JSONデータを生成 json_t *root = json_pack("{}"); // JSONデータをエンコードして標準出力へ出力 json_dump_file(root, "/dev/stdout", 0); // JSONメモリー解放 json_decref(root); return 0; } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <jansson.h> int main(int argc, char *argv[]) { // JSONデータを生成 json_t *root = json_pack("{s:i, s:i}", "鍵1", 510, "鍵2", 0); // JSONデータをエンコードして標準出力へ出力 json_dump_file(root, "/dev/stdout", 0); // JSONメモリー解放 json_decref(root); return 0; } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "Image": { "幅": 800, "高さ": 600, "タイトル": "View from 15th Floor", "サムネイル": { "Url": "http://www.example.com/image/481989943", "高さ": 125, "幅": 100 }, "アニメーション" : false, "IDs": [116, 943, 234, 38793] } } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <jansson.h> int main(int argc, char *argv[]) { // JSONデータを生成 json_t *root = json_pack( "{s:{s:i,s:i,s:s,s:{s:s,s:i,s:i},s:b,s:[i,i,i,i]}}", "Image", "幅", 800, "高さ", 600, "タイトル", "View from 15th Floor", "サムネイル", "Url", "http://www.example.com/image/481989943", "高さ", 125, "幅", 100, "アニメーション", 0, "IDs", 116, 943, 234, 38793 ); // JSONデータをエンコードして標準出力へ出力 json_dump_file(root, "/dev/stdout", JSON_INDENT(2)); // JSONメモリー解放 json_decref(root); return 0; } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
[ { "精度": "zip", "緯度": 37.7668, "経度": -122.3959, "住所": "", "都市": "SAN FRANCISCO", "州": "CA", "郵便番号": "94107", "国": "US" }, { "精度": "zip", "緯度": 37.371991, "経度": -122.026020, "住所": "", "都市": "SUNNYVALE", "州": "CA", "郵便番号": "94085", "国": "US" } ] |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
#include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <jansson.h> int main(int argc, char *argv[]) { // JSONデータを生成 json_t *root = json_pack( "[{sssfsfssssssssss}" "{sssfsfssssssssss}]", "精度", "zip", "緯度", 37.7668, "経度", -122.3959, "住所", "", "都市", "SAN FRANCISCO", "州", "CA", "郵便番号", "94107", "国", "US", "精度", "zip", "緯度", 37.371991, "経度", -122.026020, "住所", "", "都市", "SUNNYVALE", "州", "CA", "郵便番号", "94085", "国", "US" ); // JSONデータをエンコードして標準出力へ出力 json_dump_file(root, "/dev/stdout", JSON_INDENT(2)| JSON_REAL_PRECISION(10)); // JSONメモリー解放 json_decref(root); return 0; } |
|
1 2 3 4 5 6 7 |
zip 37.7668 -122.3959 _ SAN_FRANCISCO CA 94107 US zip 37.371991 -122.02602 _ SUNNYVALE CA 94085 US zip 32.8209296 -97.0117199 _ DALLAS TX 75001 US zip 32.8010236 -97.4294011 _ FORT_WORTH TX 76006 US zip 47.6131229 -122.4121037 _ SEATTLE WA 98101 US zip 41.3141183 -88.2727778 _ CHICAGO IL 60007 US zip 40.6976701 -74.259856 _ NEW_YORK NY 10001 US |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
#include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <sysexits.h> #include <jansson.h> #define ITEM_MAX_LEN 100 // 地図データ構造体 struct map_info { char precise[ITEM_MAX_LEN]; double latitude; double longitude; char address[ITEM_MAX_LEN]; char city[ITEM_MAX_LEN]; char state[ITEM_MAX_LEN]; char zip[ITEM_MAX_LEN]; char country[ITEM_MAX_LEN]; }; int main(int argc, char *argv[]) { FILE *fp; struct map_info map; json_t *root; // データファイルオープン fp = fopen(argv[1], "r"); if (NULL == fp) return EX_NOINPUT; // データを8項目ごとに読み取って順次処理 while (EOF != fscanf(fp, "%s %lf %lf %s %s %s %s %s", map.precise, &map.latitude, &map.longitude, map.address, map.city, map.state, map.zip, map.country)) { // JSONデータを生成 root = json_pack( "{sssfsfssssssssss}", "精度", map.precise, "緯度", map.latitude, "経度", map.longitude, "住所", map.address, "都市", map.city, "州", map.state, "郵便番号", map.zip, "国", map.country ); // JSONデータをエンコードして標準出力へ json_dump_file(root, "/dev/stdout", JSON_INDENT(2)| JSON_REAL_PRECISION(7)); putchar('\n'); // JSONメモリー解放 json_decref(root); } // データファイルクローズ fclose(fp); return 0; } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
CMD= make_json1 make_json2 \ make_json3 make_json4 \ make_json5 CC= cc RM= rm -f CFLAGS+= -I/usr/local/include \ -L/usr/local/lib \ -ljansson build: ${CMD} .for i in ${CMD} ${i}: ${CC} -o ${i} ${i}.c ${CFLAGS} .endfor clean: ${RM} ${CMD} |

シェルスクリプトマガジン Vol.57で掲載しているコードをまとめています。
プレゼント&アンケートページはこちら!
シェルスクリプトマガジン Vol.57は以下のリンク先でご購入できます。
![]()
004 レポート LPI-Japanインタビュ
005 レポート 第34回のTechLION開催
006 NEWS FLASH
008 特集1 ラズパイでセンサーを使う/米田聡、麻生二郎 コード掲載
020 特集2 Python標準モジュール9選/降籏洋行、西川公一朗 コード掲載
038 特集3 Adobe XD CCに触れてみよう/湯口りさ、北村崇
052 特別企画 セキュリティ人材不足を解決する/園田道夫
058 c人間とコンピュータの可能性/大岩元
060 UNIXの歴史を振り返る/古寺雅弘
066 法林浩之のFIGHTING TALKS/法林浩之
068 スズラボ通信/すずきひろのぶ コード掲載
073 姐のNOGYO
074 バーティカルバーの極意/飯尾淳 コード掲載
080 RESEARCHES FOR FUTURE/飯尾淳
082 中小企業手作りIT化奮戦記/菅雄一
088 円滑コミュニケーションが世界を救う!/濱口誠一
090 「Visual Studio Code」を便利に使う/山本美穂 コード掲載
094 香川大学SLPからお届け!/飯國隆志 コード掲載
098 統合/桑原滝弥・イケヤシロウ
100 アジャイル開発 Let’s Practice!/熊野憲辰
104 Node.js/Expressで楽々Webアプリ開発/しょっさん コード掲載
112 漢のUNIX/後藤大地 コード掲載
122 ユニケージ新コードレビュー/村本禎実 コード掲載
128 Techパズル/gori.sh
130 コラム「世の中の流れとは別に」/シェル魔人
著者:山本美穂
米Microsoft 社発のオープンソースエディタ「Visual Studio Code」には「Extension」と呼ばれる機能拡張用のソフトウエアが多数提供されています。本連載では便利なExtension の使い方を中心に紹介します。第1回は、PaaS「Azure App Service」と連携できるExtension です。
シェルスクリプトマガジン Vol.57は以下のリンク先でご購入できます。
|
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "name": "app-service-hello-world", "description": "Simple Hello World Node.js sample for Azure App Service", "version": "0.0.1", "private": true, "license": "MIT", "author": "Microsoft", "scripts": { "start": "node index.js" start_azure_debug":"node --inspect=0.0.0.0:$APPSVC_TUNNEL_PORT index.js } } |
著者:飯尾淳
バーティカルバー(垂直棒)の集合体「バーコード」の読み取りに挑戦します。すでに、スマートフォンのアプリなどでは、カメラでバーコードを認識する機能が当たり前になっています。この機能を自前のアプリに組み込むことができれば、いろいろな応用が考えられそうです。そのためにも、まずは、バーコード写真を読み込んで認識する基本的な機能を実現してみましょう。
シェルスクリプトマガジン Vol.57は以下のリンク先でご購入できます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title>JOB</title> 6 </head> 7 <body> 8 <div id="container"> 9 <input id="fileSelecter" accept="image/*" type="file"><br /> 10 <canvas width="320" height="240" id="picture"></canvas> 11 <p id="textLabel"></p> 12 </div> 13 <script type="text/javascript" src="JOB.js"></script> 14 <script type="text/javascript" src="mySettings.js"></script> 15 </body> 16 </html> |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
1 var targetFile = document.getElementById("fileSelecter"); 2 var bcImage = document.createElement("img"); 3 var bcLabel = document.getElementById("textLabel"); 4 var canvas = document.getElementById("picture"); 5 var context = canvas.getContext("2d"); 6 7 targetFile.onchange = function (event) { 8 var files = event.target.files; 9 if (files && files.length > 0) { 10 var file = files[0]; 11 var URL = window.URL || window.webkitURL; 12 bcImage.onload = function(event) { 13 bcLabel.innerHTML = ""; 14 JOB.DecodeImage(bcImage); 15 URL.revokeObjectURL(bcImage.src); 16 }; 17 bcImage.src = URL.createObjectURL(file); 18 } 19 }; 20 21 JOB.Init(); 22 JOB.SetImageCallback(function(result) { 23 if (result.length > 0) { 24 var tempArray = []; 25 for (var i = 0; i < result.length; i++) { 26 tempArray.push(result[i].Format+" : "+result[i].Value); 27 } 28 bcLabel.innerHTML=tempArray.join("<br />"); 29 } else { 30 if (result.length === 0) { 31 bcLabel.innerHTML = "Decoding failed."; 32 } 33 } 34 }); 35 JOB.PostOrientation = true; 36 JOB.OrientationCallback = function(result) { 37 canvas.width = result.width; 38 canvas.height = result.height; 39 var data = context.getImageData(0,0,canvas.width,canvas.height); 40 for (var i = 0; i < data.data.length; i++) { 41 data.data[i] = result.data[i]; 42 } 43 context.putImageData(data,0,0); 44 }; 45 JOB.SwitchLocalizationFeedback(true); 46 JOB.SetLocalizationCallback(function(result) { 47 context.beginPath(); 48 context.lineWidth = "2"; 49 context.strokeStyle="red"; 50 for (var i = 0; i < result.length; i++) { 51 context.rect(result[i].x,result[i].y, 52 result[i].width,result[i].height); 53 } 54 context.stroke(); 55 }); |
著者:飯國 隆志
前回は、我々が開発した小規模利用向けプライベートPaaS「LiPP」の概要と環境構築手順を紹介しました。LiPPは、ユーザーが開発したアプリケーションをDockerコンテナ上にリリースする機能を提供します。またLiPPは、GitHubと連携して、リポジトリにpushしたアプリケーションを自動的にデプロイできます。今回は、前回構築したLiPP環境に、サンプルWebアプリをデプロイする手順を紹介します。
シェルスクリプトマガジン Vol.57は以下のリンク先でご購入できます。
|
1 2 3 4 5 6 7 |
from flask import Flask app = Flask(__name__) @app.route('/') def hello(): return 'Hello World from LiPP!' if __name__ == '__main__': app.run(debug=False, host='0.0.0.0', port=8000) |
|
1 2 3 4 5 6 |
image: python:3.7.0 port: 8000 scripts: - run: command: pip3 install -r requirements.txt daemon: ['python3', 'app.py'] |
|
1 2 3 4 5 6 7 8 |
server { listen 80; server_name sample-app.アプリケーションノードのドメイン名 charset utf-8; location / { proxy_pass http://localhost:8000; } } |
著者:すずきひろのぶ
前回は、Arduino IDEを使って、中国Espressif Systems社のSoC「ESP32」を搭載する開発ボード「ESP32-DevKitC v2」で動作するサンプルプログラムを作成しました。今回は、前回作成したサンプルプログラムを省電力化してみます。
シェルスクリプトマガジン Vol.57は以下のリンク先でご購入できます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
// Hironobu SUZUKI // <suzuki.hironobu@gmail.com> // SUZULABO #include <WiFi.h> #include <WiFiUdp.h> #define SENDBUFSIZE 32 #define SLEEPTIME 3 char ssid[] = "XXXXX"; char pass[] = "XXXXXX"; const char remoteIpAddr[] = "192.168.1.3"; const int port = 21231; uint8_t sendBuffer[SENDBUFSIZE]; int counter = 1; int WiFiStatus = WL_IDLE_STATUS; WiFiUDP udp; void setup() { Serial.begin(115200); WiFiConnectionManage(); udp.begin(port); } void loop() { for (int j = 0; j < SENDBUFSIZE; j++) { sendBuffer[j] = 0; } sendBuffer[0] = counter; sendBuffer[1] = '\0'; WiFiConnectionManage(); udp.beginPacket(remoteIpAddr, port); udp.write(sendBuffer, sizeof(sendBuffer)); udp.endPacket(); Serial.println(counter++); delay(500); if ( counter > 30 ) { system_deep_sleep(10 * 1000000); } } void WiFiConnectionManage() { int connection_error = 0; if ( (WiFiStatus = WiFi.status()) == WL_NO_SHIELD) { WiFiStatus = WL_IDLE_STATUS; } switch (WiFiStatus) { case WL_CONNECT_FAILED: // disconnect -> build connection WiFi.disconnect(); case WL_DISCONNECTED: // build connection case WL_IDLE_STATUS: // build connection Serial.print("WiFi Status: "); Serial.println(WiFiStatus); Serial.print("Attempting to WiFi connection SSID: "); Serial.println(ssid); while ( true ) { WiFiStatus = WiFi.begin(ssid, pass); if ( WiFiStatus == WL_CONNECTED ) { break; } delay(10000); if ( connection_error > 6 ) { // Too many connection error why? reboot. ESP.restart(); } connection_error++; } Serial.println("WiFi Connection Complete."); delay(10000); printWifiStatus(); break; case WL_CONNECTION_LOST: // lost -> try reconnection Serial.println("Try WiFi reconnect"); WiFi.reconnect(); break; default: break; } } void printWifiStatus() { String myssid; while (true) { myssid = WiFi.SSID(); if (myssid != "" ) { break; } delay(3000); } Serial.print("SSID: "); Serial.println(myssid); IPAddress ip = WiFi.localIP(); Serial.print("IP Address: "); Serial.println(ip); } |
|
1 2 3 4 5 |
if ( counter > 30 ) { esp_sleep_enable_timer_wakeup(10*1000000); esp_light_sleep_start(); counter = 0; } |
著者:村本 禎実
ユニケージでは、小さな道具の「コマンド」をシェルスクリプトで組み合わせて、さまざまな業務システムを構築しています。本連載では、毎回あるテーマに従ってユニケージによるシェルスクリプトの記述例を分かりやすく紹介します。第4回は、Webブラウザからデータを参照・削除するためのサーバー側の処理です。
シェルスクリプトマガジン Vol.57は以下のリンク先でご購入できます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
51 #################################################################### 52 # 画面表示用データ作成 53 #################################################################### 54 # 表示データ取得 55 # 1:部門コード 2:部門名称 3:担当者 4:適用開始日 5 56 # :適用終了日 6:適用フラグ(1:適用中 2:適用外) 7:登録日時 57 cat ${lv3d}/BUMON_MST | 58 # 現在適用中のレコードを抽出 59 awk '$4<="'${today}'"' | 60 awk '$5>="'${today}'"' | 61 # 部門コード、適用開始日、登録日時でソート 62 msort key=1@4@7 | 63 # 部門コードをキーとして重複を削除 64 getlast 1 1 | 65 # 適用フラグで判断 66 delr 6 2 | 67 # 部門コードを5桁にする 68 maezero 1.5 > ${tmp}-bumon_data 69 ERROR_CHECK |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
66 67 # LV1データ作成 68 # 適用フラグ 登録日時を作成 69 echo "2 ${todayhms}" > ${tmp}-lv1_del_flg 70 ERROR_CHECK 71 72 # 登録画面から移動してきたことを考え、TOROKUのレコードを除外 73 # 1:id名 2:チェックフラグ 74 cat ${tmp}-name | 75 # TOROKUが入っているレコードを除外 76 ugrep -v "TOROKU" > ${tmp}-name_del 77 ERROR_CHECK 78 79 # POSTされたデータが0バイトより大きいときに処理 80 if [ -s ${tmp}-name_del ] ; then 81 # 1:BUMON_部門コード 2:チェックフラグ(on) 82 cat ${tmp}-name | 83 # 画面でチェックされたものを取得 84 selr 2 on | 85 # 第1フィールドを取得 86 self 1 | 87 # 1:BUMON_部門コード 88 # 余分な文字列を削除 89 sed 's/BUMON_//g' | 90 # 1:部門コード 91 # 部門コードを5桁にする 92 maezero 1.5 | 93 # 部門コードでソート 94 msort key=1 | 95 # LV3ファイルから削除対象のレコードを抽出 96 cjoin0 key=1 - ${lv3d}/BUMON_MST | 97 # 1:部門コード 2:部門名称 3:担当者 4:適用開始日 5 98 # :適用終了日 6:適用フラグ(1:適用中 2:適用外) 7:登録日時 99 # 適用フラグと登録日時を入れ替える 100 # 適用フラグと登録日時を入れ替える為に適用フラグと登録日時を削除 101 delf NF-1 NF | 102 # 1:部門コード 2:部門名称 3:担当者 4:適用開始日 5 103 # :適用終了日 104 # 新しい適用フラグ、登録日時をつける 105 joinx - ${tmp}-lv1_del_flg | 106 # 後ほどデータをマージする為部門コードでソート 107 msort key=1 > ${tmp}-del_bumon 108 # 1:部門コード 2:部門名称 3:担当者 4:適用開始日 109 # 5:適用終了日 6:適用フラグ(1:適用中 2:適用外) 7:登録日時 110 ERROR_CHECK 111 112 # LV1ディレクトリへコピー 113 cp -p ${tmp}-del_bumon ${lv1d}/BUMON_DELETE_${todayhms} 114 ERROR_CHECK 115 else 116 # POSTされたデータが存在しない場合の処理 117 echo "_ _ _ _ _ _ _" > ${tmp}-del_bumon 118 ERROR_CHECK 119 fi 120 |
著者:しょっさん
プログラミング言語「JavaScript」の実行環境「Node.js」と「Express」フレームワークを使って、基本となるWebアプリの開発手法を習得しましょう。第3回は「テストファースト」(最初にテストプログラムを作る)を意識したテストコードの作成方法を解説します。
シェルスクリプトマガジン Vol.57は以下のリンク先でご購入できます。
|
1 2 3 4 5 6 7 8 |
describe('=validate', () => { // 検証された結果 it('should get the result is true', () => { const result = book.validate(req.body); expect(result).toBe(true); expect(req.body.image_url).toBe('http://example.com/'); }); }); |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
beforeEach(() => { req = { body: { book_title: 'title', author: ' しょっさん', publisher: 'USP 研究所', image_uml: '' }, params: { id: 1 } }; res = { redirect: function () { }, render: function () { } }; }); |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
create: function (req, res) { module.exports.register_book(req.body) .then(result => { res.redirect(/books/${result.id}); }).catch(errors => { res.render('error', { message: ' エラーが発生しました.', error: { status: ' 本を登録できませんでした.', stack: errors } }); }); }, |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
describe('=register_book', () => { it('should get the result is book information', (done) => { book.register_book(req.body).then(result => { expect(result.book_title).toBe('title'); expect(result.id).toBeGreaterThanOrEqual(2); expect(result.image_url).toBe('http://example.com/'); done(); }).catch(done.fail); }); it('should catch an error', (done) => { req.body.book_title = ''; book.register_book(req.body).then(done.fail) .catch(result => { expect(result).toEqual([' 本のタイトルが入っていません']); done(); }); }); }); |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
'use strict'; module.exports = { up: (queryInterface, Sequelize) => { /* Add altering commands here. Return a promise to correctly handle asynchronicity. Example: return queryInterface.bulkInsert('Person', [{ name: 'John Doe', isBetaMember: false }], {}); */ }, down: (queryInterface, Sequelize) => { /* Add reverting commands here. Return a promise to correctly handle asynchronicity. Example: return queryInterface.bulkDelete('Person', null, {}); */ } }; |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
'use strict'; module.exports = { up: (queryInterface, Sequelize) => { const models = require('../models'); return models.Library.bulkCreate([ { id: 1, book_title: ' シェルスクリプトマガジン vol.54', author: ' しょっさん', publisher: 'USP 研究所', image_uml: 'https://uec.usp-lab.com/INFO/IMG/SHELLSCRIPTMAG_VOL54.JPG' }, { id: 2, book_title: ' シェルスクリプトマガジン vol.55', author: ' しょっさん', publisher: 'USP 研究所', image_uml: 'https://uec.usp-lab.com/INFO/IMG/SHELLSCRIPTMAG_VOL55.JPG' } ]); }, down: (queryInterface, Sequelize) => { return queryInterface.bulkDelete('libraries', null, {}); } }; |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// routing テスト const request = require('supertest'); const app = require('../app'); // GET の場合 describe('GET /books/', () => { it('respond with http', (done) => { request(app) .get('/books/') .set('Accept', 'text/html') .expect(200, done); }); }); //POST の場合 describe('POST /books/create', () => { it('respond with http', (done) => { request(app) .post('/books/create') .send({book_title: 'test'}) .set('Accept', 'text/html') .expect(302, done); }); }); |
|
1 2 3 4 |
"scripts": { "start": "node ./bin/www", "test": "jasmine" }, |
|
1 2 3 4 5 6 7 8 9 |
"scripts": { "start": "node ./bin/www", "migrate": "sequelize db:migrate", "seed": "sequelize db:seed:all", "build": "npm run migrate && npm run seed", "clean": "sequelize db:seed:undo:all", "retest": "npm run clean && npm run build && npm test", "test": "jasmine" }, |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
language: node_js node_js: - "10.11" - "8.12" before_install: before_script: - "npm install" - "npm run build" script: - "npm test" notifications: on_success: always on_failure: always |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
version: 2 jobs: build: docker: - image: circleci/node:10.11 working_directory: ~/repo steps: - checkout - restore_cache: keys: - v1-dependencies-{{ checksum "package.json" }} - v1-dependencies- - run: npm install - run: npm run build - save_cache: paths: - node_modules key: v1-dependencies-{{ checksum "package.json" }} - run: npm test |
日々のサーバーの運用管理として、決まった時間、決まった曜日、決まった日にちに、ある処理(ジョブ)を実行するという作業はよくあります。Linuxには、そのような時間、曜日、日付を指定してジョブを自動実行するための仕組みの「cron」(クローンまたはクーロン)があります。
今回は、このcronを紹介します。cronでは、あらかじめ実行日時や曜日、ジョブとして実行するためのコマンドを記したファイルを用意しておきます。そのファイルの内容に従って、常駐プログラム(デーモン)「crond」が決められた日時や曜日に、指定されたコマンドを実行します(図1)。

図1 決まった日時や曜日にジョブを自動実行する「cron」
前回は「tar」コマンドによる基本的なバックアップ方法を紹介しました。紹介した方法は、バックアップ対象となるすべてのファイルやディレクトリーをアーカイブ化して一つのファイルに保存する「フルバックアップ」です。このフルバックアップは、毎日実施するバックアップには向きません。バックアップに時間がかかりますし、日々のアーカイブファイルには重複するファイルやディレクトリーが数多く含まれてバックアップデータが肥大化する恐れがあるからです。
日々のサーバー運用管理では、フルバックアップだけでなく、「差分バックアップ」(Partial backup)または「増分バックアップ」(Incremental backup)を併用します。それにより、バックアップ時間を短く、バックアップ保存先の容量を無駄なく抑えられます(図1)。

図1 日々のバックアップ