特集1 Windows 10でWSL 2を使おう
WSL 2ですが、Windows 10 May 2020 Updateを適用したWindows 10 バージョン2004以降だけでなく、バージョン1909や1903にもバックポートされて使えるようになりました。詳しくは、こちらを参照してください。
情報は随時更新致します。
test
WSL 2ですが、Windows 10 May 2020 Updateを適用したWindows 10 バージョン2004以降だけでなく、バージョン1909や1903にもバックポートされて使えるようになりました。詳しくは、こちらを参照してください。
情報は随時更新致します。
004 レポート 暗号通信のTLS1.2以前に脆弱性
005 NEWS FLASH
008 特集1 Windows 10でWSL 2を使おう/三沢友治
020 特集2 高機能CMS Drupal入門/小薗井康志
034 特別企画 量子コンピュータの基礎を知る/沼田祈史、小林有里
046 Raspberry Piを100%活用しよう/米田聡 コード掲載
049 Hello Nogyo!
050 レッドハットのプロダクト/小杉研太 コード掲載
059 中小企業手作りIT化奮戦記/菅雄一
064 法林浩之のFIGHTING TALKS/法林浩之
066 香川大学SLPからお届け!/山下賢治 コード掲載
072 RPA/桑原滝弥、イケヤシロウ
074 Webアプリの正しい作り方/しょっさん コード掲載
084 円滑コミュニケーションが世界を救う!/濱口誠一
086 バーティカルバーの極意/飯尾淳 コード掲載
092 シェルスクリプトの書き方入門/大津真 コード掲載
100 Techパズル/gori.sh
102 コラム「人生の入り口」/シェル魔人
著者:米田 聡
小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第1回は、電子ペーパーディスプレイ搭載の拡張基板を扱います。
シェルスクリプトマガジン Vol.68は以下のリンク先でご購入できます。
図3 電子ペーパーディスプレイに文字を表示するサンプルプログラム(text.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from inky import Inky from PIL import Image, ImageFont, ImageDraw DEFAULT_FONT = '/usr/share/fonts/truetype/fonts-japanese-gothic.ttf' FONT_SIZE = 24 LINE_HEIGHT = 26 ink = Inky() # 2値イメージの作成 image = Image.new('P',(ink.width, ink.height)) draw = ImageDraw.Draw(image) font = ImageFont.truetype(DEFAULT_FONT, FONT_SIZE) # 文字描画 draw.text((0, 0), "シェルスクリプト" , font=font, fill=1) draw.text((0,26), "マガジン" , font=font, fill=1) draw.text((0,52), "ゼロ・ワンシリーズ", font=font, fill=1) draw.text((0,78), "電子ペパーモニタ" , font=font, fill=1) # セットして表示 ink.set_image(image) ink.show() |
図5 電子ペーパーディスプレイに画像を表示するサンプルプログラム(logo.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
from inky import Inky from PIL import Image ink = Inky() img = Image.open("shelllogo.png") # サイズ変換 img = img.resize((ink.width, ink.height)) # 2値画像への変換 img = img.convert('1', dither=True) # セットして表示 ink.set_image(img) ink.show() |
著者:小杉 研太
前回(第3回)に引き続き、アプリやデータの連携を実現するためのミドルウエア製品「Red Hat Integration」を紹介します。第4回はRed Hat Integrationに含まれる「Red Hat AMQ」と「Red Hat Fuse」のアップストリームとなる「Strimzi」と「Apache Camel」について触れます。
シェルスクリプトマガジン Vol.68は以下のリンク先でご購入できます。
図17 KafkaとSlackを統合できるコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
FromKafkaToSlack.java import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.slack.SlackComponent; public class FromKafkaToSlack extends RouteBuilder { @Override public void configure() throws Exception { final SlackComponent slackComponent = (SlackComponent) this.getContext().getComponent("slack"); slackComponent.setWebhookUrl(Webhook URL); from("kafka:my-topic?brokers=my-cluster-kafka-bootstrap:9092") .routeId("from-kafka-to-slack") .to("slack:#my-kafka-project"); } } |
著者:山下 賢治
初めまして。香川大学 工学研究科 修士1年の山下賢治です。今回は、JavaScriptライブラリ「React」と、API向けのクエリー言語「GraphQL」を用いて、GitHubで公開されているリポジトリの検索Webアプリケーションを作成します。リポジトリの絞り込みには、開発言語とスター数を利用します。
シェルスクリプトマガジン Vol.68は以下のリンク先でご購入できます。
図3 「src/auth.js」ファイルに記述する内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client'; import { setContext } from '@apollo/client/link/context'; const httpLink = createHttpLink({ uri: 'https://api.github.com/graphql', }); const authLink = setContext(() => { const TOKEN = process.env.REACT_APP_TOKEN; return { headers: { Authorization: `Bearer ${TOKEN}`, }, }; }); export const client = new ApolloClient({ link: authLink.concat(httpLink), cache: new InMemoryCache(), }); |
図4 「src/index.js」ファイルに記述する内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import React from 'react'; import ReactDOM from 'react-dom'; import { ApolloProvider } from '@apollo/client'; import { client } from './auth'; import RepoInfo from './components/RepoInfo'; import * as serviceWorker from './serviceWorker'; ReactDOM.render( <React.StrictMode> <ApolloProvider client={client}> <RepoInfo /> </ApolloProvider> </React.StrictMode>, document.getElementById('root') ); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister(); |
図5 「src/graphql/index.js」ファイルに記述する内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import { gql } from '@apollo/client'; export const SEARCH_REPO = gql` query getData($queryString: String!) { search(query: $queryString, type: REPOSITORY, first: 10) { nodes { ... on Repository { databaseId nameWithOwner openGraphImageUrl } } } } `; |
図6 「src/components/RepoInfo.jsx」ファイルに記述する内容
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 |
import React, {useState, useEffect, useCallback} from 'react'; import {useLazyQuery} from '@apollo/client'; import { SEARCH_REPO } from '../graphql'; const useRepoData = () => { const [getData, {loading, error, data}] = useLazyQuery(SEARCH_REPO) const [query, setQuery] = useState('') const fetchData = useCallback(() => { getData({ variables: { queryString: query } }); }, [getData, query]); useEffect(() => { fetchData() }, [fetchData]) return [setQuery, {loading, error, data}] } const RepoInfo = () => { const [fetchData, {loading, error,data}] = useRepoData() const handleOnClick = () => { fetchData(`language:python stars:>100`) } if (loading) return <p>Loading Repository</p> if (error) return <p>Error while searching Repository</p> return ( <> <button onClick={handleOnClick}> search </button> { data ? data.search.nodes.map( repo => <div key={repo.databaseId}> <h2>{repo.nameWithOwner}</h2> <img src={repo.openGraphImageUrl} alt='repoImage' /> </div> ) : <></> } </> ) } export default RepoInfo; |
図9 変更後の「src/components/RepoInfo.jsx」ファイルの内容
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 |
import React, {useState, useEffect, useCallback} from 'react'; import {useLazyQuery} from '@apollo/client'; import { SEARCH_REPO } from '../graphql'; const useInput = initialValue => { const [value, set] = useState(initialValue) return {value, onChange: (e) => set(e.target.value)} } const useRepoData = () => { const [getData, {loading, error, data}] = useLazyQuery(SEARCH_REPO) const [query, setQuery] = useState('') const fetchData = useCallback(() => { getData({ variables: { queryString: query } }); }, [getData, query]); useEffect(() => { fetchData() }, [fetchData]) return [setQuery, {loading, error, data}] } const RepoInfo = () => { const stars = useInput(0) const language = useInput('') const [fetchData, {loading, error,data}] = useRepoData() const handleOnClick = () => { fetchData(`language:${language.value} stars:>${stars.value}`) } if (loading) return <p>Loading Repository</p> if (error) return <p>Error while searching Repository</p> return ( <> <label> Language : <input type='text' {...language} /> </label> <label> More than : <input type='text' {...stars} /> Stars </label> <button onClick={handleOnClick}> search </button> { data ? data.search.nodes.map( repo => <div key={repo.databaseId}> <h2>{repo.nameWithOwner}</h2> <img src={repo.openGraphImageUrl} alt='repoImage' /> </div> ) : <></> } </> ) } export default RepoInfo; |
著者:しょっさん
ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならないでしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。第8回は、第1回で述べた「クリーンアーキテクチャ」に習って、ロジックと、フレームワークやライブラリ、その他の処理を分離します。
シェルスクリプトマガジン Vol.68は以下のリンク先でご購入できます。
図1 経費精算申請を処理しているコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import { Request, Response, NextFunction } from "express"; import Express from "express"; import { Expense } from "../models/expense"; const router = Express.Router(); // POST 経費の入力 router.post("/", (req: Request, res: Response, next: NextFunction) => { Expense.create(req.body) .then((result) => { res.status(200).json(result); }) .catch((err) => { console.log(err); res.status(400).json({ id: 20002, message: err }); }); }); |
図3 「User Entity」オブジェクト
1 2 3 4 5 6 |
class User { id: number; name: string; salaray: number; } |
図5 「common/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 |
export enum approval_status { minimum, unapproved, approved, reject, reimburse, maximum, } // エンティティ用のオブジェクトの基本構成 export abstract class EntityObject<T> { protected props: T; protected constructor(props: T) { this.props = props; } } // プリミティブ型のビジネスルール実装のための基本構成 export abstract class PrimitiveObject<T> extends EntityObject<T> { get value(): T { return this.props; } } |
図6 「domains/expenseEntity.ts」ファイル
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 |
import { EntityObject, approval_status, PrimitiveObject } from "../common"; export const MAX_LENGTH = 64; export const MAX_AMOUNT = 1000000; // 費目名のルール class Type extends PrimitiveObject<string> { static create(value: string): Type { if (value.length > MAX_LENGTH || value.length <= 0) throw new Error("費目名が長すぎるか、ありません"); return new Type(value); } } // 承認コードのルール class Approval extends PrimitiveObject<approval_status> { static create(value: approval_status = approval_status.unapproved): Approval { if (value <= approval_status.minimum || value >= approval_status.maximum) throw new Error("承認コードがおかしい"); return new Approval(value); } } // 請求金額のルール class Amount extends PrimitiveObject<number> { static create(value: number): Amount { if (value <= 0 || value >= MAX_AMOUNT) throw new Error("請求金額が範囲を超えている"); return new Amount(value); } } // 経費精算で利用されるクラスの実態 interface IExpenseProps { id?: number | undefined; user_id: string; user_name?: string; date: Date; type: Type; description?: string | null; approval: Approval; amount: Amount; } // オブジェクトを構成する要素 export interface IExpenseValue { id?: number | undefined; user_id: string; user_name?: string; date: Date; type: string; description?: string | null; approval: approval_status; amount: number; } export class ExpenseEntity extends EntityObject<IExpenseProps> { constructor(props: IExpenseProps) { super(props); } set approval(status: approval_status) { this.props.approval = Approval.create(status); } static create(values: IExpenseValue): ExpenseEntity { return new ExpenseEntity({ id: values.id, user_id: values.user_id, user_name: values.user_name, date: values.date, type: Type.create(values.type), description: values.description, approval: Approval.create(values.approval), amount: Amount.create(values.amount), }); } public read(): IExpenseValue { return { id: this.props.id, user_id: this.props.user_id, user_name: this.props.user_name, date: this.props.date, type: this.props.type.value, description: this.props.description, approval: this.props.approval.value, amount: this.props.amount.value, }; } } |
図7 「usecases/SubmitExpense.ts」ファイル
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import { IExpenseRepository } from "./IExpenseRepository"; import { ExpenseEntity, IExpenseValue } from "../domains/expenseEntity"; export class SubmitExpense { private _expenseRepository: IExpenseRepository; constructor(expenseRepository: IExpenseRepository) { this._expenseRepository = expenseRepository; } execute(expense: IExpenseValue) { const e = ExpenseEntity.create(expense); return this._expenseRepository.store(e); } } |
図8 「usecases/IExpenseRepository.ts」ファイル
1 2 3 4 5 6 7 8 9 10 11 12 |
import { ExpenseEntity } from "../domains/expenseEntity"; export interface IExpenseRepository { findAllApproved(): Promise<ExpenseEntity[]>; findAllRejected(): Promise<ExpenseEntity[]>; findUnapproval(id: string): Promise<ExpenseEntity[]>; updateApproval(id: number, expense: ExpenseEntity): Promise<ExpenseEntity>; findById(id: number): Promise<ExpenseEntity>; update(expense: ExpenseEntity): Promise<ExpenseEntity>; store(expense: ExpenseEntity): Promise<ExpenseEntity>; } |
図9 「interfaces/ExpenseController.ts」ファイル
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import { SubmitExpense } from "../usecases/SubmitExpense"; import { IExpenseValue } from "../domains/expenseEntity"; import { ExpenseRepository } from "./expenseRepository"; export class ExpenseController { async submitExpenseController(expense: IExpenseValue): Promise<IExpenseValue> { const expenseRepository = new ExpenseRepository(); try { const usecase = new SubmitExpense(expenseRepository); const result = await usecase.execute(expense); return result.read(); } catch (error) { throw new Error(error); } } } |
図10 「interfaces/ExpenseRepository.ts」ファイル
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 |
import { Expense } from "../../models/expense"; import { approval_status } from "../common"; import { ExpenseEntity } from "../domains/expenseEntity"; import { IExpenseRepository } from "../usecases/IExpenseRepository"; export class ExpenseRepository implements IExpenseRepository { findAllApproved(): Promise<ExpenseEntity[]> { return Expense.findAll({ where: { approval: approval_status.approved, }, }).then((results) => { return results.map((value, index, array) => { return ExpenseEntity.create(value); }); }); } (略) store(e: ExpenseEntity): Promise<ExpenseEntity> { return Expense.create(e.read()) .then((result) => { return ExpenseEntity.create(result); }) .catch((err) => { throw new Error("請求処理が失敗しました"); }); } } |
図11 「expense.ts」ファイル
1 2 3 4 5 6 7 8 9 10 11 12 |
// POST 経費の入力 router.post("/", (req: Request, res: Response, next: NextFunction) => { const e = new ExpenseController(); e.submitExpenseController(req.body!) .then((result) => { res.status(200).json(result); }) .catch((err) => { res.status(400).json({ id: "20201", message: err }); }); }); |
図13 「index.ts」ファイル
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// API app.use("/api/auth", auth); app.use("/api/expense", Authorization.isAuthorized, expense); app.use( "/api/payment", Authorization.isAuthorized, Authorization.isAccounting, payment ); app.use( "/api/approval", Authorization.isAuthorized, Authorization.isBoss, approval ); |
著者:飯尾 淳
Twitterのトレンド分析に関する解説も、とうとう3回目に突入しました。今回は、共起ネットワークグラフの描画処理について説明します。描画にはD3.jsというグラフ描画フレームワークを使います。この描画処理では、データの受け渡し方法にちょっとした工夫をしており、それについても解説します。
シェルスクリプトマガジン Vol.68は以下のリンク先でご購入できます。
図2 トレンド表示画面を構成するビューのソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<h2> <%= link_to @trend.label, "https://twitter.com/search?q=#{@trend.label}", :target => '_blank' %> </h2> <p> <%= t('collected') %> <%= link_to l(@trend.collected, format: :long), "../#{@trend.collected}", :class => 'href' %> <%= link_to t('prev_item'), trend_path(@prev), :class => 'href' if @prev != nil %> <%= link_to t('next_item'), trend_path(@next), :class => 'href' if @next != nil %> </p> <div id="graph_canvas" data-src="<%= api_trend_path(@trend) %>"> </div> |
図5 サーバー側の処理をするRailsのコントローラのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Api::TrendsController < ApplicationController def index render json: Trend.where(collected: params[:date]) end def show l = [] @trend = Trend.find(params[:id]) l.push(@trend.nodes) @trend.nodes.each {|n| l.push(n.links) } render json: l end end |
図6 クライアント側の処理をするコード
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 |
$(document).on('turbolinks:load', function() { if ($('#graph_canvas').attr('data-src') != undefined) { $.ajax({ url: $('#graph_canvas').attr('data-src'), dataType: 'json', success: function(data) { drawGraph(data); }, error: function(data) { alert('error'); } }); } }); function drawGraph(data) { "use strict" var width, height, chartWidth, chartHeight, margin d3.select("#svg").remove() var svg = d3.select("#graph_canvas") .append("svg").attr("id", "svg") var chartLayer = svg.append("g").classed("chartLayer", true) setSize() drawChart(convertData(data)) function convertData(data) { var nodes = data.shift() var n_ary = nodes.map(function(d) { d['r'] = d.freq / 4 + 15; return d }) var l_hash = {} var ctr = 0 for (var n_links of data) { for (var link of n_links) { if (l_hash[link.id] == undefined) { l_hash[link.id] = { line_width: link.corr / 20, source: nodes[ctr] } } else { l_hash[link.id]['target'] = nodes[ctr] } } ctr++ } return { nodes: n_ary, links: Object.values(l_hash) } } function setSize() { width = document.querySelector("#graph_canvas").clientWidth height = document.querySelector("#graph_canvas").clientHeight margin = { top:0, left:0, bottom:0, right:0 } chartWidth = width - (margin.left+margin.right) chartHeight = height - (margin.top+margin.bottom) svg.attr("width", width).attr("height", height) chartLayer .attr("width", chartWidth) .attr("height", chartHeight) .attr("transform", "translate("+[margin.left, margin.top]+")") } function drawChart(data) { var STEM_LENGTH=30 var simulation = d3.forceSimulation() .force("link", d3.forceLink().id(function(d) { return d.index })) .force("collide", d3.forceCollide(function(d) { return d.r + STEM_LENGTH }) .iterations(16) ) .force("charge", d3.forceManyBody()) .force("center", d3.forceCenter(chartWidth / 2, chartHeight / 2)) .force("y", d3.forceY(0)) .force("x", d3.forceX(0)) var link = svg.append("g") .attr("class", "links") .selectAll("line") .data(data.links) .enter() .append("line") .attr("stroke", "brown") .attr("stroke-width", function(d) { return d.line_width }) var node_label = svg.append("g") .attr("class", "nodes") .selectAll("g") .data(data.nodes) .enter().append("g") .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)); var node = node_label.append("circle") .attr("r", function(d) { return d.r }) .attr("fill", function(d) { return (d.freq > 60.0) ? "moccasin" : (d.freq > 20.0) ? "lemonchiffon" : (d.freq > 5.0) ? "beige" : "lavender" }); var label = node_label.append("text") .attr("text-anchor", "middle") .attr("font-family", "Arial") .attr("dy", "0.5em") .attr("font-size", function(d) {return d.r / 1.5; }) .text(function(d) { return d.word; }) var ticked = function() { link.attr("x1", function(d) { return d.source.x; }) .attr("y1", function(d) { return d.source.y; }) .attr("x2", function(d) { return d.target.x; }) .attr("y2", function(d) { return d.target.y; }); node_label.attr("transform", function(d) { return "translate("+d.x+","+d.y+")"; }) } simulation.nodes(data.nodes).on("tick", ticked); simulation.force("link").links(data.links); function dragstarted(d) { if (!d3.event.active) { simulation.alphaTarget(0.1).restart(); } d.fx = d.x; d.fy = d.y; } function dragged(d) { d.fx = d3.event.x; d.fy = d3.event.y; } function dragended(d) { if (!d3.event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; } } } |
著者:大津 真
本連載ではシェルスクリプトの書き方をやさしく紹介します。対象とするシェルは、多くのLinuxディストリビューションが標準シェルとして採用する「Bash」です。第4回は、繰り返し処理を実現する制御構造を中心に解説します。
シェルスクリプトマガジン Vol.68は以下のリンク先でご購入できます。
図1 リストにある果物名を一つずつ表示するシェルスクリプトの例
1 2 3 4 |
for name in メロン バナナ イチゴ ミカン do echo $name done |
図2 シェルスクリプト「showArgs1.sh」の内容
1 2 3 4 5 |
#!/bin/bash for name in $@ do echo $name done |
図3 シェルスクリプト「showArgs2.sh」の内容
1 2 3 4 5 |
#!/bin/bash for name in "$@" do echo $name done |
図4 シェルスクリプト「showArgs3.sh」の内容
1 2 3 4 5 |
#!/bin/bash for name do echo $name done |
図5 シェルスクリプト「colon_to_comma2.sh」の内容
1 2 3 4 5 6 7 8 9 10 11 12 |
#!/bin/bash if [[ $# -eq 0 ]]; then echo "引数でファイルを指定してください" exit 1 fi if [[ ! -f $1 ]]; then echo "$1が見つかりません" exit 1 fi fname=$1 cp "$fname" "${fname}~" tr ":" "," < "${fname}~" > "$fname" |
図6 シェルスクリプト「colon_to_comma3.sh」の内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#!/bin/bash if [[ $# -eq 0 ]]; then echo "引数でファイルを指定してください" exit 1 fi for file in $@ do if [[ ! -f $file ]]; then echo "$fileが見つかりません" exit 1 fi fname=$file echo "変換中: $file" cp "$fname" "${fname}~" tr ":" "," < "${fname}~" > "$fname" done |
図7 シェルスクリプト「hello10.sh」の内容
1 2 3 4 5 |
#!/bin/bash for i in $(seq 10) do echo "$i: こんにちは" done |
図8 シェルスクリプト「case1.sh」の内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#!/bin/bash case $1 in [a-z]*) echo "アルファベット小文字で始まります" ;; [A-Z]*) echo "アルファベット大文字で始まります" ;; [0-9]*) echo "数字で始まります" ;; *) echo "その他" esac |
図9 シェルスクリプト「case2.sh」の内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#!/bin/bash for file do case $file in *.txt) echo "テキスト: $file" ;; *.htm | *.html) echo "HTML: $file" ;; *) echo "その他: $file" esac done |
図10 シェルスクリプト「pat1.sh」の内容
1 2 3 4 5 6 |
#!/bin/bash path="/home/o2/music/sample.test.mp3" echo '${path#/*/} = ' ${path#/*/} echo '${path##/*/} = ' ${path##/*/} echo '${path%.*} = ' ${path%.*} echo '${path%%.*} = ' ${path%%.*} |
図11 シェルスクリプト「cgExt1.sh」の内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#!/bin/bash if [[ $# -eq 0 ]]; then echo "引数でファイルを指定してください" exit 1 fi for file do case $file in *.htm) newfile=${file%.*}.html echo "$file to $newfile" mv $file $newfile ;; *.jpeg) newfile=${file%.*}.jpg echo "$file to $newfile" mv $file $newfile ;; esac done |
図12 シェルスクリプト「while1.sh」の内容
1 2 3 4 5 6 7 |
#!/bin/bash read -p "文字列? " str while [[ -n $str ]] do echo $str | tr "a-z" "A-Z" read -p "文字列? " str done |
図13 シェルスクリプト「while2.sh」の内容
1 2 3 4 5 6 7 8 9 |
#!/bin/bash while true do read -p "文字列? " str if [[ -z $str ]]; then break fi echo $str | tr "a-z" "A-Z" done |
図14 シェルスクリプト「cgExt2.sh」の内容
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 |
#!/bin/bash if [[ $# -eq 0 ]]; then echo "引数でファイルを指定してください" exit 1 fi for file do if [[ ! -f $file ]]; then echo "${file}が見つかりません" continue fi case $file in *.htm) newfile=${file%.*}.html echo "$file to $newfile" mv $file $newfile ;; *.jpeg) newfile=${file%.*}.jpg echo "$file to $newfile" mv $file $newfile ;; esac done |
004 レポート Microsoft社製BASICのソースコード公開
005 NEWS FLASH
008 特集1 まんがで学ぶ自宅ネットワーク入門/麻生二郎
014 特集2 Cisco Webexが実現するテレワーク環境/粕谷一範
022 特集3 PythonとSeleniumを活用 自動操作とデータ分析/川嶋宏彰 コード掲載
035 Hello Nogyo!
036 特別企画 Microsoft Power Platform(後編)/清水優吾
050 ラズパイセンサーボードで学ぶ 電子回路の制御/米田聡 コード掲載
056 レッドハットのプロダクト/杉本拓 コード掲載
064 法林浩之のFIGHTING TALKS/法林浩之
066 バーティカルバーの極意/飯尾淳 コード掲載
072 tele-/桑原滝弥、イケヤシロウ
074 中小企業手作りIT化奮戦記/菅雄一
078 Webアプリの正しい作り方/しょっさん コード掲載
092 円滑コミュニケーションが世界を救う!/濱口誠一
094 香川大学SLPからお届け!/山内真仁 コード掲載
102 シェルスクリプトの書き方入門/大津真 コード掲載
108 Techパズル/gori.sh
110 コラム「ユニケージの本領発揮」/シェル魔人
著者:川嶋 宏彰
最近、プログラミング言語「Python」による自動化やデータ分析が注目されています。本特集では、Pythonと、Webブラウザを自動操作するためのライブラリ「Selenium WebDriver」を用いて、インターネットから取得できるオープンデータを例に、Webブラウザの自動操作方法およびデータ分析方法を分かりやすく紹介します。
シェルスクリプトマガジン Vol.67は以下のリンク先でご購入できます。
図4 非headlessモードのサンプルコード(sample.py)
1 2 3 4 5 6 7 8 9 10 11 |
import time from selenium import webdriver driver = webdriver.Chrome() driver.get('https://www.google.com/') time.sleep(5) search_box = driver.find_element_by_name('q') search_box.send_keys('ChromeDriver') search_box.submit() time.sleep(5) driver.quit() |
図7 headlessモードのサンプルコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
from selenium import webdriver options = webdriver.ChromeOptions() options.add_argument('--headless') options.add_argument('--disable-gpu') driver = webdriver.Chrome(options=options) driver.get('https://www.google.com/') print(driver.title) search_box = driver.find_element_by_name('q') search_box.send_keys('ChromeDriver') search_box.submit() print(driver.title) driver.save_screenshot('search_results.png') driver.quit() |
図24 Seleniumを用いた気温データの自動取得プログラム
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 |
import time from pathlib import Path from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.select import Select # ダウンロード先フォルダの指定 dldir_path = Path('csv') # csv という名前のフォルダとする dldir_path.mkdir(exist_ok=True) # なければ作成 download_dir = str(dldir_path.resolve()) # 絶対パスを取得 print("download_dir: " + download_dir) options = webdriver.ChromeOptions() options.add_experimental_option('prefs', { # Chrome のオプションに 'download.default_directory': download_dir # 絶対パスで指定 }) driver = webdriver.Chrome(options=options) wait = WebDriverWait(driver, 10) # 明示的待機用 (Timeout 10秒) # 自動操作開始 driver.get('https://www.data.jma.go.jp/gmd/risk/obsdl/index.php') # 「地点を選ぶ」 xpath = '//div[@class="prefecture" and text()="東京"]' time.sleep(2) driver.find_element_by_xpath(xpath).click() xpath = '//div[@class="station" and contains(@title, "地点名:東京")]' time.sleep(2) # (★) driver.find_element_by_xpath(xpath).click() # (★) # 「項目を選ぶ」 driver.find_element_by_id('elementButton').click() xpath = '//span[text()="月別値"]/preceding-sibling::input' time.sleep(2) driver.find_element_by_xpath(xpath).click() css = '#日最高気温の平均' time.sleep(2) driver.find_element_by_css_selector(css).click() # 「期間を選ぶ」 driver.find_element_by_id('periodButton').click() time.sleep(2) # <select>内の<option>要素を選択 Select(driver.find_element_by_name('iniy')).select_by_value('2010') Select(driver.find_element_by_name('inim')).select_by_value('1') time.sleep(2) # いったん止めてみる Select(driver.find_element_by_name('endy')).select_by_value('2019') Select(driver.find_element_by_name('endm')).select_by_value('12') time.sleep(2) # 「CSVファイルをダウンロード」 driver.find_element_by_id('csvdl').click() time.sleep(2) driver.quit() |
図27 e-StatのAPI機能を利用した家計調査データを取得するプログラム
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 |
import sys import urllib import urllib.request import json import calendar import matplotlib.pyplot as plt import japanize_matplotlib # japanize-matplotlib を使う場合 import pandas as pd from scipy import stats url = 'https://api.e-stat.go.jp/rest/3.0/app/json/getStatsData?' app_id = '<e-Statマイページで取得したアプリケーションIDを挿入>' cat01 = '010800150' # アイスクリーム・シャーベット # cat01 = '010800130' # チョコレート # cat01 = '011100030' # ビール remove_month = 0 # 特定月を除く場合は1-12のいずれかを指定 # 指定する数字の桁数は決まっているので注意 keys = { 'appId' : app_id, 'lang' : 'J', 'statsDataId' : '0003343671', # 家計調査データ 'metaGetFlg' : 'Y', 'cntGetFlg' : 'N', 'cdTab' : '01', # 金額 'cdTimeFrom' : '2010000101', # 2010年1月から 'cdTimeTo' : '2019001212', # 2019年12月まで 'cdArea' : '00000', # 全国 'cdCat01' : cat01, 'cdCat02' : '03' # 二人以上世帯 } params = urllib.parse.urlencode(keys) r_obj = urllib.request.urlopen(url + params) # データを取得 r_str = r_obj.read() res = json.loads(r_str) # Pythonの辞書へ stats_data = res['GET_STATS_DATA']['STATISTICAL_DATA'] class_obj = stats_data['CLASS_INF']['CLASS_OBJ'] # メタ情報 if 'DATA_INF' not in stats_data: # ['DATA_INF']が入らないときのチェック用 for co in class_obj: if 'CLASS' not in co: print("ERROR: Check params @id= {}, @name= {}" \ .format(co['@id'], co['@name'])) sys.exit(1) values = stats_data['DATA_INF']['VALUE'] # 統計データの数値情報を取得 # メタ情報(CLASS_INF)から取得した品目名称を図のタイトルに使う title = [co['CLASS']['@name'] for co in class_obj if co['@id'] == 'cat01'][0] print(title) # 各要素が [年, 月, 支出金額] の2次元リストにする data = [[int(v['@time'][0:4]), int(v['@time'][6:8]), int(v['$'])] for v in values] print("n =", len(data)) # 120 = 10年 x 12カ月 # Pandasデータフレームの準備 df = pd.DataFrame(data, columns=['year', 'month', '支出(円)']) df['days'] = [calendar.monthrange(df.loc[i, 'year'], df.loc[i, 'month'])[1] for i in df.index] # 各月の日数 df['支出(円/日)'] = df['支出(円)'] / df['days'] # 1日あたりの支出金額 df['y/m'] = df['year'].astype(str) + '/' + df['month'].astype(str) # 結合用 # 気象庁の気温データとマージ df_jma = pd.read_csv('csv/data.csv', skiprows=5, header=None, usecols=[0,1], encoding='Shift_JIS') df_jma.columns = ['y/m', '平均最高気温(℃)'] df = pd.merge(df, df_jma, on='y/m') # データフレームの結合 if remove_month > 0: df = df.query('month != @remove_month') # 特定月を除く場合 # 相関係数を計算 corr, _ = stats.pearsonr(df['平均最高気温(℃)'], df['支出(円/日)']) corr_str = "相関係数: {:2f}".format(corr) print(corr_str) # 散布図をプロット ax = df.plot(kind='scatter', x='平均最高気温(℃)', y='支出(円/日)') ax.set_title(title + ', ' + corr_str) plt.show() |
著者:米田 聡
シェルスクリプトマガジンでは、小型コンピュータボード「Raspberry Pi」(ラズパイ)向けのセンサー搭載拡張ボード「ラズパイセンサーボード」を制作しました。最終回は、前回作成したスクリプトによって記録された温度と湿度をグラフ化して、Webブラウザで閲覧可能にします。
シェルスクリプトマガジン Vol.67は以下のリンク先でご購入できます。
図1 サンプルのHTMLテンプレートファイル(~/webapp/templates/hello.html)
1 2 3 4 5 6 7 8 9 10 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>こんにちは世界</title> </head> <body> <div>{{ text }}</div> </body> </html> |
図2 サンプルのWebアプリケーションスクリプト(~/webapp/hello.py)
1 2 3 4 5 6 7 8 9 10 11 |
from flask import Flask, request, render_template app = Flask(__name__) @app.route('/' , methods=['GET','POST']) def index(): message = 'Hello, World' return render_template("hello.html", text=message) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000) |
図4 トップページのテンプレートファイル(~/webapp/templates/index.html)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>温度・湿度グラフ化ツール</title> </head> <body> <div>グラフ化する日時の範囲を指定してください。</div> <form action="graph" method="post"> <div>開始日時</div> <input id="startdt" type="datetime-local" name="startdt" min="{{ mindt }}" max="{{ maxdt }}" required> <div>終了日時</div> <input id="enddt" type="datetime-local" name="enddt" min="{{ mindt }}" max="{{ maxdt }}" required> <div><input type="submit" value="実行"></div> </form> </body> </html> |
図5 テンプレートファイルからトップページを作成するスクリプト(~/webapp/app.py)
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 |
from flask import Flask, request,render_template import sqlite3 import datetime DBNAME="weather.sqlite3" app = Flask(__name__) @app.route('/') def index(): conn = sqlite3.connect(DBNAME) cur = conn.cursor() sql = "SELECT min(dt) from bme" cur.execute(sql) conn.commit() res = cur.fetchone() m = datetime.datetime.fromisoformat(res[0]) mindt = m.strftime("%Y-%m-%dT%H:%M") sql = "SELECT max(dt) from bme" cur.execute(sql) conn.commit() res = cur.fetchone() m = datetime.datetime.fromisoformat(res[0]) maxdt = m.strftime("%Y-%m-%dT%H:%M") conn.close() return render_template("index.html", maxdt=maxdt, mindt=mindt) # ここに後でグラフ表示ページの関数を追加する if __name__ == '__main__': app.run(host='0.0.0.0', port=5000) |
図8 グラフを表示するページのテンプレートファイル(~/webapp/templates/graph.html)
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 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>グラフ</title> </head> <body> <canvas id="bmeChart"></canvas> <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.js"></script> <script> var elm = document.getElementById("bmeChart"); var bmeChart = new Chart(elm, { type: 'line', data: { labels: [ {% for d in data %} '{{ d[0] }}', {% endfor %} ], datasets: [ { label: '気温(度)', data:[ {% for d in data %} {{ d[1] }}, {% endfor %} ], borderColor: "rgba(255,0,0,1)", backgroundColor: "rgba(0,0,0,0)", }, { label: '湿度(%)', data: [ {% for d in data %} {{ d[2] }}, {% endfor %} ], borderColor: "rgba(0,255,0,1)", backgroundColor: "rgba(0,0,0,0)", }, ], }, options: { }, }); </script> </body> |
図9 app.pyに追加する関数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@app.route('/graph', methods=['POST']) def graph(): if request.method == 'POST': startdt = datetime.datetime.fromisoformat(request.form['startdt']) enddt = datetime.datetime.fromisoformat(request.form['enddt']) conn = sqlite3.connect(DBNAME) cur = conn.cursor() sql = "SELECT dt,temp,hum from bme where dt < " + "'" + enddt.strftime("%Y-%m-%d %H:%M:%S") + "' and dt > '" + startdt.strftime("%Y-%m-%d %H:%M:%S") + "'" cur.execute(sql) conn.commit() res = cur.fetchall() conn.close() # データ件数が200件以上なら100件台になるよう抑える if len(res) > 200: p = int(len(res) / 100) res = res[::p] return render_template("graph.html", data=res) |
著者:杉本 拓
「Red Hat Integration」はアプリやデータの連携を実現するための、インテグレーションパターン、API 連携、API管理とセキュリティ、データ変換、リアルタイムメッセージング、データストリーミングなどを提供するオープンソース製品です。同製品には多くの機能が含まれていますが、本連載ではその概要と一部の機能を紹介します。
シェルスクリプトマガジン Vol.67は以下のリンク先でご購入できます。
図18 二つのdependencyを追加する
1 2 3 4 5 6 7 8 9 |
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jackson2-provider</artifactId> </dependency> <dependency> <groupId>io.apicurio</groupId> <artifactId>apicurio-registry-utils-serde</artifactId> <version>1.2.1.Final</version> </dependency> |
図19 「AvroRegistryExample.java」ファイルの内容
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 |
package com.redhat.kafka.registry; import java.io.File; import java.io.IOException; import java.util.Random; import java.util.concurrent.TimeUnit; import javax.enterprise.context.ApplicationScoped; import org.apache.avro.Schema; import org.apache.avro.generic.GenericData; import org.apache.avro.generic.GenericData.Record; import org.eclipse.microprofile.reactive.messaging.Outgoing; import io.reactivex.Flowable; import io.smallrye.reactive.messaging.kafka.KafkaRecord; @ApplicationScoped public class AvroRegistryExample { private Random random = new Random(); private String[] symbols = new String[] { "RHT", "IBM", "MSFT", "AMZN" }; @Outgoing("price-out") public Flowable<KafkaRecord<String, Record>> generate() throws IOException { Schema schema = new Schema.Parser().parse( new File(getClass().getClassLoader().getResource("price-schema.avsc").getFile()) ); return Flowable.interval(1000, TimeUnit.MILLISECONDS) .onBackpressureDrop() .map(tick -> { Record record = new GenericData.Record(schema); record.put("symbol", symbols[random.nextInt(4)]); record.put("price", String.format("%.2f", random.nextDouble() * 100)); return KafkaRecord.of(record.get("symbol").toString(), record); }); } } |
図20 「price-schema.avsc」ファイルの内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{ "type": "record", "name": "price", "namespace": "com.redhat", "fields": [ { "name": "symbol", "type": "string" }, { "name": "price", "type": "string" } ] } |
図21 登録されたAvroのスキーマ
1 2 3 4 5 6 7 8 |
{ "createdOn": 1575919739708, "modifiedOn": 1575919739708, "id": "prices-value", "version": 1, "type": "AVRO", "globalId": 4 } |
図22 プロパティファイル
1 2 3 4 5 6 7 8 9 10 11 |
# Configuration file kafka.bootstrap.servers=localhost:9092 mp.messaging.outgoing.price-out.connector=smallrye-kafka mp.messaging.outgoing.price-out.client.id=price-producer mp.messaging.outgoing.price-out.topic=prices mp.messaging.outgoing.price-out.key.serializer=org.apache.kafka.common.serialization.StringSerializer mp.messaging.outgoing.price-out.value.serializer=io.apicurio.registry.utils.serde.AvroKafkaSerializer mp.messaging.outgoing.price-out.apicurio.registry.url=http://localhost:8081/api mp.messaging.outgoing.price-out.apicurio.registry.artifact-id=io.apicurio.registry.utils.serde.strategy.TopicIdStrategy |
著者:飯尾 淳
前回に引き続き、筆者らの研究グループが開発したTwitterのトレンド分析システムについて解説します。前回は、そもそもTwitterのトレンドとは何かから始まり、開発者登録をしてトレンドAPIを叩き、そのリストを収集する方法まで説明しました。今回は、トレンドをキーにしてツイートを取得する方法と、ツイート群を対象として共起ネットワークグラフを作る方法を紹介します。
シェルスクリプトマガジン Vol.67は以下のリンク先でご購入できます。
図4 トレンドを収集して分析するプログラムのコード(その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 |
#!/usr/bin/env python ########################################################### # ヘルパーファンクション ########################################################### import re # 特殊文字をエスケープする def escape(s, quoted=u'\'"\\', escape=u'\\'): return re.sub(u'[%s]' % re.escape(quoted), lambda mo: escape + mo.group(), s) # ノードのプリティプリンタ def pp_node(ary_of_nodes): for node in ary_of_nodes: keyword = escape(node[0]) frequency = node[1] print("node_hash['{0}'] = trend.nodes.create(word: '{1}', freq: {2:6.2f})" .format(keyword, keyword, frequency)) # リンクのプリティプリンタ def pp_link(ary_of_links): for link in ary_of_links: (kw1,kw2) = link[0].split(',') print("link = Link.create(corr: {0:6.2f})".format(link[1])) print("node_hash['{0}'].links << link".format(escape(kw1))) print("node_hash['{0}'].links << link".format(escape(kw2))) # 値を正規化する。p_fragは百分率とするときTrue def normalize(hash_obj, p_flag): maxval = max(hash_obj, key=lambda x: x[1])[1] factor = 100 if p_flag else 1 return [(x[0], x[1] / maxval * factor) for x in hash_obj] |
図5 トレンドを収集して分析するプログラムのコード(その2)
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 MeCab # 「PATH_TO_MECAB_NEOLOGD」の部分は環境に合わせて修正 m = MeCab.Tagger("-d PATH_TO_MECAB_NEOLOGD") stopwords = {'*', 'HTTPS', 'HTTP', 'WWW', 'の', 'ん', 'ン', 'ω', '???'} def create_graph(keyword, collected, tweets): ary_of_ary = [] for tweet in tweets: ary = [] lines = m.parse(tweet).split('\n') for line in lines: if line.count('\t') == 0: continue (kw, prop) = line.split('\t') props = prop.split(',') if len({props[-3]} & stopwords) > 0: continue if props[1] == '固有名詞': ary.append(props[-3]) ary_of_ary.append(ary) KW_THRESHOLD = 20 kw_dict = {} counter = 0 for ary in ary_of_ary: for kw in ary: if kw in kw_dict: kw_dict[kw] = kw_dict[kw] + 1.0 else: kw_dict[kw] = 1.0 counter = counter + 1 for kw,val in kw_dict.items(): kw_dict[kw] = val / counter * 100 if len(kw_dict) > 0: kw_dict = sorted(kw_dict.items(), key = lambda x: x[1], reverse = True)[0:KW_THRESHOLD-1] kw_dict = normalize(kw_dict, True) print("node_hash = {}") print("trend = Trend.create(label: '{0}', collected:'{1}')" .format(escape(keyword), collected)) pp_node(kw_dict) corr_dict = {} for src in kw_dict: for dst in kw_dict: if src == dst: continue src_w = src[0] dst_w = dst[0] sd_pair = src_w + "," + dst_w if sd_pair in corr_dict: continue prob = 0.0 for ary in ary_of_ary: if ((src_w in ary) & (dst_w in ary)): prob = prob + 1.0 prob = 100 * prob / len(ary_of_ary) if prob > 0: corr_dict[dst_w + "," + src_w] = prob if len(corr_dict) > 0: lk_dict = sorted(corr_dict.items(), key = lambda x: x[1], reverse = True) # 後半3/4をカットして短くする lk_dict = lk_dict[0:int(len(lk_dict)*1/4)] if len(lk_dict) > 0: lk_dict = normalize(lk_dict, True) pp_link(lk_dict) |
図6 トレンドを収集して分析するプログラムのコード(その3)
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 |
########################################################### # すでに登録済みか否かのチェック ########################################################### import sqlite3 from contextlib import closing def has_been_registered(keyword, collected): # 「PATH_TO_THE_DATABASE_FILE」の部分は環境に合わせて修正 dbfile = "PATH_TO_THE_DATABASE_FILE(development.sqlite3)" with closing(sqlite3.connect(dbfile)) as conn: c = conn.cursor() sql = 'select label, collected from trends \ where label=? and collected=?' res = (keyword, collected) c.execute(sql, res) return (len(c.fetchall()) > 0) ########################################################### # メイン関数 ########################################################### from twitter import * from datetime import date def main(): # WOEID を希望の場所に合わせて指定。「23424856」は日本 woeid = 23424856 # アプリ登録時に取得した情報を下記に設定 CK = 'ADD_HERE_YOUR_CONSUMER_KEY' CS = 'ADD_HERE_YOUR_CONSUMER_SECRET' AT = 'ADD_HERE_YOUR_ACCESS_TOKEN' AS = 'ADD_HERE_YOUR_ACCESS_TOKEN_SECRET' twitter = Twitter(auth = OAuth(AT,AS,CK,CS)) results = twitter.trends.place(_id = woeid, exclude="hashtags") d = date.today() collected = d.strftime('%Y-%m-%d') for location in results: for trend in location["trends"]: keyword = trend["name"] if has_been_registered(keyword, collected): continue query_kw = keyword + " exclude:retweets exclude:nativeretweets" tw_rslts = twitter.search.tweets(q=query_kw, lang='ja', locale='ja', count=100) tw_ary = [] for tweet in tw_rslts["statuses"]: tw_ary.append(tweet["text"]) create_graph(keyword, collected, tw_ary) if __name__ == "__main__": main() |
図7 ワードグラフ作成部で出力されるRubyスクリプトの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
node_hash = {} trend = Trend.create(label: 'ホシガリス', collected:'2020-06-07') node_hash['アニポケ'] = trend.nodes.create(word: 'アニポケ', freq: 100.00) node_hash['ポケモン'] = trend.nodes.create(word: 'ポケモン', freq: 56.76) node_hash['ゴルーグ'] = trend.nodes.create(word: 'ゴルーグ', freq: 29.73) node_hash['思'] = trend.nodes.create(word: '思', freq: 18.92) (略) node_hash['ピカチュウ'] = trend.nodes.create(word: 'ピカチュウ', freq: 5.41) node_hash['パチリス'] = trend.nodes.create(word: 'パチリス', freq: 5.41) node_hash['スピアー'] = trend.nodes.create(word: 'スピアー', freq: 5.41) link = Link.create(corr: 100.00) node_hash['ポケモン'].links << link node_hash['アニポケ'].links << link link = Link.create(corr: 75.00) node_hash['ゴルーグ'].links << link node_hash['アニポケ'].links << link (略) link = Link.create(corr: 25.00) node_hash['マユルド'].links << link node_hash['ドクケイル'].links << link node_hash = {} trend = Trend.create(label: '孤独のグルメ', collected:'2020-06-07') (略) |
著者:しょっさん
ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならないでしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。第7回は、JavaScriptフレームワーク「Vue.js」でアプリを作成し、テストとリリースの方法を紹介します。
シェルスクリプトマガジン Vol.67は以下のリンク先でご購入できます。
図2 Vue.js および各ライブラリを利用するための定義(simple.htmlから抜粋)
1 2 3 4 |
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script src="https://unpkg.com/vue-router@3.3.2/dist/vue-router.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios@0.19.2/dist/axios.min.js"></script> <script src="./jwt-decode.min.js"></script> |
図3 Vue RouterのHTMLファイル側の定義
1 2 3 4 5 6 7 8 |
<div id="app"> <h1>経費精算アプリケーション(Vue.js)</h1> <router-link to="/expense">経費登録</router-link> <router-link to="/payment">経費精算</router-link> <router-link to="/login">ログイン</router-link> <router-link to="/logout">ログアウト</router-link> <router-view /> </div> |
図4 Vue RouterのJavaScript側の定義
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 |
const router = new VueRouter({ routes: [ // 精算処理 { path: "/expense", }, // 支払処理 { path: "/payment", }, // 認証 - ログイン { path: "/login", }, // 認証 - ログアウト { path: "/logout", }, // どのURLにも該当しなかった場合 { path: "*", redirect: "/expense", }, ], }); |
図5 認証部分のVueコンポーネントとVue Router定義
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 |
const Login = { template: "#login", data: function () { return { user: localStorage.user || "", password: localStorage.password || "", remember: localStorage.remember || false, error: false, }; }, methods: { login: function () { axios .post(`${baseUrl}/api/auth`, { user: this.user, password: this.password, }) .then((response) => { if (response.status === 200) { // ログインが成功した場合は、ローカルストレージにトークンを保管する(ログインが成功した状態とする) this.error = false; localStorage.token = response.data.token; // "remember me" チェックボックスが付いていたら、各々の入力項目を保管する if (this.remember) { localStorage.user = this.user; localStorage.password = this.password; localStorage.remember = true; } else { // 逆にオフであれば入力項目の内容を破棄する delete localStorage.user; delete localStorage.password; delete localStorage.remember; } // ログイン成功したら /expense へ切り替える this.$router.replace("/expense"); } else { this.error = true; } }) .catch((response) => { this.error = true; this.remember = false; console.log(response); }); }, }, }; const router = new VueRouter({ routes: [ (略) // ログイン画面 { path: "/login", component: Login, }, // ログアウト処理 { path: "/logout", beforeEnter: (to, from, next) => { delete localStorage.token; next("/login"); }, }, ], }); |
図6 認証部分のHTMLテンプレート
1 2 3 4 5 6 7 8 9 10 |
<script type="text/x-template" id="login"> <form @submit.prevent="login"> <input v-model="user" type="email" placeholder="Your Email" autofocus="" /> <input v-model="password" type="password" placeholder="Your Password" /> <button type="submit">ログイン</button> </form> <div v-show="error"> <p>ログイン失敗</p> </div> </script> |
図7 請求処理ののVueコンポーネントとVue Router定義
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 |
const Expense = { template: "#expense", data: function () { let decoded = {}; if (localStorage.token) { // トークン内のユーザー情報を基に変数へ配置 decoded = jwt_decode(localStorage.token); } return { user: decoded.email || "", id: decoded.id || "", user_name: decoded.user_name || "", date: "", type: "", amount: "", description: "", error: false, }; }, // 経費を登録するメソッド methods: { expense: function () { axios .post( `${baseUrl}/api/expense`, { user: this.user, user_id: this.id, user_name: this.user_name, date: this.date, type: this.type, amount: this.amount, description: this.description, }, { headers: { Authorization: `Bearer ${localStorage.token}`, }, } ) .then((response) => { if (response.status === 200) { // 正常に登録できた場合は、変更が伴うフィールドをクリアーして、再度入力可能な状態にする this.error = false; console.log(response); this.date = ""; this.type = ""; this.amount = ""; this.description = ""; } }) .catch((response) => { this.error = true; console.log(response); }); }, }, }; const router = new VueRouter({ routes: [ // 経費登録 { path: "/expense", component: Expense, beforeEnter: (to, from, next) => { // 認証前の場合は /login ページへ遷移する if (!localStorage.token) { next({ path: "/login", query: { redirect: to.fullPath }, }); } else { next(); } }, }, (略) }); |
図8 請求処理のHTMLテンプレート
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<script type="text/x-template" id="expense"> <div> <form @submit.prevent="expense"> <input v-model="user_name" type="text" placeholder="Your Name" /> <input v-model="user" type="email" placeholder="Your Email" /> <input v-model="id" type="hidden" placeholder="Your User ID" /> <input v-model="date" type="datetime-local" placeholder="経費利用日" autofocus="" /> <input v-model="type" type="text" placeholder="費目" /> <input v-model="amount" type="number" placeholder="金額" /> <input v-model="description" type="text" placeholder="費用詳細" /> <button type="submit">経費申請</button> </form> <p v-if="error" class="error">経費登録失敗</p> </div> </script> |
図9 支払処理のVueコンポーネントとVue Router定義
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 |
// 経費リストを axios を利用して取得する関数 const getPayments = function (callback) { axios .get(`${baseUrl}/api/payment`, { // JWTの認可ヘッダー headers: { Authorization: `Bearer ${localStorage.token}`, }, }) .then((response) => { if (response.status === 200) { // "response.data" 配下に経費リストが含まれる callback(null, response.data); } else { callback(true, response); } }) .catch((response) => { callback(true, response); }); }; // 経費リスト用の Vueコンポーネント const Payment = { template: "#payment", data: function () { return { loading: false, error: false, payments: function () { return []; }, }; }, // 初期化されたときにデータを取得する created: function () { this.fetchData(); }, // ルーティングが変更されてきたときに再度データを取得する watch: { $route: "fetchData", }, // 経費データを取得するメソッドのメイン部分 methods: { fetchData: function () { this.loading = true; getPayments( function (err, payments) { this.loading = false; if (!err) this.payments = payments; else this.error = true; }.bind(this) ); }, }, }; const router = new VueRouter({ routes: [ (略) { path: "/payment", component: Payment, beforeEnter: (to, from, next) => { // 認証前の場合は /login ページへ遷移する if (!localStorage.token) { next({ path: "/login", query: { redirect: to.fullPath }, }); } else { next(); } }, }, (略) ], }); |
図10 支払処理のHTMLテンプレート
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<script type="text/x-template" id="payment"> <div> <table> <tr> <th>ユーザー名</th> <th>発生日</th> <th>費目</th> <th>経費</th> <th>詳細</th> </tr> <tr v-for="payment in payments"> <td>{{payment.user_name}}</td> <td>{{payment.date}}</td> <td>{{payment.type}}</td> <td>{{payment.amount}}</td> <td>{{payment.description}}</td> </tr> </table> <div class="loading" v-if="loading">アクセス中...</div> <p v-if="error" class="error">経費取得失敗</p> </div> </script> |
図14 controllers/auth/authentication.tsファイルの追加・改修部分
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 |
import jwt from "jsonwebtoken"; export class Authentication { (略) static verifyLocal(username: string, password: string, done: any) { User.findOne({ where: { email: username, deleted_at: null, }, }) .then((user) => { if (user && bcrypt.compareSync(password, user.hash)) { const opts = { issuer: process.env.ISSUER, audience: process.env.AUDIENCE, expiresIn: process.env.EXPIRES, }; const secret: string = process.env.SECRET || "secret"; const token: string = jwt.sign( { email: user.email, id: user.id, user_name: user.last_name + " " + user.first_name, }, secret, opts ); return done(null, token); } return done(true, "authentication error"); }) .catch((err) => done(true, err)); } (略) } |
図15 controllers/auth/authorization.tsファイル
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 |
import { Request, Response, NextFunction } from "express"; import { User } from "../../models/user"; import passport from "passport"; import { Strategy as JWTStrategy, ExtractJwt } from "passport-jwt"; export class Authorization { // JWTトークンで該当するユーザーの有無をチェック static verifyJWT(req: Request, jwt_payload: any, done: any) { User.findOne({ where: { email: jwt_payload.email, deleted_at: null, }, }).then((user) => { if (!user) return done(null, false); return done(null, user.get()); }); } // JWT Strategyの定義 static setJWTStrategy() { const field = { jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), issuer: process.env.ISSUER, audience: process.env.AUDIENCE, secretOrKey: process.env.SECRET || "secret", passReqToCallback: true, }; passport.use(new JWTStrategy(field, this.verifyJWT)); } // 認可チェック static isAuthorized(req: Request, res: Response, next: NextFunction) { passport.authenticate("jwt", { session: false }, (err, user) => { if (err) { res.status(401).json({ status: "10001" }); } if (!user) { res.status(401).json({ status: "10002" }); } else { return next(); } })(req, res, next); } } |
図16 API用のモジュールとルーティング定義
1 2 3 4 5 6 7 8 9 |
// API用ルーティング先のモジュール import auth from "./api/auth"; import payment from "./api/payment"; import expense from "./api/expense"; // APIルーティング app.use("/api/auth", auth); app.use("/api/expense", Authorization.isAuthorized, expense); app.use("/api/payment", Authorization.isAuthorized, payment); |
図18 請求処理(/api/expense.ts)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import { Request, Response, NextFunction } from "express"; import Express from "express"; import { Expense } from "../models/expense"; const router = Express.Router(); // POST 経費の入力 router.post("/", (req: Request, res: Response, next: NextFunction) => { Expense.create(req.body) .then((result) => { res.status(200).json(result); }) .catch((err) => { console.log(err); res.status(400).json({ id: 20002, message: err }); }); }); export default router; |
図19 支払処理(/api/payment.ts)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import { Request, Response, NextFunction } from "express"; import Express from "express"; import { Expense } from "../models/expense"; const router = Express.Router(); // POST / ユーザーの認証処理 router.get("/", (req: Request, res: Response, next: NextFunction) => { Expense.findAll() .then((results) => { res.status(200).json(results); }) .catch((err) => { res.status(400).json({ id: 20011, message: err }); }); }); export default router; |
図35 修正した/src/index.tsファイル
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 |
import { Request, Response, NextFunction } from "express"; import Express from "express"; const app = Express(); import logger from "morgan"; import { Authentication } from "./controllers/auth/authentication"; import { Authorization } from "./controllers/auth/authorization"; app.use(logger("dev")); app.use(Express.json()); app.use(Express.urlencoded({ extended: true })); Authentication.initialize(app); Authentication.setLocalStrategy(); Authorization.setJWTStrategy(); app.use(Express.static("htdocs")); // API用ルーティング import auth from "./api/auth"; import payment from "./api/payment"; import expense from "./api/expense"; // API app.use("/api/auth", auth); app.use("/api/expense", Authorization.isAuthorized, expense); app.use("/api/payment", Authorization.isAuthorized, payment); app.use((req: Request, res: Response, next: NextFunction) => { var err: any = new Error("Not Found"); err.status = 404; next(err); }); // error handler app.use((err: any, req: Request, res: Response, next: NextFunction) => { res.locals.message = err.message; res.locals.error = req.app.get("env") === "development" ? err : {}; res.status(err.status || 500); res.json(err); }); const port = process.env.PORT || 3000; app.listen(port, () => { console.log(`http://localhost:${port}`); }); export default app; |
著者:山内 真仁
以前、ゲームエンジン「Unity」を使ったレースゲームをSLPのチーム活動で
作成しました。その経験を生かして今回は、Unityとプログラミング言語「C#」を使って、簡単なレースゲームを作成する方法を紹介します。Unityの物理エンジンを使用することで、複雑なコードを書かなくてもリアルな挙動のゲームを作成できます。
シェルスクリプトマガジン Vol.67は以下のリンク先でご購入できます。
図12 「CarController.cs」ファイルに記述する内容
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 |
using UnityEngine; public class CarController : MonoBehaviour { public GameObject[] wheelMeshes = new GameObject[4]; // ホイールオブジェクトを格納する配列 public WheelCollider[] wheelColliders = new WheelCollider[4]; // Wheel Colliderを格納する配列 public float maxMotorTorque = 300f; // 車輪に加える最大トルク public float brakeTorque = 500f; // ブレーキのトルク public float maxSteerAngle = 25f; // ステアリングの最大舵角 float accel, steer; // アクセルとステアリングの入力値 bool brake; // ブレーキをかけているかどうか // 画面を描画するたびに実行されるメソッド(実行間隔はフレームレートに依存) void Update() { steer = Input.GetAxis("Horizontal"); // ←→で旋回 accel = Input.GetAxis("Vertical"); // ↑↓でアクセル brake = Input.GetKey(KeyCode.Space); // スペースでブレーキ // Wheel Colliderの動きを見た目に反映する for (int i = 0; i < 4; i++) { wheelColliders[i].GetWorldPose(out Vector3 position, out Quaternion rotation); wheelMeshes[i].transform.position = position; wheelMeshes[i].transform.rotation = rotation; } } // フレームレートに依存せず、定期的に実行されるメソッド(0.02秒に1回) void FixedUpdate() { // Wheel Colliderに各パラメータを代入 for(int i = 0; i < 4; i++) { if (i < 2) wheelColliders[i].steerAngle = steer * maxSteerAngle; // ステアリング(前輪) wheelColliders[i].motorTorque = accel * maxMotorTorque; // アクセル wheelColliders[i].brakeTorque = brake ? brakeTorque : 0f; // ブレーキ } } } |
図14 CarController.csファイルに追加する記述
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
GameObject brakeLight, headLight; // ランプ類のオブジェクトを格納する変数 // ゲーム開始時に1回のみ実行されるメソッド void Start() { // ランプ類のオブジェクトを探して取得 brakeLight = GameObject.Find("SkyCarBrakeLightsGlow"); headLight = GameObject.Find("SkyCarHeadLightsGlow"); } void Update() { // ランプ類の点灯・消灯 brakeLight.SetActive(brake); if (Input.GetKeyDown(KeyCode.H)) { headLight.SetActive(!headLight.activeSelf); } |
図16 「Timer.cs」ファイルに記述する内容
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 |
using UnityEngine; using UnityEngine.UI; using UnityEngine.SceneManagement; public class Timer : MonoBehaviour { public Text timeText; // タイム表示用のテキスト float startTime; // 計測開始の時刻 bool start, check, goal; // 各地点の通過フラグ void Start() { // オブジェクトのコンポーネントを取得 timeText = GameObject.Find("TimeText").GetComponent<Text>(); timeText.text = "TIME 00.000"; // テキストの初期化 } void Update() { // スタートしてからゴールするまでタイムを表示 if (!goal && start) timeText.text = "TIME " + (Time.time - startTime).ToString("00.000"); // ゴール後に[R]キーを押すとリスタート if (goal && Input.GetKey(KeyCode.R)) SceneManager.LoadScene(SceneManager.GetActiveScene().name); } // トリガーオブジェクトに侵入した時に呼び出されるメソッド void OnTriggerEnter(Collider other) { if (other.gameObject.name == "StartPoint") { if (check) { goal = true; // チェックポイント通過済みならゴール timeText.color = Color.red; } else if (!start && !check) { start = true; // チェックポイントを通過していない場合、タイム計測開始 startTime = Time.time; } } else if (start && other.gameObject.name == "CheckPoint") check = true; // チェックポイントを通過 } } |
著者:大津 真
本連載ではシェルスクリプトの書き方をやさしく紹介します。対象とするシェルは、多くのLinuxディストリビューションが標準シェルとして採用する「Bash」です。第3回は、シェルスクリプトにおける条件分岐の使用方法を中心に解説します。
図1 シェルスクリプト「ping.sh」の内容
1 2 3 4 5 |
#!/bin/bash if ping -c1 192.168.0.2 > /dev/null then echo " 応答あり" fi |
図2 シェルスクリプト「secret1.sh」の内容
1 2 3 4 5 6 7 |
#!/bin/bash secret=" ひらけごま" echo -n " 合言葉は?: " read aikotoba if test $secret = $aikotoba; then echo " 正しい合言葉です!" fi |
図3 シェルスクリプト「secret2.sh」の内容
1 2 3 4 5 6 7 |
#!/bin/bash secret=" ひらけごま" echo -n " 合言葉は?: " read aikotoba if [[ $secret = $aikotoba ]]; then echo " 正しい合言葉です!" fi |
図4 シェルスクリプト「secret3.sh」の内容
1 2 3 4 5 6 7 8 9 |
#!/bin/bash secret=" ひらけごま" echo -n " 合言葉は?: " read aikotoba if [[ $secret = $aikotoba ]]; then echo " 正しい合言葉です!" else echo " 合言葉が間違っています" fi |
図5 平成年を西暦年に変換するシェルスクリプト「heiseiToSeireki.sh」
1 2 3 4 5 6 7 8 9 10 |
#!/bin/bash if [[ $# -eq 0 ]]; then echo " 平成年を指定してください" exit 1 fi if [[ "$1" -lt 1 || "$1" -gt 31 ]]; then echo "1〜31 までの数値を入力してください" exit 1 fi echo " 平成$1 年は西暦$(($1+1988)) 年" |
図6 指定したファイル中のコロンをカンマに変換するシェルスクリプト「colon_to_comma.sh」
1 2 3 4 |
#!/bin/bash fname=$1 cp "$fname" "${fname}~" tr ":" "," < "${fname}~" > "$fname" |
図7 引数チェック用のコードを追加した「colon_to_comma2.sh」
1 2 3 4 5 6 7 8 9 10 11 12 |
#!/bin/bash if [[ $# -eq 0 ]]; then echo " 引数でファイルを指定してください" exit 1 fi if [[ ! -f $1 ]]; then echo "$1 が見つかりません" exit 1 fi fname=$1 cp "$fname" "${fname}~" tr ":" "," < "${fname}~" > "$fname" |
004 レポート Linuxの新版「Ubuntu 20.04 LTS」リリース
005 レポート VS Codeライクな「Eclipse Theia」登場
006 NEWS FLASH
008 特集1 1000円から始めるIoT/麻生二郎 コード掲載
023 Hello Nogyo!
026 特集2 OneDriveを有効活用しよう/三沢友治
042 特別企画 Microsoft Power Platform(前編)/清水優吾
050 ラズパイセンサーボードで学ぶ 電子回路の制御/米田聡 コード掲載
054 レッドハットのプロダクト/平田千浩 コード掲載
056 バーティカルバーの極意/飯尾淳 コード掲載
062 μ(マイクロ)/桑原滝弥、イケヤシロウ
064 Webアプリの正しい作り方/しょっさん コード掲載
070 円滑コミュニケーションが世界を救う!/濱口誠一
072 MySQL Shellを使おう/梶山隆輔
079 中小企業手作りIT化奮戦記/菅雄一
084 法林浩之のFIGHTING TALKS/法林浩之
086 香川大学SLPからお届け!/檜垣龍德 コード掲載
092 ユニケージ開発手法入門/石崎博之、掛本健一 コード掲載
098 シェルスクリプトの書き方入門/大津真 コード掲載
104 Techパズル/gori.sh
106 コラム「コロナ禍の中で」/シェル魔人
著者:麻生 二郎
センサーをつないで状態を監視するだけのIoT(モノのインターネット)を
始めるには、小型コンピュータボード「Raspberry Pi」は高機能かつ高価で
す。そこで1000円以下で購入できるマイコンボード「ESP32 ESP-32S」を使
って、簡単なプログラムと共にI oTを始めてみましょう。
シェルスクリプトマガジン Vol.66は以下のリンク先でご購入できます。
図2 ネットワーク接続プログラム(wlan.py)
1 2 3 4 5 6 |
import network wlan_if = network.WLAN(network.STA_IF) wlan_if.active(True) wlan_if.ifconfig(('192.168.1.100', '255.255.255.0', '192.168.1.1', '192.168.1.1')) wlan_if.connect('SSID', 'パスワード') wlan_if.inconfig() |
図3 簡易サーバーを立ち上げるプログラム(webserver_test.py)
1 2 3 4 5 6 7 8 9 10 |
import picoweb app = picoweb.WebApp(__name__) @app.route("/") def index(req, resp): yield from picoweb.start_response(resp) yield from resp.awrite("こんにちは") app.run(debug=True, host = "192.168.1.100") |
図12 Webブラウザから湿温度・気圧を取得するプログラム(web_bme280.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import picoweb, machine, bme280 i2cpin = machine.I2C(scl=machine.Pin(22), sda=machine.Pin(21)) bme = bme280.BME280(i2c=i2cpin) app = picoweb.WebApp(__name__) @app.route("/") def index(req, resp): bme280values = bme.values yield from picoweb.start_response(resp) yield from resp.awrite("気温:" + bme280values[0]) yield from resp.awrite("|湿度:" + bme280values[2]) yield from resp.awrite("|気圧:" + bme280values[1]) app.run(debug=True, host = "192.168.1.100") |
図16 明るさによってライトの点灯を促すプログラム(web_cds.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import picoweb, machine adc = machine.ADC(machine.Pin(36)) adc.atten(machine.ADC.ATTN_11DB) app = picoweb.WebApp(__name__) @app.route("/") def index(req, resp): csdvalue = adc.read() yield from picoweb.start_response(resp) if csdvalue < 2500: yield from resp.awrite("明かりをつけましょう") else: yield from resp.awrite("十分な明るさです") app.run(debug=True, host = "192.168.1.100") |
著者:米田 聡
シェルスクリプトマガジンでは、小型コンピュータボード「Raspberry Pi」(ラズパイ)向けのセンサー搭載拡張ボード「ラズパイセンサーボード」を制作しました。第13 回では、同ボードに搭載された湿温度・気圧センサー「BME280」から取得したデータをグラフ化する準備をします。
シェルスクリプトマガジン Vol.66は以下のリンク先でご購入できます。
図2 PythonからSQLiteを操作するサンプルプログラム(test_sqlite3.py)
1 2 3 4 5 6 7 8 9 |
import sqlite3 DBNAME = "test.sqlite3" # データベースファイル名 conn = sqlite3.connect(DBNAME) # SQLite 3に接続 cur = conn.cursor() # カーソルを得る cur.execute("SQL文") # SQLを発行 conn.commit() # コミット data = cur.fetchall() # 結果をdataに格納 conn.close() # データベースを閉じる |
図3 温度、湿度、気圧のデータをSQLiteに保存するプログラム(storebme.py)
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 |
import time, os, sys, signal import smbus2 import bme280 import sqlite3 BME280_ADDR = 0x76 BUS_NO = 1 DBNAME = 'weather.sqlite3' def store_values(values): conn = sqlite3.connect(DBNAME) cur = conn.cursor() sql = "INSERT INTO bme(dt,temp,hum,press) VALUES(datetime('now', '+9 hours'),?,?,?)" cur.execute(sql,(values[0],values[1],values[2])) conn.commit() conn.close() def signal_handler(sig, handler): sys.exit() # テーブル作成 if not os.path.exists(DBNAME): conn = sqlite3.connect(DBNAME) cur = conn.cursor() cur.execute("CREATE TABLE bme(id INTEGER PRIMARY KEY AUTOINCREMENT, dt TEXT,temp REAL, hum REAL, press REAL)") conn.commit() conn.close() # BME280 i2c = smbus2.SMBus(BUS_NO) bme280.load_calibration_params(i2c, BME280_ADDR) # signal signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) # メインループ while True: data = bme280.sample(i2c, BME280_ADDR) store_values([data.temperature,data.humidity,data.pressure ]) time.sleep(60) |
著者:平田 千浩
「Red Hat Ansible Automation Platform」は、OSS のAnsibleとAWXを基とした企業向け自動化の基盤です。RHELを含むさまざまなOS、さまざまなアプリケーション、さまざまな機器に対応し、局所的な作業からバージョン管理システムと連携した構成管理までを自動化できます。
シェルスクリプトマガジン Vol.66は以下のリンク先でご購入できます。
図2 インベントリファイルの例(hosts)
1 2 3 4 5 6 7 8 9 10 |
[all:vars] ansible_ssh_private_key_file=/home/student1/.ssh/aws-private.pem [ios] ios1 ansible_host=XX.XX.XX.XX [ios:vars] ansible_user=ec2-user ansible_network_os=ios ansible_connection=network_cli |
図3 Cisco IOS向けの簡単なPlaybookの例(snmp.yml)
1 2 3 4 5 6 7 8 9 10 11 |
--- - name: snmp ro / rw string configuration hosts: ios gather_facts: no tasks: - name: ensure that the desired snmp strings are present ios_config: commands: - snmp-server community ansible-public RO - snmp-server community ansible-private RW |
図6 Arista EOSを追加したインベントリファイル例(hosts)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
[all:vars] ansible_ssh_private_key_file=/home/student1/.ssh/aws-private.pem [ios] ios1 ansible_host=XX.XX.XX.XX [ios:vars] ansible_user=ec2-user ansible_network_os=ios ansible_connection=network_cli [eos] eos1 ansible_host=YY.YY.YY.YY [eos:vars] ansible_user=ec2-user ansible_network_os=eos ansible_connection=network_cli ansible_become=true ansible_become_method=enable [control] ansible ansible_host=AA.AA.AA.AA ansible_user=student1 ansible_password=PASSWORD |
図7 Arista EOSのコンフィグをバックアップするPlaybookの例(eos_backup.yml)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
--- - name: retrieve router configurations hosts: eos gather_facts: no tasks: - name: BACKUP THE CONFIG eos_config: backup: yes register: config_output - name: Save THE CONFIG vars: ansible_connection: ssh copy: src: "{{config_output.backup_path}}" dest: "/backup/{{inventory_hostname}}" delegate_to: ansible become: yes |
図10 CISCO IOSのコンフィグをバックアップするPlaybookの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
- name: retrieve router configurations hosts: ios gather_facts: no tasks: - name: BACKUP THE CONFIG ios_config: backup: yes register: config_output - name: REMOVE NON CONFIG LINES - REGEXP lineinfile: path: "{{config_output.backup_path}}" line: "Building configuration..." state: absent - name: Save THE CONFIG vars: ansible_connection: ssh copy: src: "{{config_output.backup_path}}" dest: "/backup/{{inventory_hostname}}" delegate_to: ansible become: yes |
図11 保存したArista EOSのコンフィグからリストアするPlaybookの例(eos_restore.yml)
1 2 3 4 5 6 7 8 9 10 |
--- - name: Restore the EOS Config hosts: eos gather_facts: no tasks: - name: RESTORE THE CONFIG eos_config: replace: config src: "/backup/{{inventory_hostname}}" |
図12 保存したCisco IOSのコンフィグからリストアするPlaybookの例
1 2 3 4 5 6 7 8 9 |
--- - name: Restore the IOS Config hosts: ios gather_facts: no tasks: - name: RESTORE THE CONFIG ios_config: src: "/backup/{{inventory_hostname}}" |
著者:飯尾 淳
計量社会学や数理社会学という学問分野があります。人々の行動や社会活動から生み出される多様なデータを定量的に分析することによって、その背景となる社会的な構造や原理を見いだそうという社会学です。筆者はそれらの専門家ではありませんが、社会情報学の文脈で似たような研究に従事しています。今回は、筆者らの研究成果の一つであるTwitterのトレンド分析について、その概要を紹介します。
シェルスクリプトマガジン Vol.66は以下のリンク先でご購入できます。
図6 トレンドのキーワードを取得するPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#!/usr/bin/env python from twitter import * woeid = 23424856 # Japan CK = 'ADD_YOUR_KEY_HERE' # Consumer Key CS = 'ADD_YOUR_KEY_SECRET_HERE' # Consumer Key Secret AT = 'ADD_YOUR_TOKEN_HERE' # Access Token AS = 'ADD_YOUR_TOKEN_SECRET_HERE' # Accesss Token Secert twitter = Twitter(auth = OAuth(AT,AS,CK,CS)) results = twitter.trends.place(_id = woeid, exclude="hashtags") for location in results: for trend in location["trends"]: print (trend["name"]) |
著者:しょっさん
ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならないでしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。第6回は、アプリケーションの開発やテストを実施する環境について詳しく考えます。
シェルスクリプトマガジン Vol.66は以下のリンク先でご購入できます。
図2 モックのコード(swagger.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 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 |
{ "openapi" : "3.0.0", "servers" : [ { "description" : "Expense Reporter Sample API", "url" : "https://virtserver.swaggerhub.com/sho7650/ExpenseMockServices/0.5.0" } ], "info" : { "description" : "シェルスクリプトマガジン用サンプル", "version" : "0.5.0", "title" : "Expense API", "contact" : { "email" : "sho@oshiire.to" }, "license" : { "name" : "Apache 2.0", "url" : "http://www.apache.org/licenses/LICENSE-2.0.html" } }, "tags" : [ { "name" : "users", "description" : "一般利用者" }, { "name" : "approvers", "description" : "承認者" } ], "paths" : { (略) "/payment" : { "get" : { "tags" : [ "approvers" ], "summary" : "支払い待ち一覧", "operationId" : "findPayments", "description" : "一般利用者からの請求一覧を表示", "security" : [ { "bearerAuth" : [ ] } ], (略) "responses" : { "200" : { "description" : "success", "content" : { "application/json" : { "schema" : { "type" : "array", "items" : { "$ref" : "#/components/schemas/ExpenseItem" } } } } }, "400" : { "description" : "bad input parameter" } } } } }, "components" : { "securitySchemes" : { "bearerAuth" : { "type" : "http", "scheme" : "bearer", "bearerFormat" : "JWT" } }, "schemas" : { "id" : { "type" : "integer", "format" : "int32", "example" : 120 }, (略) } } } |
著者:檜垣 龍德
今回は、Pythonとそのライブラリである「slacker」「python-crontab」を用いて、チャットサービス「Slack」にメッセージを自動投稿するbo(t Slackbot)を開発する方法を解説します。例として開発するのは、参加者からメッセージが投稿されるまで「Get Up !!」というメッセージを連続投稿するbotです。
シェルスクリプトマガジン Vol.66は以下のリンク先でご購入できます。
図7 「.env」ファイルに記述する内容
1 2 |
ACCESS_TOKEN=アクセストークン CHANNEL_ID=チャンネルID |
図8 「settings.py」ファイルに記述する内容
1 2 3 4 5 6 7 8 9 |
import os from os.path import join, dirname from dotenv import load_dotenv dotenv_path = join(dirname(__file__), '.env') load_dotenv(dotenv_path) ACCESS_TOKEN = os.environ.get("ACCESS_TOKEN") CHANNEL_ID = os.environ.get("CHANNEL_ID") |
図9 「main.py」ファイルに記述する内容
1 2 3 4 5 |
from slacker import Slacker import settings slack = Slacker(settings.ACCESS_TOKEN) slack.chat.post_message("チャンネル名", "Get Up !!") |
図11 「cron.py」ファイルに記述する内容
1 2 3 4 5 6 7 |
from crontab import CronTab cron = CronTab() job = cron.new('python main.py') job.setall('00 00 * * *') for result in cron.run_scheduler(): print("Done Job" |
図12 書き換えたmain.pyファイルの内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
from slacker import Slacker from time import sleep import settings slack = Slacker(settings.ACCESS_TOKEN) channel_id = settings.CHANNEL_ID slack.chat.post_message("チャンネル名", "Get Up !!") history = slack.channels.history(channel_id) start_ts = history.body["messages"][0]["ts"] while True: sleep(1) history = slack.channels.history(channel_id, count=10000, oldest=start_ts) not_bot_users = [message["user"] for message in history.body["messages"] if "bot_id" not in message] if len(not_bot_users): break else: slack.chat.post_message("チャンネル名", "Get Up !!") |
図14 書き換えたmain.pyファイルに追記する内容
1 2 3 4 5 |
history = slack.channels.history(channel_id, count=10000, oldest=start_ts) ts_list = [message["ts"] for message in history.body["messages"] if 'bot_id' in message] ts_list.append(start_ts) for ts in ts_list: slack.chat.delete(channel_id, ts=ts, as_user=True) |
著者:石崎 博之、掛本 健一
ユニケージ開発手法によるシステムの設計・実装・保守を数年以上経験したメンバーが、業務システム構築のための「ユニケージ開発手法」を解説します。我々の経験に基づきながら、具体的なシステム構築の流れを示していきます。第1回は、ユニケージ開発手法の特徴をつかむです。
シェルスクリプトマガジン Vol.66は以下のリンク先でご購入できます。
図2 ユニケージのシェルスクリプトの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#!/bin/bash join0 key=1 MASTER TRAN | self 2 3 4 5 | msort key=1/2 | sm2 1 2 3 4 | sm4 1 1 2 2 3 4 | self 1 2 3 4 | sm5 1 3 4 4 | map num=1 | sed 's/A/Sales/g' | sed 's/B/Profit/g | keta 4 6@NF-1 | comma 3/NF | cat header - | tocsv > result exit 0 |
著者:大津 真
本連載ではシェルスクリプトの書き方をやさしく紹介します。対象とするシェルは、多くのLinuxディストリビューションが標準シェルとして採用する「Bash」です。第2回では、変数の概要と、シェルスクリプト内でコマンドライン引数を扱う方法について解説します。
シェルスクリプトマガジン Vol.66は以下のリンク先でご購入できます。
図4 シェルスクリプト「 permtest1.sh 」の内容
1 2 3 4 5 6 |
#!/bin/bash echo " スクリプト名: $0" echo " 引数の数: $#" echo " 引数1: $1" echo " 引数2: $2" echo " 引数3: $3" |
図5 シェルスクリプト「 colon_to_comma.sh 」の内容
1 2 3 4 |
#!/bin/bash fname=$1 cp "$fname" "${fname}~" tr ":" "," < "${fname}~" > "$fname" |
図6 加工対象のファイル「 customer.txt 」の内容
1 2 3 |
010: 白戸二郎: 男:39: 北海道 011: 小山田花子: 女:24: 福岡 012: 三村美子: 女:29: 東京 |
筆者:大津 真
本連載ではシェルスクリプトの書き方をやさしく紹介します。対象とす
るシェルは、多くのLinuxディストリビューションが標準シェルとして
採用する「Bash」です。第1回目となる今回は、シェルスクリプトの概要
と作成、実行の方法を解説します。
シェルスクリプトマガジン Vol.65は以下のリンク先でご購入できます。
図4 ホスト名と日時を表示するシェルスクリプト「 today1.sh 」
1 2 |
hostname date '+ 今日は%Y 年%m 月%d 日(%a) です' |
図5 シバンを追加したシェルスクリプト「 today2.sh 」
1 2 3 |
#!/bin/bash hostname date '+ 今日は%Y 年%m 月%d 日(%a) です' |
図6 コメントを追加したシェルスクリプト「 today3.sh 」
1 2 3 4 5 |
#!/bin/bash # ホスト名と今日の日時を表示する # ver.1.0 2020/2/1 hostname date '+ 今日は%Y 年%m 月%d 日(%a) です' |
図8 コマンド置換を使用したシェルスクリプト「today4.sh」
1 2 3 4 5 |
#!/bin/bash # ホスト名と今日の日時を表示する # ver.1.1 2020/2/1 echo " ホスト名は$(hostname) です" date '+ 今日は%Y 年%m 月%d 日(%a) です' |
筆者:米田 聡
シェルスクリプトマガジンでは、小型コンピュータボード「Raspberry Pi」(ラズパイ)は2種類の拡張ボードを制作しています。第12回は、最初に作成した「ラズパイ入門ボード」に「ロータリエンコーダ」を接続し、オーディオのボリュームのようなコントローラを実装します。
シェルスクリプトマガジン Vol.65は以下のリンク先でご購入できます。
図5 ロータリエンコーダ用のクラスファイル(rotary.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 |
import RPi.GPIO as GPIO import time P_A = 21 P_B = 25 class rotaryenc(): def __init__(self, phase_a = P_A, phase_b = P_B): self.phase_a = phase_a self.phase_b = phase_b GPIO.setmode(GPIO.BCM) GPIO.setup(phase_a, GPIO.IN) GPIO.setup(phase_b, GPIO.IN) GPIO.add_event_detect(phase_a, GPIO.RISING, callback=self.__changeStatus) self.__callback = None self.prev_forward_time = 0 self.prev_backward_time = 0 def __changeStatus(self, gpio): pa = GPIO.input(self.phase_a) pb = GPIO.input(self.phase_b) if pa == GPIO.LOW: # チャタリング等 return value = 0 current = time.time() if pb == GPIO.LOW: # 時計回り value = 1 if self.prev_forward_time != 0: if (current - self.prev_forward_time) < 0.1: # 100ms以内 value = 10 self.prev_forward_time = current self.prev_backward_time = 0 if pb == GPIO.HIGH: value = -1 if self.prev_backward_time != 0: if (current - self.prev_backward_time) < 0.1: # 100ms以内 value = -10 self.prev_forward_time = 0 self.prev_backward_time = current if value != 0 and self.__callback is not None: self.__callback(value) def registerCallback(self, c): self.__callback = c return def unregisterCallback(self): self.__callback = None def __del__(self): GPIO.cleanup() |
図6 テスト用のサンプルプログラム(test.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
from rotary import rotaryenc from EbOled import EbOled import time # OLED oled = EbOled() oled.begin() oled.clear() oled.display() value = 0 def callback(r): global value value += r oled.drawString('value=' + str(value)) oled.display() re = rotaryenc() re.registerCallback(callback) try: time.sleep(120) except KeyboardInterrupt: pass |
筆者:松田 絵里奈
「Red Hat Decision Manager」は、OSSの「Drools」がベースのルールエンジンです。同製品で業務ロジックを実装することで、アプリから業務ロジックを分離でき、メンテナンスが容易になります。また、複雑なロジックでも簡単で分かりやすい形式で記述できます。
シェルスクリプトマガジン Vol.65は以下のリンク先でご購入できます。
図11 pom.xml(抜粋)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
(略) <dependencies> <dependency> <groupId>org.kie</groupId> <artifactId>kie-api</artifactId> </dependency> <dependency> <groupId>org.drools</groupId> <artifactId>drools-core</artifactId> </dependency> <dependency> <groupId>org.drools</groupId> <artifactId>drools-decisiontables</artifactId> </dependency> </dependencies> (略) |
図12 過去利用状況ファクト(過去利用状況.java)
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 |
package com.example.rhdm; public class 過去利用状況 { private String ユーザーID; private int 過去利用回数; private int 過去利用額計; public String get ユーザーID() { return ユーザーID; } public void set ユーザーID(String ユーザーid) { ユーザーID = ユーザーid; } public int get 過去利用回数() { return 過去利用回数; } public void set 過去利用回数(int 過去利用回数) { this.過去利用回数 = 過去利用回数; } public int get 過去利用額計() { return 過去利用額計; } public void set 過去利用額計(int 過去利用額計) { this.過去利用額計 = 過去利用額計; } } |
図13 今回利用ファクト(今回利用.java)
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 |
package com.example.rhdm; public class 今回利用 { private String ユーザーID; private int 利用額; private String 割引ランク; private int 割引率; public String get ユーザーID() { return ユーザーID; } public void set ユーザーID(String ユーザーid) { ユーザーID = ユーザーid; } public int get 利用額() { return 利用額; } public void set 利用額(int 利用額) { this.利用額 = 利用額; } public String get 割引ランク() { return 割引ランク; } public void set 割引ランク(String 割引ランク) { this.割引ランク = 割引ランク; } public int get 割引率() { return 割引率; } public void set 割引率(int 割引率) { this.割引率 = 割引率; } } |
図14 最初の/src/main/resources/割引決定.drlファイル
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package com.example.rhdm import com.example.rhdm.* dialect "java" rule "利用回数2回未満、利用額1万未満" when $過去利用:過去利用状況(過去利用回数 < 2, 過去利用額計 < 10000) $今回利用:今回利用(ユーザーID == $過去利用.ユーザーID) then // modify($今回利用) {set 割引ランク("X")} $今回利用.set 割引ランク("X"); System.out.println($今回利用.get ユーザーID() + "の割引ランクは" + $今回利用.get 割引ランク() + "です"); end |
図16 ルールを動かすためのJavaクラス
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 |
package com.example.rhdm; import org.kie.api.KieServices; import org.kie.api.runtime.KieContainer; import org.kie.api.runtime.KieSession; public class DrlTest { public static final void main(String[] args) { try { KieServices ks = KieServices.Factory.get(); KieContainer kContainer = ks.getKieClasspathContainer(); // 実行対象ルールを指定 KieSession kSession = kContainer.newKieSession("ksession-rules"); // ファクトを生成 過去利用状況 fact1 = new 過去利用状況(); fact1.setユーザーID("A001"); fact1.set過去利用回数(1); fact1.set過去利用額計(5000); 今回利用 fact2 = new 今回利用(); fact2.setユーザーID("A001"); // ルールエンジンにファクトをインサート kSession.insert(fact1); kSession.insert(fact2); // ルール実行 kSession.fireAllRules(); } catch (Throwable t) { t.printStackTrace(); } } } |
図17 追加するルール
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 |
rule "利用回数2回未満、利用額1万以上" when $過去利用:過去利用状況(過去利用回数 < 2, 過去利用額計 >= 10000) $今回利用:今回利用(ユーザーID == $過去利用.ユーザーID) then $今回利用.set割引ランク("E"); System.out.println($今回利用.getユーザーID() + "の割引ランクは"+ $今回利用.get割引ランク() + "です"); end rule "利用回数2回以上5回未満、利用額1万未満" when $過去利用:過去利用状況(過去利用回数 >= 2, 過去利用回数 < 5, 過去利用額計 < 10000) $今回利用:今回利用(ユーザーID == $過去利用.ユーザーID) then $今回利用.set割引ランク("E"); System.out.println($今回利用.getユーザーID() + "の割引ランクは"+ $今回利用.get割引ランク() + "です"); end rule "利用回数2回以上5回未満、利用額1万以上" when $過去利用:過去利用状況(過去利用回数 >= 2, 過去利用回数 < 5, 過去利用額計 >= 10000) $今回利用:今回利用(ユーザーID == $過去利用.ユーザーID) then $今回利用.set割引ランク("D"); System.out.println($今回利用.getユーザーID() + "の割引ランクは"+ $今回利用.get割引ランク() + "です"); end |
図18 さらに追加するルール
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
rule "割引ランクX割引率設定" when $今回利用:今回利用(割引ランク == "X") then $今回利用.set割引率(0); System.out.println($今回利用.getユーザーID() + "の割引率は"+ $今回利用.get割引率() + "です"); end rule "割引ランクE割引率設定" when $今回利用:今回利用(割引ランク == "E") then $今回利用.set割引率(3); System.out.println($今回利用.getユーザーID() + "の割引率は"+ $今回利用.get割引率() + "です"); end rule "割引ランクD割引率設定" when $今回利用:今回利用(割引ランク == "D") then $今回利用.set割引率(5); System.out.println($今回利用.getユーザーID() + "の割引率は"+ $今回利用.get割引率() + "です"); end |
筆者:飯尾 淳
今回はデータ分析から少し離れてフラクタル図形について語りましょ
う。例として、縦棒(バーティカルバー)と横棒が縦横無尽に組み合わさっ た図形であるヒルベルト曲線を考えます。ヒルベルト曲線はフラクタル図形の一つで、自己相似性という特徴を持ちます。本記事ではこれを描画するプログラムを紹介します。シンプルなプログラムでこんなにも複雑な図形を描くことができるのかと驚くはずです。
シェルスクリプトマガジン Vol.65は以下のリンク先でご購入できます。
図4 図を出力するためのHTMLコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<!DOCTYPE html> <html> <head> <title>Hilbert Curve</title> <meta charset="utf-8"> <meta name="description" content="Drawing Hilbert curve"> <meta name="author" content="Jun Iio"> <meta name="viewport" content="width=device-width,initial-scale=1"> </head> <body> <canvas id="theCanvas" width="1000" height="1000"></canvas> <script src="hilbert.js"></script> </body> </html> |
図5 1次ヒルベルト曲線を描くJavaScriptコード
1 2 3 4 5 6 7 8 9 10 11 12 |
// the canvas and its graphic context var cs = document.getElementById('theCanvas'); var ctx = cs.getContext('2d'); ctx.lineWidth = 5; ctx.strokeStyle = 'black'; ctx.beginPath(); [ [0.25, 0.25], [0.25, 0.75], [0.75, 0.75], [0.75, 0.25] ] .map(p => [p[0]*cs.width, p[1]*cs.height]) .forEach(p => { ctx.lineTo(p[0],p[1]); }); ctx.stroke(); |
図7 n次のヒルベルト曲線を描く手続き
1 2 3 4 5 6 7 8 9 |
function hilbert(n, R) { if (n > 1) { hilbert(n-1, R’=f(R,"左上")); hilbert(n-1, R’=f(R,"左下")); hilbert(n-1, R’=f(R,"右下")); hilbert(n-1, R’=f(R,"右上")); } else { (Rで示される座標上で1次のヒルベルト曲線を描くコード) } |
図10 座標変換ルールを格納した 配列を定義するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var tm = [ // tm[0] [ [ 0, 1/2, 0], [ 1/2, 0, 0], [ 0, 0, 1] ], // tm[1] [ [ 1/2, 0, 0], [ 0, 1/2, 1/2], [ 0, 0, 1] ], // tm[2] [ [ 1/2, 0, 1/2], [ 0, 1/2, 1/2], [ 0, 0, 1] ], // tm[3] [ [ 0, -1/2, 1], [-1/2, 0, 1/2], [ 0, 0, 1] ] ] |
図11 1次から8次までのヒルベルト曲線を描くコード
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 |
// the canvas and its graphic context var cs = document.getElementById('theCanvas'); var ctx = cs.getContext('2d'); // line style var colors = [ 'gray', 'navy', 'purple', 'brown', 'red', 'orange', 'yellowgreen', 'skyblue' ]; var widths = [5, 4, 3, 2, 2, 1, 1, 0.5 ]; var tm = [ [ [ 0, 1/2, 0], [ 1/2, 0, 0], [0, 0, 1] ], [ [1/2, 0, 0], [ 0, 1/2, 1/2], [0, 0, 1] ], [ [1/2, 0, 1/2], [ 0, 1/2, 1/2], [0, 0, 1] ], [ [ 0, -1/2, 1], [-1/2, 0, 1/2], [0, 0, 1] ] ]; var E = [ [ 1, 0, 0], [0, 1, 0], [0, 0, 1] ]; function affine_transform(m, p) { return [ m[0][0] * p[0] + m[0][1] * p[1] + m[0][2], m[1][0] * p[0] + m[1][1] * p[1] + m[1][2] ]; } function mat_mul(m0, m1) { return [ [m0[0][0]*m1[0][0]+m0[0][1]*m1[1][0]+m0[0][2]*m1[2][0], m0[0][0]*m1[0][1]+m0[0][1]*m1[1][1]+m0[0][2]*m1[2][1], m0[0][0]*m1[0][2]+m0[0][1]*m1[1][2]+m0[0][2]*m1[2][2]], [m0[1][0]*m1[0][0]+m0[1][1]*m1[1][0]+m0[1][2]*m1[2][0], m0[1][0]*m1[0][1]+m0[1][1]*m1[1][1]+m0[1][2]*m1[2][1], m0[1][0]*m1[0][2]+m0[1][1]*m1[1][2]+m0[1][2]*m1[2][2]], [m0[2][0]*m1[0][0]+m0[2][1]*m1[1][0]+m0[2][2]*m1[2][0], m0[2][0]*m1[0][1]+m0[2][1]*m1[1][1]+m0[2][2]*m1[2][1], m0[2][0]*m1[0][2]+m0[2][1]*m1[1][2]+m0[2][2]*m1[2][2]] ]; } function hilbert(n, m) { if (n > 0) { tm.forEach(mm => { hilbert(n-1, mat_mul(m, mm)); }); } else { [ [0.25, 0.25], [0.25, 0.75], [0.75, 0.75], [0.75, 0.25] ] .map(p => affine_transform(m, p)) .map(p => [p[0]*cs.width, p[1]*cs.height]) .forEach(p => { ctx.lineTo(p[0],p[1]); }); } } function drawHilbert(i) { ctx.beginPath(); ctx.lineWidth = widths[i]; ctx.strokeStyle = colors[i]; hilbert(i, E); ctx.stroke(); } for(i=0; i<colors.length; i++) { drawHilbert(i); } |
筆者:重松 亜夢
はじめまして!香川大学の重松亜夢です。2019年秋にSLPの所長を引き継ぎま
した。SLPの最近の主な活動はチーム開発です。2019年12月には部員が最近の活動をブログに投稿し、Advent Calendarを作成しました。また、2020年1月には餅つきで親交を深めました。
今回は、Webアプリケーションに認証機能を実装します。具体的には、米Google社の「Google Cloud Platform」のAPIを使って「Googleでログイン」を実装します。
シェルスクリプトマガジン Vol.65は以下のリンク先でご購入できます。
図9 「.env」ファイルに記述する内容
1 2 3 |
SAMPLE_HOST="localhost:1323" GOOGLE_CLIENT_ID="クライアントID" GOOGLE_CLIENT_SECRET="クライアントシークレット" |
図10 「main.go」ファイルに記述する内容
1 2 3 4 5 6 7 |
package main import ( route "example.com/user_name/sample-app/route" ) func main() { route.Echo.Logger.Fatal(route.Echo.Start(":1323")) } |
図11 「router.go」ファイルに記述する内容
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 |
package route import ( "fmt" "log" "os" "example.com/user_name/sample-app/handler" "github.com/joho/godotenv" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/stretchr/gomniauth" "github.com/stretchr/gomniauth/providers/google" "github.com/stretchr/signature" ) var Echo *echo.Echo func init() { e := echo.New() err := setupOAuth() if err != nil { log.Fatal("Error loading .env file") } e.Use(middleware.Logger()) e.GET("/auth/login/:provider", handler.LoginHandler) e.GET("/auth/callback/:provider", handler.CallbackHandler) Echo = e } |
図12 「router.go」ファイルに追記する内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
func setupOAuth() error { err := godotenv.Load() if err != nil { return err } host := os.Getenv("SAMPLE_HOST") googleCallbackURL := fmt.Sprintf("http://%s/auth/callback/google", host) gomniauth.SetSecurityKey(signature.RandomKey(64)) gomniauth.WithProviders( google.New( os.Getenv("GOOGLE_CLIENT_ID"), os.Getenv("GOOGLE_CLIENT_SECRET"), googleCallbackURL, ), ) return nil } |
図13 「sesseion.go」ファイルに記述する内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package handler import ( "net/http" "github.com/labstack/echo/v4" "github.com/stretchr/gomniauth" "github.com/stretchr/objx" ) func LoginHandler(c echo.Context) error { provider, err := gomniauth.Provider(c.Param("provider")) if err != nil { return err } authURL, err := provider.GetBeginAuthURL(nil, nil) if err != nil { return err } return c.Redirect(http.StatusTemporaryRedirect, authURL) } |
図13 「sesseion.go」ファイルに記述する内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package handler import ( "net/http" "github.com/labstack/echo/v4" "github.com/stretchr/gomniauth" "github.com/stretchr/objx" ) func LoginHandler(c echo.Context) error { provider, err := gomniauth.Provider(c.Param("provider")) if err != nil { return err } authURL, err := provider.GetBeginAuthURL(nil, nil) if err != nil { return err } return c.Redirect(http.StatusTemporaryRedirect, authURL) } |
図14 「sesseion.go」ファイルに追記する内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
func CallbackHandler(c echo.Context) error { provider, err := gomniauth.Provider(c.Param("provider")) if err != nil { return err } omap, err := objx.FromURLQuery(c.QueryString()) if err != nil { return err } _, err = provider.CompleteAuth(omap) if err != nil { return err } return c.String(http.StatusOK, "Login Success!") } |
図16 「sesseion.go」ファイルのCallbackHandler関数の変更コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
creds, err := provider.CompleteAuth(omap) if err != nil { return err } user, err := provider.GetUser(creds) if err != nil { return err } authCookieValue := objx.New(map[string]interface{}{ "name": user.Name(), "email": user.Email(), "avatarURL": user.AvatarURL(), }).MustBase64() cookie := &http.Cookie{ Name: "auth", Value: authCookieValue, Path: "/", Expires: time.Now().Add(24 * time.Hour), } c.SetCookie(cookie) return c.Redirect(http.StatusTemporaryRedirect, "/") |
図17 「router.go」ファイルに追記する内容
1 2 3 4 5 6 7 8 |
type TemplateRenderer struct { templates *template.Template } func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error { return t.templates.ExecuteTemplate(w, name, data) } |
図18 「router.go」ファイルのinit関数に挿入する内容
1 2 3 4 5 |
renderer := &TemplateRenderer{ templates: template.Must(template.ParseGlob("templates/*.html")), } e.Renderer = renderer e.GET("/", handler.MainPageHandler) |
図19 「handler.go」ファイルに記述する内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package handler import ( "net/http" "github.com/labstack/echo/v4" "github.com/stretchr/objx" ) func MainPageHandler(c echo.Context) error { auth, err := c.Cookie("auth") if err != nil { return c.Render(http.StatusOK, "welcome", map[string]interface{}{ "title": "Welcome", }) } userData := objx.MustFromBase64(auth.Value) return c.Render(http.StatusOK, "top", map[string]interface{}{ "name": userData["name"], "email": userData["email"], "avatarURL": userData["avatarURL"], "title": "TopPage", }) } |
図20 「index.html」ファイルに記述する内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{{define "top"}} {{template "head" .}} <img src={{.avatarURL}} width="10%"> <h2>Hello, {{.name}}</h2> Your Email : {{.email}} {{end}} {{define "welcome"}} {{template "head" .}} <h1>ようこそ</h1> <ul> <li> <a href="/auth/login/google">Googleでログイン</a> </li> </ul> {{end}} |
図21 「base.html」ファイルに記述する内容
1 2 3 |
{{define "head"}} <title>{{ .title }} / Sample-App</title> {{end}} |
図23 「router.go」ファイルのinit関数定義部分に挿入する内容
1 2 |
e.Pre(middleware.RemoveTrailingSlash()) e.Group("", authCheckMiddleware()) |
図24 「router.go」ファイルの末尾に追記する内容
1 2 3 4 5 6 7 8 9 10 11 |
func authCheckMiddleware() echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { _, err := c.Cookie("auth") if err != nil { return c.Redirect(http.StatusTemporaryRedirect, "/") } return next(c) } } } |
図25 認証後だけアクセスできるルートを設定する例
1 2 |
authCheck := e.Group("", authCheckMiddleware()) authCheck.GET("/fuga", handler.SamplePage) |
004 レポート WebブラウザMicrosoft Edgeの新版
005 レポート 仮想マシン構築・運用ソフト「Multipass」
006 NEWS FLASH
008 特集1 知っておきたいDebian/やまねひでき
026 特集2 NGINX Plus徹底解説/髙田知典 コード掲載
042 特別企画 詳説 OpenPOWER/OpenCAPI/河井裕
050 ラズパイ入門ボードで学ぶ 電子回路の制御/米田聡 コード掲載
054 レッドハットのプロダクト/松田絵里奈 コード掲載
064 姐のNOGYO
066 漢のUNIX/後藤大地
072 円滑コミュニケーションが世界を救う!/濱口誠一
074 バーティカルバーの極意/飯尾淳 コード掲載
080 中小企業手作りIT化奮戦記/菅雄一
084 やっつける/桑原滝弥、イケヤシロウ
086 Webアプリの正しい作り方/しょっさん
096 法林浩之のFIGHTING TALKS/法林浩之
098 香川大学SLPからお届け!/重松亜夢 コード掲載
106 MySQL Shellを使おう/梶山隆輔
114 シェルスクリプトの書き方入門/大津真 コード掲載
120 Techパズル/gori.sh
122 コラム「社訓」/シェル魔人
著者:髙田 知典
「NGINX」(エンジンエックス)は、人気の高いWebサーバーソフトウエ
アです。「NGINX Plus」は、オープンソース版のNGINXにさまざまな
機能拡張を施した商用版です。追加された拡張機能を利用すること
で、システムの可用性と堅牢性の向上や、運用の簡素化を実現できま
す。本特集では、NGINX Plusの機能や試用方法などを紹介します。
シェルスクリプトマガジン Vol.65は以下のリンク先でご購入できます。
図2 アクティブヘルスチェックの設定例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
upstream my_upstream { zone my_upstream 64k; server server1.example.com slow_start=30s; server server2.example.com slow_start=30s; } server { location / { proxy_pass http://my_upstream; health_check interval=5s uri=/test.php match=statusok; } } match statusok { status 200; header Content-Type = text/html; body ~ "Server[0-9]+ is alive"; } |
図3 Sticky cookie の設定例
1 2 3 4 5 |
upstream my_upstream { server server1.example.com; server server2.example.com; sticky cookie srv_id expires=1h; } |
図4 Sticky route の設定例
1 2 3 4 5 6 7 8 9 10 11 |
map $cookie_jsessionid $route_cookie { ~.+\.(?P<route>\w+)$ $route; } map $request_uri $route_uri { ~jsessionid=.+\.(?P<route>\w+)$ $route; } upstream backend { server backend1.example.com route=a; server backend2.example.com route=b; sticky route $route_cookie $route_uri; } |
図5 Sticky learnの設定例
1 2 3 4 5 6 7 8 9 |
upstream backend { server backend1.example.com; server backend2.example.com; sticky learn create=$upstream_cookie_examplecookie lookup=$cookie_examplecookie zone=client_sessions:1m timeout=1h; } |
図6 DNS名をAレコード情報を使って解決する設定例
1 2 3 4 5 6 7 8 9 10 |
resolver 10.0.0.2 valid=10s; upstream backends { zone backends 64k; server backends.example.com:8080 resolve; } server { location / { proxy_pass http://backends; } } |
図7 DNS名をSRVレコード情報を使って解決する設定例
1 2 3 4 5 6 7 8 9 10 |
resolver 10.0.0.2 valid=10s; upstream backends { zone backends 64k; server backends.example.com service=_http._tcp resolve; } server { location / { proxy_pass http://backends; } } |
図9 コンテンツキャッシュのパージ設定例
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 |
http { (略) proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=mycache:10m purger=on; map $request_method $purge_method { PURGE 1; default 0; } server { listen 80; server_name www.example.com; location / { proxy_pass https://localhost:8002; proxy_cache mycache; proxy_cache_purge $purge_method; } } geo $purge_allowed { default 0; 10.0.0.1 1; 192.168.0.0/24 1; } map $request_method $purge_method { PURGE $purge_allowed; default 0; } } |
図15 nginx-sync.confの設定例
1 2 3 |
NODES="node2.example.com node3.example.com node4.example.com" CONFPATHS="/etc/nginx/nginx.conf /etc/nginx/conf.d" EXCLUDE="default.conf" |
図16 NGINX Plus APIの設定例
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 |
http { (略) # Configuration of the server group upstream appservers { # 共有メモリーにサーバーグループ構成を保存するようにゾーンを設定 zone appservers 64k; server appserv1.example.com weight=5; server appserv2.example.com:8080 fail_timeout=5s; server reserve1.example.com:8080 backup; server reserve2.example.com:8080 backup; } server { location / { proxy_pass http://appservers; health_check; } location /api { # GET以外のメソッドをBasic認証で制限 limit_except GET { auth_basic "NGINX Plus API"; auth_basic_user_file /etc/nginx/.htpasswd; } # APIを書き込みモードで動作させる api write=on; # アクセス元をローカルホストのみに制限 allow 127.0.0.1; deny all; } } } |
図18 stateファイルの設定を追加した例
1 2 3 4 5 6 7 8 9 10 11 12 |
http { (略) # Configuration of the server group upstream appservers { zone appservers 64k; state /var/lib/nginx/state/appservers.conf; # server appserv1.example.com weight=5; # server appserv2.example.com:8080 fail_timeout=5s; # server reserve1.example.com:8080 backup; # server reserve2.example.com:8080 backup; } } |
図19 キーバリューストアの設定例
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 |
http { # キーバリューストアの定義 keyval_zone zone=blacklist:1M state=/tmp/blacklist.json; keyval $remote_addr $black_list zone=blacklist; server { listen 80; location / { root /usr/share/nginx/html; if ($black_list = 0) { return 403; } } location /api { # GET以外のメソッドをベーシック認証で制限 limit_except GET { auth_basic "NGINX Plus API"; auth_basic_user_file /etc/nginx/.htpasswd; } # APIを書き込みモードで動作させる api write=on; #アクセス元をローカルホストのみに制限 allow 127.0.0.1; deny all; } } } |
p.31にあるUSPファームのサイトの「http://www.uspeace.jp/」は「https://farm.usp-lab.com/」の誤りです。お詫びして訂正いたします。
情報は随時更新致します。
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 コラム「新しい風が吹いてくる」/シェル魔人
著者 :河野 達也
最近、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) |
著者:清水赳
前回に引き続き、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 |
著者:しょっさん
ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならないでしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。第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' ] }); } }; |
著者:後藤 大地
今回も引き続き、プログラミング言語の「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("エラー")); } |
著者:米田 聡
シェルスクリプトマガジンでは、小型コンピュータボード「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 |
著者:清水 赳
こんにちは、香川大学修士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'] |
著者:飯尾 淳
本連載は、「縦棒」(バーティカルバー)をキーワードにデータ処理や効果的なアルゴリズムについて考えるものです。ここ何回かバーティカルバーから離れて自由なテーマで論じ過ぎていたような気がするので、今回は初心に戻ってヒストグラムを用いたデータ分析の議論を紹介してみることにしましょう。
テーマは入力効率の問題です。普段、皆さんがお使いのキーボードによるタイピングとスマートフォンのフリック入力、はたしてどちらが効率的に入力できるのでしょうか?
シェルスクリプトマガジン 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 }, }); } } |
著者:後藤 大地
Rustは、Mozilla Foundationが開発を支援している比較的新しいプログラミング言語だ。「マルチパラダイムシステムプログラミング言語」と呼ばれており、C/C++の代わりに利用できるプログラミング言語と言われている。C/C++のように見えるが、関数型プログラミングのパラダイムも織り込まれていて、かなり厳密なコーディングができるようになっている。本連載では、しばらく、このRustを取り上げていく。
シェルスクリプトマガジン Vol.63は以下のリンク先でご購入できます。
図20 Hello Worldのコード
1 2 3 |
fn main() { println!("Hello, world!"); } |
著者:菅 雄一
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> |
著者:しょっさん
ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならないでしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。第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" } } |
著者:岡田 健
ユニケージでは、小さな道具の「コマンド」をシェルスクリプトで組み合わせて、さまざまな業務システムを構築しています。本連載では、毎回あるテーマに従ってユニケージによるシェルスクリプトの記述例を分かりやすく紹介します。第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] } |
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)」の誤りです。お詫びして訂正いたします。
情報は随時更新致します。
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 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 |
著者:岡本 秀高
米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 } |
著者:石井 一夫
最終回は、次世代データサイエンス言語として注目されている「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"' |
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 コラム「平凡で地味な人生を幸せに送る」/シェル魔人
著者:坂東 勝也
ユニケージでは、小さな道具の「コマンド」をシェルスクリプトで組み合わせて、さまざまな業務システムを構築しています。本連載では、毎回あるテーマに従ってユニケージによるシェルスクリプトの記述例を分かりやすく紹介します。第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 |
著者:米田 聡
シェルスクリプトマガジンでは、小型コンピュータボード「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() |
著者:しょっさん
ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならない でしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。第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) |
シェルスクリプトマガジンとビット・トレード・ワンで共同制作した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)連載「ラズパイセンサーボードで学ぶ電子回路の制御」最終回
※関連記事掲載時に追加していきます。
※ラズパイ入門ボード向けソースコード集はこちら。
著者:しょっさん
ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならないのでしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。第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}`); }) |
著者:あかね
米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'です!' |
著者:麻生二郎
電子回路を触ってみたい、作ってみたい、制御してみたいと思ったときに、人気の小型コンピュータボード「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() |
著者:飯尾淳
本連載の第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の環境があれば、すぐに実行して試せるのでとても便利です。本連載では、役立ちそうなシェルスクリプトを紹介しながら、シェルスクリプトの書き方を説明していきます(隔週更新予定)。
なお、シェルスクリプトを開発・実行するには、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 コラム「ユニケージお作法のココロ」/シェル魔人
著者:岡本秀高
カンファレンス「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 |
著者:坂東勝也
ユニケージでは、小さな道具の「コマンド」をシェルスクリプトで組み合わせて、さまざまな業務システムを構築しています。本連載では、毎回あるテーマに従ってユニケージによるシェルスクリプトの記述例を分かりやすく紹介します。第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 |
著者:竹原一駿
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); }); }); }); |
著者:竹原 一駿
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; } |
シェルスクリプトマガジン 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 コラム「仕事座右の銘」/シェル魔人
著者:しょっさん
プログラミング言語「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); }); }); |
著者:石井 一夫
今回は、機械学習による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 |
著者:降籏洋行、西川公一朗
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]) |
著者:後藤大地
これまで、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} |
カテゴリー コード のアーカイブを表示しています。