004 レポート テキストエディタ「Vim 9.0」リリース コード掲載
005 レポート 「Raspberry Pi Zero 2 W」国内版発売 コード掲載
006 製品レビュー イヤホン「LinkBuds」
007 NEWS FLASH
008 特集1 仮想化ソフト「VirtualBox」 徹底活用/麻生二郎
020 特集2 FlutterでGUIアプリを開発しよう/三好文二郎 コード掲載
030 特集3 ユニケージ開発のシステム/松浦智之 コード掲載
042 特別企画 本格的なホームサーバーを構築 インターネット公開編/麻生二郎 コード掲載
051 Hello Nogyo!
052 Raspberry Piを100%活用しよう/米田聡 コード掲載
056 Pythonあれこれ/飯尾淳
064 マルウエア/桑原滝弥、イケヤシロウ
066 中小企業手作りIT化奮戦記/菅雄一
070 タイ語から分かる現地生活/つじみき
076 法林浩之のFIGHTING TALKS/法林浩之
078 香川大学SLPからお届け!/岩本和真 コード掲載
084 AWKでデジタル信号処理/斉藤博文 コード掲載
090 Bash入門/大津真
098 Techパズル/gori.sh
099 コラム「こうしてユニケージは生まれた」/シェル魔人
シェルスクリプトマガジンvol.79 Web掲載記事まとめ
レポート テキストエディタ「Vim 9.0」リリース(Vol.79掲載)
著者:末安 泰三
テキストエディタ「Vim」の最新版「Vim 9.0」が2022年6月28日に公開された。前版「Vim 8.2」の公開は2019年12月のため、約2年半ぶりのリリースとなる。Vim 9.0の目玉となる新機能は、文法を見直して大幅な高速化を実現したスクリプト言語「Vim9 Script」のサポートである。
シェルスクリプトマガジン Vol.79は以下のリンク先でご購入できます。
図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 |
command! -bar TimerStart let start_time = reltime() command! -bar TimerEnd echo reltimestr(reltime(start_time)) \ | unlet start_time function Add_old() let a = 0 for i in range(1, 1000000) let a += i endfor return a endfunction def Add_new(): number var a = 0 for i in range(1, 1000000) a += i endfor return a enddef TimerStart echo Add_old() TimerEnd TimerStart echo Add_new() TimerEnd |
レポート 「Raspberry Pi Zero 2 W」国内版発売(Vol.79掲載)
著者:末安 泰三
超小型コンピュータボード「Raspberry Pi Zero」シリーズの最新版、「Raspberry Pi Zero 2 W」の国内版が2022年6月に発売された。Raspberry Pi財団が2021年10月に発売した製品だが、国内版には技適マークが付く。同シリーズの従来製品と比べ、マルチスレッド性能が4~5倍程度向上した。
シェルスクリプトマガジン Vol.79は以下のリンク先でご購入できます。
図2 LinuxカーネルのRaspberry Pi Zero 2 W用のDTSファイルの内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
(略) compatible = "raspberrypi,model-zero-2-w", "brcm,bcm2837"; model = "Raspberry Pi Zero 2 W"; memory@0 { device_type = "memory"; reg = <0 0x20000000>; }; chosen { /* 8250 auxiliary UART instead of pl011 */ stdout-path = "serial1:115200n8"; }; (略) |
特集2 FlutterでGUIアプリを開発しよう(Vol.79記載)
著者:三好 文二郎
「Flutter」は、米Google社がOSS(オープンソースソフトウエア)として提供する、GUI アプリ開発用のSDK(Software Development Kit)です。単一のコードで複数のプラットフォーム向けのGUIアプリを作成できるほか、標準で用意されるUIコンポーネントが豊富、Hot Reloadによる開発者体験が素晴らしいことなどから開発者の間で人気が高まっています。Flutterの概要と、Flutterを利用したアプリ開発方法について紹介します。
シェルスクリプトマガジン Vol.79は以下のリンク先でご購入できます。
図18 ウィジェットに関する記述の例
1 2 3 4 5 6 7 8 9 |
children: <Widget>[ const Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], |
図19 ボタンウィジェットを追加するコードの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
children: <Widget>[ const Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), MaterialButton ( color: Colors.cyan.withOpacity(0.7), shape: RoundedRectangleBorder(borderRadius:BorderRadius.circular(30.0)), onPressed: () {}, child: const Text('this is a button widget'), ), ], |
図21 my_first_appプロジェクトの「pubspec.yaml」ファイルの内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
name: my_first_app description: A new Flutter project. publish_to: "none" version: 1.0.0+1 environment: sdk: ">=2.16.2 <3.0.0" dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^1.0.0 flutter: uses-material-design: true |
図22 go_routerパッケージを利用する場合の記述例
1 2 3 4 5 |
dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2 go_router: ^3.1.1 |
図23 画面を追加するためのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class NavigatedPage extends StatelessWidget { const NavigatedPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('navigated page'), ), body: const Center( child: Text('navigated with go_router'), ), ); } } |
図24 画面遷移のためのコード
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 |
class MyApp extends StatelessWidget { MyApp({Key? key}) : super(key: key); final _router = GoRouter( routes: [ GoRoute( path: '/', builder: (context, state) => const MyHomePage(title: 'Flutter Demo Home Page'), ), GoRoute( path: '/navigated', builder: (context, state) => const NavigatedPage(), ), ], ); @override Widget build(BuildContext context) { return MaterialApp.router( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), routeInformationParser: _router.routeInformationParser, routerDelegate: _router.routerDelegate); } } |
図25 ボタンウィジェットに画面遷移処理を割り当てるコード
1 2 3 4 5 6 7 8 |
MaterialButton ( color: Colors.cyan.withOpacity(0.7), shape: RoundedRectangleBorder(borderRadius:BorderRadius.circular(30.0)), onPressed: () { GoRouter.of(context).go('/navigated'); }, child: const Text('Navigate'), ), |
図26 main()関数のコードを変更
1 2 3 |
void main() { runApp(MyApp()); } |
特集3 ユニケージ開発のシステム(Vol.79記載)
著者:松浦 智之
システム開発手法「ユニケージ」では、データ保存用に「ファイル」を使い、ユニケージ専用コマンド群「usp Tukubai」と「シェルスクリプト」で業務システムを開発します。usp Tukubaiは有償ソフトですが、無償のオープンソース版「Open usp Tukubai」もあります。このOpen usp Tukubaiを使って、ユニケージ開発を無料で始めてみましょう。
シェルスクリプトマガジン Vol.79は以下のリンク先でご購入できます。
図6 Apache HTTP Serverの郵便番号・住所検索システム用設定ファイル(/etc/apache2/sites-available/zip2addr.conf)
1 2 3 4 5 6 7 8 |
<IfModule alias_module> Alias /zip2addr /home/ユーザー名/ZIP2ADDR/public_html <Directory /home/ユーザー名/ZIP2ADDR/public_html> AddHandler cgi-script .cgi Options all Require all granted </Directory> </IfModule> |
図10 MK_ZIPTBL.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 |
#!/bin/sh -u (略) homd=$(d=${0%/*}/; [ "_$d" = "_$0/" ] && d='./'; cd "$d.."; pwd) datd=$homd/DATA shld=$homd/SHELL webd=$homd/public_html url_ken=https://www.post.japanpost.jp/zipcode/dl/oogaki/zip/ken_all.zip url_jig=https://www.post.japanpost.jp/zipcode/dl/jigyosyo/zip/jigyosyo.zip export LC_ALL=C (略) nocmds='' type wget >/dev/null 2>&1 || { nocmds="$nocmds,wget" ; } type gunzip >/dev/null 2>&1 || { nocmds="$nocmds,gunzip"; } type iconv >/dev/null 2>&1 || { nocmds="$nocmds,iconv" ; } if [ -n "$nocmds" ]; then echo "${0##*/}: ${nocmds#,} not found. Install them in advance." 1>&2 exit 1 fi (略) wget -q -O - "$url_ken" | gunzip | tr -d '\r' | iconv -c -f Shift_JIS -t UTF-8 | tr -d '"' | awk -F , '{print $3,$7,$8,$9}' > $datd/ziptbl_ken.txt if [ ! -s $datd/ziptbl_ken.txt ]; then echo "${0##*/}: Failed to make zip_ken.txt" 1>&2; exit 1 fi (略) wget -q -O - "$url_jig" | gunzip | tr -d '\r' | iconv -c -f Shift_JIS -t UTF-8 | tr -d '"' | awk -F , '{print $8,$4,$5,$6 $7}' > $datd/ziptbl_jig.txt if [ ! -s $datd/ziptbl_jig.txt ]; then echo "${0##*/}: Failed to make zip_ken.txt" 1>&2; exit 1 fi (略) exit 0 |
図12 ZIP2ADDR.AJAX.cgiのコード
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 |
#!/bin/sh -u (略) homd=$(d=${0%/*}/; [ "_$d" = "_$0/" ] && d='./'; cd "$d.."; pwd) datd=$homd/DATA shld=$homd/SHELL webd=$homd/public_html export LC_ALL=C (略) exit_trap() { set -- ${1:-} $? # $? is set as $1 if no argument given trap '' EXIT HUP INT QUIT PIPE ALRM TERM [ -d "${tmpd:-}" ] && rm -rf "$tmpd" trap - EXIT HUP INT QUIT PIPE ALRM TERM exit $1 } error500_exit() { echo 'Status: 500 Internal Server Error' echo 'Content-Type: text/plain' echo echo '500 Internal Server Error' echo "($@)" exit 1 } error400_exit() { echo 'Status: 400 Bad Request' echo 'Content-Type: text/plain' echo echo '400 Bad Request' echo "($@)" exit 1 } (略) trap 'exit_trap' EXIT HUP INT QUIT PIPE ALRM TERM tmpd=$(mktemp -d -t "_${0##*/}.$$.XXXXXXXXXXX") [ -d "$tmpd" ] || error500_exit 'Failed to mktemp' (略) printf '%s\n' "${QUERY_STRING:-}" | cgi-name > $tmpd/cgivars zip=$(nameread zipcode $tmpd/cgivars) printf '%s\n' "$zip" | grep -qE '^[0-9]{7}$' || error400_exit 'Invalid zipcode' (略) echo $homd/DATA/ziptbl* | grep -qF '*' && error500_exit 'No table files exist' cat $homd/DATA/ziptbl* | awk '$1=="'"$zip"'"{print "pref",$2; print "city",$3; print "town",$4;}' > $tmpd/address123 [ -s $tmpd/address123 ] || error400_exit 'No address found' (略) echo 'Content-Type: text/html; charset=utf-8' echo 'Cache-Control: private, no-store, no-cache, must-revalidate' echo 'Pragma: no-cache' echo formhame $webd/ZIP2ADDR.html $tmpd/address123 | sed -n '/BEGIN ADDRESS123/,/END ADDRESS123/p' (略) [ -d "${tmpd:-}" ] && rm -rf "$tmpd" exit 0 |
図13 ZIP2ADDR.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 |
<!DOCTYPE html> (略) <script type="text/javascript" src="ZIP2ADDR.js"></script> (略) </head> <body> <h1>郵便番号→住所検索 デモ</h1> <form action="#dummy"> <table border="0" id="addressform"> <tr> <td> <dl> <dt>郵便番号</dt> <dd><input id="zipcode1" type="text" name="zipcode1" value="" size="3" maxlength="3" />-<input id="zipcode2" type="text" name="zipcode2" value="" size="4" maxlength="4" /></dd> <dd><input id="run" type="button" name="run" value="検索実行!" onclick="zip2addr();"></dd> </dl> </td> </tr> <tr> <td> <dl id="adress123"> <!-- BEGIN ADDRESS123--> <dt>住所(都道府県名)</dt> <dd> <select id="pref" name="pref"> <option value="(未選択)">(未選択)</option> (略) <option value="沖縄県" >沖縄県</option> </select> </dd> <dt>住所(市区町村名)</dt><dd><input id="city" type="text" size="60" name="city" value="" /></dd> <dt>住所(町名以降)</dt><dd><input id="town" type="text" size="60" name="town" value="" /></dd> <!-- END ADDRESS123--> </dl> </td> </tr> </table> </form> </body> </html> |
図14 ZIP2ADDR.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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
(略) function zip2addr() { var url; var zipcode; var xhr; (略) if (! document.getElementById('zipcode1').value.match(/^([0-9]{3})$/)) { alert('郵便番号(前の3桁)が正しくありません'); return; } zipcode = RegExp.$1; if (! document.getElementById('zipcode2').value.match(/^([0-9]{4})$/)) { alert('郵便番号(後の4桁)が正しくありません'); return; } zipcode += RegExp.$1; (略) xhr = createXMLHttpRequest(); if (xhr) { url = 'ZIP2ADDR.AJAX.cgi?zipcode='+zipcode; url += '&dummy='+parseInt((new Date)/1); xhr.open('GET', url, true); xhr.onreadystatechange = function(){zip2addr_callback(xhr)}; xhr.send(null); } (略) return; } (略) function zip2addr_callback(xhr) { var e; (略) if (xhr.readyState != 4) {return;} if (xhr.status == 0 ) {return;} if (xhr.status == 400) { alert('郵便番号が正しくありません'); return; } else if (xhr.status != 200) { alert('アクセスエラー(' + xhr.status + ')'); return; } (略) e = document.getElementById('adress123'); if (!e) {alert('エラー: 住所欄が存在しない!'); return;} e.innerHTML = xhr.responseText; (略) return; } |
特別企画 本格的なホームサーバーを構築(Vol.79記載)
著者:麻生 二郎
小型コンピュータボードの最上位モデルである「Raspberry Pi 4 Model B」の2G/4G/8Gバイト版と、人気のLinuxディストリビューション「Ubuntu」のサーバー版を組み合わせて、本格的なサーバーを構築しましょう。本特集では、サーバーをインターネットに公開する方法を紹介します。
シェルスクリプトマガジン Vol.79は以下のリンク先でご購入できます。
図A3 ラズパイサーバーの初期設定(ubuntu_init1.sh)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#!/bin/sh ##日本のタイムゾーン設定 sudo timedatectl set-timezone Asia/Tokyo ##全ソフトウエア更新 sudo apt update sudo apt -y upgrade sudo apt -y autoremove sudo apt clean ##ファームウエアアップデート sudo apt -y install rpi-eeprom sudo rpi-eeprom-update ##完了後の再起動 read -p "再起動しますか [y/N]:" YN if [ " $YN" = " y" ] || [ " $YN" = " Y" ]; then sudo reboot fi |
図A4 ラズパイサーバーの初期設定(ubuntu_init2.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 |
#!/bin/sh ##固定IPアドレスとルーターのIPアドレス IP_ADDRESS="192.168.10.100" ROUTER_IP="192.168.10.1" ##旧設定バックアップ mkdir -p ~/old_settings sudo mv /etc/netplan/50-cloud-init.yaml ~/old_settings/. ##新ネットワーク設定作成 cat << EOF | sudo tee /etc/netplan/50-cloud-init.yaml > /dev/null network: ethernets: eth0: dhcp4: false addresses: [ip_address/24] gateway4: router_ip nameservers: addresses: [8.8.8.8] version: 2 EOF sudo sed -i -e "s%ip_address%$IP_ADDRESS%" /etc/netplan/50-cloud-init.yaml sudo sed -i -e "s%router_ip%$ROUTER_ip%" /etc/netplan/50-cloud-init.yaml ##ネットワーク設定反映 sudo netplan apply |
Raspberry Piを100%活用しよう(Vol.79掲載)
著者:米田 聡
小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第12回は、電流を測定するクランプメーターを搭載する拡張基板を扱います。
シェルスクリプトマガジン Vol.79は以下のリンク先でご購入できます。
図6 本連載で作成したサンプルプログラム(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 37 38 39 40 41 42 43 44 45 |
import time import board import math import busio import adafruit_ads1x15.ads1115 as ADS from adafruit_ads1x15.ads1x15 import Mode from adafruit_ads1x15.analog_in import AnalogIn GAIN = 1 RATE = 860 i2c = busio.I2C(board.SCL, board.SDA, frequency=1000000) ads = ADS.ADS1115(i2c, GAIN) # 差動入力で初期化 in0 = AnalogIn(ads, ADS.P0, ADS.P1) # 連続モードの設定 ads.mode = Mode.CONTINUOUS ads.data_rate = RATE SAMPLE_INTERVAL = 1.0 / ads.data_rate # ADS1115のCONTINUOUSモードは初回読み取り実行後に設定が行われ # 設定に2サンプリングクロックが必要になる _ = in0.voltage # 設定用の空読み込み value = 0.0 current_value = 0.0 sqrtI = 0.0 time_next_sample = time.monotonic() + SAMPLE_INTERVAL for i in range(ads.data_rate): while time.monotonic() < (time_next_sample): pass # 前回と同じ値が読み取られた場合は読み飛ばす current_value = in0.voltage while current_value == value: current_value = in0.voltage value = current_value sqrtI += value * value time_next_sample = time.monotonic() + SAMPLE_INTERVAL # 電流値(RMS)の計算 ampere = math.sqrt(sqrtI/ads.data_rate) * 20.0 print(ampere) |
シェルスクリプトマガジンvol.78 Web掲載記事まとめ
004 レポート OpenSSH 9.0/9.0p1リリース
005 レポート ゲームエンジンのUnreal Engine 5
006 製品レビュー マグカップ型電気鍋「Cook Mug」
007 NEWS FLASH
008 特集1 アプリケーション開発基盤 .NET/松井恵一、木下舜、上原将司 コード掲載
023 Hello Nogyo!
024 特集2 UMLを使い始めてみよう/森三貴
040 特別企画 ESP32とラズパイで作る本格IoT 簡易地震計/魔法少女 コード掲載
058 Raspberry Piを100%活用しよう/米田聡 コード掲載
060 Pythonあれこれ/飯尾淳 コード掲載
066 法林浩之のFIGHTING TALKS/法林浩之
068 中小企業手作りIT化奮戦記/菅雄一 コード掲載
072 ダークウェブ/桑原滝弥、イケヤシロウ
074 香川大学SLPからお届け!/樋口史弥 コード掲載
084 タイ語から分かる現地生活/つじみき
092 Bash入門/大津真
098 Techパズル/gori.sh
099 コラム「計画は全体を、問題は点を追求する」/シェル魔人
特集1 アプリケーション開発基盤 .NET(Vol.78記載)
著者:松井 恵一、木下 舜、上原 将司
デスクトップパソコンやモバイル、Web、クラウドなど、さまざまな環境で動作するアプリケーションを開発できる基盤が「.NET」です。昨年11月に初の長期サポート版となる.NET 6がリリースされました。本特集では、最新の.NET 6を含め、.NETについて分かりやすく解説します。
シェルスクリプトマガジン Vol.78は以下のリンク先でご購入できます。
図13 Program.csファイルの中身
1 2 |
// See https://aka.ms/new-console-template for more information Console.WriteLine("Hello, World!"); |
図14 C#で記述した、「Hello World」を表示するプログラムの例
1 2 3 4 5 6 7 8 9 10 11 12 |
using System; namespace ConsoleApplication { public class Program { public static void Main() { Console.WriteLine("Hello World!"); } } } |
図15 MyFirstDotnetApp.csprojファイルの中身
1 2 3 4 5 6 7 8 9 10 |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> </Project> |
特集1 ESP32とラズパイで作る本格IoT(Vol.78記載)
筆者:魔法少女
マイコン「ESP32」と小型コンピュータボード「Raspberry Pi」を使って本格的なIoT(Internet of Things、モノのインターネット)環境を構築してみましょう。本企画では、3軸の加速度センサーを用いて簡易地震計を作成し、リアルタイムに揺れをグラフで表示します。
シェルスクリプトマガジン Vol.78は以下のリンク先でご購入できます。
図4 Eclipse Mosquittoの設定スクリプト(mosquitto_setting.sh)
1 2 3 4 5 6 7 8 9 |
#!/bin/sh sudo tee /etc/mosquitto/conf.d/confer.conf << EOF >dev/null listener 1883 allow_anonymous true listener 8081 protocol websockets sudo systemctl reload mosquitto |
図18 ESP32搭載ボートの起動時に無線LANに接続するプログラム(boot.py)
1 2 3 4 5 6 7 8 9 |
import network SSID = 'SSID' PASSWORD = 'パスワード' wlan_if = network.WLAN(network.STA_IF) wlan_if.active(True) wlan_if.connect(SSID, PASSWORD) |
図19 パブリッシャのプログラム(main.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 |
import time from machine import Pin, SoftI2C import mpu6050 from umqtt.simple import MQTTClient import json mqtt_topic = "home/seismometer1" publisher_id = "place_esp32" broker_address = "192.168.1.100" interval_time = "1" i2c = SoftI2C(scl=Pin(22), sda=Pin(21)) accelerometer = mpu6050.accel(i2c) time.sleep(5) while True: iot_value = accelerometer.get_values() iot_value_json = json.dumps(iot_value) print(iot_value_json) publisher = MQTTClient(publisher_id,broker_address) publisher.connect() publisher.publish(mqtt_topic, msg=str(iot_value_json)) publisher.disconnect() time.sleep(int(interval_time)) |
図24 ブスクライバとなるJavaScriptプログラムを含んだHTMLファイル(seismometer.html)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>簡易地震計</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-streaming@1.9.0"></script> <script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script> </head> <body> <canvas id="myChart"></canvas> <script> const client = mqtt.connect('ws://raspibroker.local:8081'); const ctx = document.getElementById('myChart').getContext('2d'); var chart = new Chart(ctx, { type: 'line', data: { datasets: [{ data: [], label: 'X軸', borderColor: 'rgb(255, 0, 255)', backgroundColor: 'rgba(255, 255, 255, 0)', lineTension: 0, }, { data: [], label: 'Y軸', borderColor: 'rgb(0, 255, 0)', backgroundColor: 'rgba(255, 255, 255, 0)', lineTension: 0, }, { data: [], label: 'Z軸', borderColor: 'rgb(0, 0, 255)', backgroundColor: 'rgba(255, 255, 255, 0)', lineTension: 0, }] }, options: { scales: { xAxes: [{ type: 'realtime', realtime: { delay: 2000, }, }], yAxes: [{ ticks: { min: -32767, max: 32767 } }] } } }); client.on('connect', () => { console.log('connected'); client.subscribe('home/seismometer1'); }); client.on('message', (topic, message) => { console.log(message.toString()); acc_json = JSON.parse(message.toString()); chart.data.datasets[0].data.push({ x: Date.now(), y: acc_json.AcX }); chart.data.datasets[1].data.push({ x: Date.now(), y: acc_json.AcY }); chart.data.datasets[2].data.push({ x: Date.now(), y: acc_json.AcZ - 16384 }); chart.update(); }); </script> </body> </html> |
Raspberry Piを100%活用しよう(Vol.78掲載)
著者:米田 聡
小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第11回は、光の反射で物体との距離を調べるセンサー搭載の拡張基板を扱います。
シェルスクリプトマガジン Vol.78は以下のリンク先でご購入できます。
図4 VCNL4010から値を読み出すPythonプログラム(sample.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import time import Adafruit_VCNL40xx vcnl = Adafruit_VCNL40xx.VCNL4010() try: while True: prox = vcnl.read_proximity() ambient = vcnl.read_ambient() print('Proximity = ' + str(prox)) print('Ambient = ' + str(ambient) + ' Lux') time.sleep(0.5) except KeyboardInterrupt: pass |
Pythonあれこれ(Vol.78掲載)
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温
かく見守ってください。皆さんと共に勉強していきましょう。第8回は、Webアプリケーションフレームワーク「Flask」を使って基本的なWebアプリを作成する方法を紹介します。データベースにアクセスする方法やテンプレートの利用方法も解説します。
シェルスクリプトマガジン Vol.78は以下のリンク先でご購入できます。
図1 「Hello World」に相当するシンプルなWebアプリのコード(app.py)
1 2 3 4 5 6 |
from flask import Flask app = Flask(__name__) @app.route('/') def hello(): return '<h1>Hello World!</h1>' |
図4 ルーティングの記述を追加したWebアプリのコード
1 2 3 4 5 6 7 8 9 10 |
from flask import Flask app = Flask(__name__) @app.route('/hello') def hello(): return '<h1>Hello World!</h1>' @app.route('/goodbye') def goodbye(): return '<h1>Good-bye World!</h1>' |
図5 パスパラメータを使うWebアプリのコードの例
1 2 3 4 5 6 |
from flask import Flask app = Flask(__name__) @app.route('/hello/<name>') def hello(name): return f'<h1>Hello {name}!</h1>' |
図7 クエリーパラメータを使うWebアプリのコードの例
1 2 3 4 5 6 7 |
from flask import Flask, request app = Flask(__name__) @app.route('/hello') def hello(): name = request.args['name'] return f'<h1>Hello {name}!</h1>' |
図9 メモ帳Webアプリのひな型コード
1 2 3 4 5 6 7 8 9 10 11 12 |
from flask import Flask, request app = Flask(__name__) @app.route('/addMemo') def addMemo(): name = request.args['name'] description = request.args['description'] return f'ADD: name = {name}, desc = {description}' @app.route('/getMemo') def getMemo(): return f'[TBD]' |
図10 データベースを使うメモ帳Webアプリのコード(その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 |
from flask import Flask, request from flask_sqlalchemy import SQLAlchemy from sqlalchemy import Integer, String, Text app = Flask(__name__) app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///memo.sqlite' db = SQLAlchemy(app) class Memo(db.Model): __tablename__ = 'memo' ID = db.Column(Integer, primary_key=True) NAME = db.Column(String(500)) DESCRIPTION = db.Column(Text) db.create_all() @app.route('/addMemo') def addMemo(): name = request.args['name'] description = request.args['description'] return f'ADD: name = {name}, desc = {description}' @app.route('/getMemo') def getMemo(): return f'[TBD]' |
図12 データベースを使うメモ帳Webアプリのコード(その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 |
from flask import Flask, request from flask_sqlalchemy import SQLAlchemy from sqlalchemy import Integer, String, Text app = Flask(__name__) app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///memo.sqlite' db = SQLAlchemy(app) class Memo(db.Model): __tablename__ = 'memo' ID = db.Column(Integer, primary_key=True) NAME = db.Column(String(500)) DESCRIPTION = db.Column(Text) db.create_all() @app.route('/addMemo') def addMemo(): name = request.args['name'] description = request.args['description'] db.session.add(Memo(NAME=name, DESCRIPTION=description)) db.session.commit() db.session.close() return f'ADD: name = {name}, desc = {description}' @app.route('/getMemo') def getMemo(): memos = db.session.query( Memo.ID, Memo.NAME, Memo.DESCRIPTION).all() return str(memos) |
図14 テンプレートファイル「index.html」の内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<!DOCTYPE html> <html> <head> <title>Flask Test</title> </head> <body> <h1>The Memo List</h1> <table> <tr><th>ID</th><th>Name</th><th>Description</th></tr> {% for memo in memos: %} <tr><td>{{ memo.0 }}</td> <td>{{ memo.1 }}</td> <td>{{ memo.2 }}</td></tr> {% endfor %} </table> </body> </html> |
図15 テンプレートを使うメモ帳Webアプリのコード(その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 |
from flask import Flask, request, render_template from flask_sqlalchemy import SQLAlchemy from sqlalchemy import Integer, String, Text app = Flask(__name__) app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///memo.sqlite' db = SQLAlchemy(app) class Memo(db.Model): __tablename__ = 'memo' ID = db.Column(Integer, primary_key=True) NAME = db.Column(String(500)) DESCRIPTION = db.Column(Text) db.create_all() @app.route('/addMemo') def addMemo(): name = request.args['name'] description = request.args['description'] db.session.add(Memo(NAME=name, DESCRIPTION=description)) db.session.commit() db.session.close() return f'ADD: name = {name}, desc = {description}' @app.route('/getMemo') def getMemo(): memos = db.session.query( Memo.ID, Memo.NAME, Memo.DESCRIPTION).all() return render_template('index.html', memos=memos) |
図17 修正したテンプレートファイル「index.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 |
<!DOCTYPE html> <html> <head> <title>Flask Test</title> </head> <body> <h1>The Memo List</h1> <table border="1" width="500" cellspacing="0" cellpadding="5" bordercolor="#333333"> <tr><th>ID</th><th>Name</th><th>Description</th></tr> {% for memo in memos: %} <tr><td>{{ memo.0 }}</td> <td>{{ memo.1 }}</td> <td>{{ memo.2 }}</td></tr> {% endfor %} </table> <h2>New Memo</h2> <form action="/addMemo" method="post"> <table style="text-align: left;"> <tr style="vertical-align: top;"><th>Name:</th> <td><input type="text" name="name"></input></td></tr> <tr style="vertical-align: top;"><th>Description:</th> <td><textarea name="description" rows="4" cols="40"> </textarea></td></td> </table> <input type="submit" value="Submit"> <input type="reset" value="Reset"> </form> </body> </html> |
図18 テンプレートを使うメモ帳Webアプリのコード(その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 |
from flask import Flask, request, render_template, redirect, url_for from flask_sqlalchemy import SQLAlchemy from sqlalchemy import Integer, String, Text app = Flask(__name__) app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///memo.sqlite' db = SQLAlchemy(app) class Memo(db.Model): __tablename__ = 'memo' ID = db.Column(Integer, primary_key=True) NAME = db.Column(String(500)) DESCRIPTION = db.Column(Text) db.create_all() @app.route('/addMemo', methods=['POST']) def addMemo(): name = request.form['name'] description = request.form['description'] db.session.add(Memo(NAME=name, DESCRIPTION=description)) db.session.commit() db.session.close() return redirect(url_for('getMemo')) @app.route('/getMemo') def getMemo(): memos = db.session.query( Memo.ID, Memo.NAME, Memo.DESCRIPTION).all() return render_template('index.html', memos=memos) |
中小企業手作りIT化奮戦記(Vol.78掲載)
著者:菅 雄一
筆者は2001年、PHP言語とOSS(オープンソースソフトウエア)データベース管理システムのPostgreSQLに出会った。それ以降、データベースが絡んだWebシステムを構築する際は、PHPとPostgreSQLを組み合わせて使っていた。しかし2021年に社内Web業務システムなどをレンタルサーバーに移行させた際には、PHPとOSSデータベース管理システムのMySQLを初めて組み合わせて使った。今回は、その移行作業について紹介する。
シェルスクリプトマガジン Vol.78は以下のリンク先でご購入できます。
図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 |
function Send_Mail($addr,$content) { $fp = fsockopen(" メールサーバーのアドレス ",25, &$errno , &$errstr); if(!$fp) { print "$errstr ($errno)<br>n" ; } else { $subject = "Subject: 発送依頼書" ; $subject = mb_convert_encoding($subject,"UTF-8"); $content = mb_convert_encoding($content,"JIS"); fwrite($fp,"HELO server.example.co.jp\n"); fwrite($fp,"MAIL FROM:chohyo@example.co.jp\n"); fwrite($fp,"RCPT TO:$addr\n"); fwrite($fp,"DATA\n"); fwrite($fp,"From: chohyo@example.co.jp\n"); fwrite($fp,"$subject\n"); fwrite($fp,"Content-Type: text/plain; charset=ISO-2022-JP\n"); fwrite($fp,"$content\n"); fwrite($fp,".\n"); fwrite($fp,"QUIT\n"); } fclose($fp); } |
香川大学SLPからお届け!(Vol.78掲載)
著者:樋口 史弥
アプリケーションやサービスは、ユーザーの意見や技術状況に合わせた変更をしやすいように開発するのが理想的です。そうした開発を実現するための設計手法の一つが「クリーンアーキテクチャ」です。今回は、クリーンアーキテクチャを用いて簡単な匿名掲示板を作る方法を解説します。
シェルスクリプトマガジン Vol.78は以下のリンク先でご購入できます。
図4 投稿削除機能を追加した場合のコード例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// メッセージを表す構造体 type struct Message { (略) } // 全メッセージを取得する関数 func (m *Message)GetAllMessages() error { (略) } // メッセージを投稿する関数 func (m *Message)Post() error { (略) } // メッセージを削除する関数 func (m *Message)Delete() error { (略) } |
図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 |
// メッセージを表す構造体 type struct Message { (略) } // 一般ユーザーを表す構造体 type struct User { Message (略) } // メッセージを投稿する関数 func (u *User)Post() error { (略) } // 全メッセージを取得する関数 func (u *User)GetAllMessages() error { (略) } // 管理者を表す構造体 func type struct Administrator { Message (略) } // メッセージを削除する関数 func (a *Administrator)Delete() error { (略) } |
図8 モジュールに依存したコードの例
1 2 3 4 5 6 |
func (m *Message)Post() error { (略) // MySQLHandlerモジュールのExecute関数を直接実行 err := MySQLHandler.Execute(sql) (略) } |
図10 モジュールに依存したコードの例
1 2 3 4 5 6 7 8 9 10 11 |
type struct Message { (略) i DBInterface } func (m *Message)Post() error { (略) // MySQLHandlerモジュールのExecute関数を // 直接実行せず、DBInterface経由で実行 err := m.i.Execute(sql) (略) } |
図19 messageモジュールのコード
1 2 3 4 5 6 7 8 |
type Message interface { // メッセージの文字列を返却する関数 Message() string } type message struct { // メッセージの文字列を格納するメンバー detail string } |
図20 投稿時刻やハンドルを付加する場合のmessageモジュールのコード
1 2 3 4 5 6 7 8 9 10 11 12 |
type Message interface { // メッセージの文字列を返却する関数 Message() string // 投稿したユーザーのハンドルを返却する関数 HandleName() string } type message struct { // メッセージの文字列を格納するメンバー detail string // 投稿ユーザーのハンドルを格納るメンバー handleName string } |
図21 ログイン機能を付加するためのuserモジュールのコード
1 2 3 4 5 6 7 8 9 10 |
type User interface { // ユーザーIDを返却する関数 ID() string // パスワードが適切かどうかを検査する関数 MatchPassword(string) bool } type user struct { id string password string } |
図22 メッセージを投稿するというユースケースに対応するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
type controller struct { // データベースに保存するモジュールに // アクセスするためのインタフェース database db_gateway.DB } func (c *controller)Post(m message.Message) error { // Messageの中身の検証 if len(m.Message()) == 0 { return MessageNotFound } // インタフェースを用いてメッセージを保存 err := c.database.RecordMessage(m) (略) } |
図23 メッセージを保存するRecordMessageメソッドのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
type mySQLHandler struct { // Frameworks&Drivers層のデータベースクライアントライブラリ // のモジュールを呼び出すインタフェース database mysql_gateway.MySQLHandler } // メッセージを保存するためのハンドラメソッド func (m *mySQLHandler)RecordMessage( message message.Message, ) error { // SQL文を生成 sql := ` INSERT INTO messages (message) VALUES (?) ` // インタフェースを介してSQL文を実行 _, err := m.database.Execute(sql, message.Message()) (略) } |
図24 mySQLHandler 構造体のdatabaseメンバーに所属するインタフェースの定義コード
1 2 3 4 |
type MySQLHandler interface { Query(string, ...interface{}) (Row, error) Execute(statement string, args ...interface{}) (Result, error) } |
図25 MySQLHandler インタフェースで定義されるExecute メソッドのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import ( (略) // データベースクライアントライブラリをインポート "database/sql" ) type mysql struct { // データベースに問い合わせるためのコネクション Conn *sql.DB } func (handler *mysql) Execute( statement string, args ...interface{}, ) (worker.Result, error) { res := new(SQLResult) // コネクションを用いてSQL文を実行 result, err := handler.Conn.Exec(statement, args...) (略) res.Result = result return res, nil } |
図26 匿名掲示板のMainコンポーネントのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
func main() { // External Interfaceのdatabaseモジュールを初期化 mysqlDB, f, err := database.NewMySQL(db_user, db_password, db_ip, db_port, db_name) (略) // Interface Adaptersのdb_handlerモジュールを初期化 DBHandler := db_handler.NewMySQL(mysqlDB) // Application Business Rulesのcontrollerモジュールを初期化 controller := interactor.NewController(DBHandler) // External Interfaceのserverモジュールを初期化 server := server.New(port, controller) // Application Business Rulesのinteractorモジュールを初期化 interactor := interactor.NewInteractor(server) // Application Business Rulesに制御を渡す e := interactor.Run() (略) } |
図27 データの保存先を変更する際のMainコンポーネント書き換え箇所
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
func main() { // External Interfaceのdatabaseモジュールを初期化 textDB, err := database.NewText(TextPath) (略) // Interface Adaptersのdb_handlerモジュールを初期化 DBHandler := db_handler.New(textDB) // Application Business Rulesのcontrollerモジュールを初期化 controller := interactor.NewController(DBHandler) // External Interfaceのserverモジュールを初期化 server := server.New(port, controller) // Application Business Rulesのinteractorモジュールを初期化 interactor := interactor.NewInteractor(server) // Application Business Rulesに制御を渡す e := interactor.Run() (略) } |
シェルスクリプトマガジンvol.77 Web掲載記事まとめ
004 レポート 古いPC向けOS「Chrome OS Flex」
005 レポート WebブラウザでDebianが動作
006 NEWS FLASH
008 特集1 AWKプログラミング入門/斉藤博文 コード掲載
017 Hello Nogyo!
018 特集2 MIRACLE LINUXという選択肢/弦本春樹
022 特集3 デスクトップ版Power Automateを活用しよう/三沢友治
032 特別企画 ESP32とラズパイで作る本格IoT/魔法少女 コード掲載
050 先端技術 量子コンピュータ
052 Raspberry Piを100%活用しよう/米田聡 コード掲載
056 機械学習ことはじめ/川嶋宏彰 コード掲載
068 Pythonあれこれ/飯尾淳 コード掲載
074 JSON/桑原滝弥、イケヤシロウ
076 中小企業手作りIT化奮戦記/菅雄一
080 法林浩之のFIGHTING TALKS/法林浩之
082 タイ語から分かる現地生活/つじみき
088 香川大学SLPからお届け!/石上椋一 コード掲載
094 Bash入門/大津真
104 Techパズル/gori.sh
105 コラム「ユニケージ流の業務システム開発」/シェル魔人
特集1 AWKプログラミング入門(Vol.77記載)
著者:斉藤 博文
シェルスクリプトで複雑なテキストデータ処理を実行する場合、「AWK」(オーク)というプログラミング言語が利用されます。AWKの処理系(実行環境)はとても小さく、シェルスクリプトと組み合わせて使うのに適しています。本特集では、このAWKのプログラミングを分かりやすく解説します。
シェルスクリプトマガジン Vol.77は以下のリンク先でご購入できます。
図8 ユーザー定義関数「factorial」の実装例
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ print factorial($0); } function factorial(n, i, ret) { ret = 1; for (i = 1; i <= n; i++) { ret *= i } return ret; } |
図9 改変した階乗処理のAWKプログラム(factorial.awk)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
BEGIN { n = n ? n : 5; print factorial(n); } function factorial(n) { ret = 1; for (i = 1; i <= n; i++) { ret *= i } return ret; } |
特別企画 ESP32とラズパイで作る本格IoT(Vol.77記載)
著者:魔法少女
マイコン「ESP32」と小型コンピュータ「Raspberry Pi」を使って本格的なIoT(モノのインターネット)環境を構築してみましょう。本企画では、五つの会議室の温度、湿度、利用状況を監視・分析し、会議室内の電源を制御するシステムを構築します。
シェルスクリプトマガジン Vol.77は以下のリンク先でご購入できます。
図4 ユーザー名とパスワードの生成スクリプト(user_create.sh)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#!/bin/sh tee ~/confer_pw.txt << EOF > /dev/null pub1:passwd1 pub2:passwd2 pub3:passwd3 pub4:passwd4 pub5:passwd5 sub1:passwd6 EOF for i in $(seq 6) do sed -i -e "s|passwd${i}|$(pwgen 8 1)|" ~/confer_pw.txt done |
図5 Eclipse Mosquittoの認証設定スクリプト(mosquitto_setting.sh)
1 2 3 4 5 6 7 8 9 10 |
#!/bin/sh sudo cp ~/confer_pw.txt /etc/mosquitto/confer_pwfile sudo mosquitto_passwd -U /etc/mosquitto/confer_pwfile sudo tee /etc/mosquitto/conf.d/confer.conf << EOF > /dev/null listener 1883 allow_anonymous false password_file /etc/mosquitto/confer_pwfile EOF sudo systemctl reload mosquitto |
図7 サブスクライバのプログラム(subscriber.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 |
#!/bin/env python3 import paho.mqtt.client as mqtt import time ## mqtt_topic = "confer/#" csv_file = "./room1.csv" subscriber_username = "sub1" subscriber_password = "IBehie1h" broker_hostname = "localhost" ## def on_connect(client, userdata, flags, rc): print("Connected with result code "+str(rc)) client.subscribe(mqtt_topic) def on_message(client, userdata, msg): now_unixtime = time.time() iot_device = msg.topic + "," + str(msg.payload, encoding='utf-8', errors='replace') + "," + str(now_unixtime) print(iot_device) with open(csv_file, mode='a') as f: f.write(iot_device + "\n") ## client = mqtt.Client() client.on_connect = on_connect client.on_message = on_message client.username_pw_set(username=subscriber_username, password=subscriber_password) client.connect(broker_hostname) client.loop_forever() |
図24 ESP32搭載ボートの起動時に無線LANに接続するプログラム(boot.py)
1 2 3 4 5 6 7 8 9 |
import network SSID = 'USP_OFFICE_B' PASSWORD = 'uspuspusp1234' wlan_if = network.WLAN(network.STA_IF) wlan_if.active(True) wlan_if.connect(SSID, PASSWORD) |
図25 パブリッシャのプログラム(main.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 |
import time import dht from machine import Pin from umqtt.simple import MQTTClient mqtt_topic = "confer/room1" publisher_username = "pub1" publisher_password = "パスワード" publisher_id = "room1_esp32" broker_address = "192.168.1.100" interval_time = "1" hf_sensor = Pin(5, Pin.IN, Pin.PULL_UP) th_sensor = dht.DHT11(Pin(22)) time.sleep(5) while True: value1 = hf_sensor.value() th_sensor.measure() value2 = th_sensor.temperature() value3 = th_sensor.humidity() iot_value = str(value1)+","+str(value2)+","+str(value3) print(iot_value) publisher = MQTTClient(publisher_id,broker_address,user=publisher_username,password=publisher_password) publisher.connect() publisher.publish(mqtt_topic, msg=iot_value) publisher.disconnect() time.sleep(int(interval_time)) |
Raspberry Piを100%活用しよう(Vol.77掲載)
筆者:米田 聡
小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第10回は、サーボモーターを制御できる拡張基板を扱います。
シェルスクリプトマガジン Vol.77は以下のリンク先でご購入できます。
図6 チャンネル1に接続したSG90を動かすサンプルプログラム(sv.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 |
import smbus, SVO import time servo = SVO.SVO() #init servo.Servo_Switch('b', '0') # 両チャンネルオフ servo.Set_servo_cycle('20000') # PWMピリオド20ミリ秒 servo.Set_servo_duty_min('b','500') # 最小デューティサイクル0.5ミリ秒 servo.Set_servo_duty_max('b','2400') # 最大デューティサイクル2.4ミリ秒 servo.Set_servo_duty('b', '500') # -90度に初期化 servo.Set_servo_Write() # 初期値を書き込み # 作動 servo.Servo_Switch('b', '1') # 両チャンネルオン time.sleep(3) for r in range(10, 190, 10): # 10度から180度まで print("r=" + str(r)) sval = str(int( r*1900/180 + 500 )) servo.Set_servo_duty('1', sval) servo.Set_servo_Write() time.sleep(3) |
機械学習ことはじめ(Vol.77掲載)
筆者:川嶋 宏彰
本連載では、機械学習の基礎となるさまざまな手法の仕組みや、それらの手法のPythonでの利用方法を解説していきます。最終回となる今回は、ニューラルネットの仕組みと、基本的なモデルについて解説します。
シェルスクリプトマガジン Vol.77は以下のリンク先でご購入できます。
図3 単純パーセプトロンを学習するための関数を定義するPythonコード
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 numpy as np def h_step(x): """ ステップ関数: 0より大きければ1、それ以外は0 """ return int(x > 0) def train_perceptron(X, y, lr=0.5, max_it=20, random_state=None): """ パーセプトロンの学習アルゴリズム lr: 学習率 (learning rate) max_it: 最大反復回数 (maximum number of iterations) """ N, d = X.shape # データ数x次元 X1 = np.c_[np.ones(N), X] # 1だけの列を1列目に追加 d1 = d + 1 # バイアス項込みの次元数 np.random.seed(random_state) w = np.random.rand(d1) # (w0, w1, w2) print('initial w', w) w_log = [w.copy()] # 初期の重み係数 info_log = [[-1] * 5] # [it, i, y, o, y-o] for it in range(max_it): # ① 反復 (iteration) print('--- iteration:', it) err = 0 for i in range(N): # ② 各データを入力 x = X1[i, :] y_pred = h_step(np.dot(w, x)) err += (y[i] - y_pred) ** 2 print('yhat:', y_pred, 'y:', y[i]) if y_pred != y[i]: w += lr * (y[i] - y_pred) * x # ③ wを更新 w_log.append(w.copy()) info_log.append([it, i, y[i], y_pred, y[i] - y_pred]) print('err:', err) if err == 0: print('## converged @ it=', it) break return w_log, info_log def get_fourpoints(xor_flag=False): """4点のデータを準備 xor_flag: 各データのラベルをどう設定するか True: 入力が異なる符号なら1, False: 入力が共に正なら1 """ X = np.array([[-1, -1], [-1, 1], [1, -1], [1, 1]]) y = np.array([0, 1, 1, 0]) if xor_flag else np.array([0, 0, 0, 1]) return X, y |
図4 単純パーセプトロンの学習過程を可視化する関数を定義するPythonコード
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 matplotlib.pyplot as plt import seaborn as sns plt.rcParams['font.size'] = 14 def plot_2dline_with_data(X, y, w, title='', i=-1): """ データと w0 + w1 x1 + w2 x2 = 0 をプロット """ fig = plt.figure(figsize=(5, 5)) sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=y, s=150) xlim = [-1.5, 1.5] ylim = [-1.5, 1.5] plt.xlim(xlim) plt.ylim(ylim) plt.xticks([-1, 1]) plt.yticks([-1, 1]) # w0 + w1*x1 + w2*x2 = 0 のプロット if w[2] == 0: # w2 == 0 のとき: x1 = -w0/w1 x2 = np.linspace(*ylim, 2) plt.plot([-w[0]/w[1]] * x2.size, x2, '-', linewidth=3, color='r') else: # w2 != 0 のとき: x2 = -(w0 + w1*x1)/w2 x1 = np.linspace(*xlim, 2) plt.plot(x1, -(w[0] + w[1]*x1)/w[2], '-', linewidth=3, color='r') if i >= 0: plt.scatter(X[i, 0], X[i, 1], s=300, facecolor='none', edgecolor='r', linewidth=2) plt.title(title) return fig |
図5 単純パーセプトロンの学習と結果表示をするPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# データの読み込みと学習 xor_flag = False # (★) True: XOR, False: AND X, y = get_fourpoints(xor_flag) # 学習データを取得 print(f' X:\n{X},\n y: {y}') w_log, info_log = train_perceptron(X, y, random_state=0) # 学習過程の表示 for step in range(len(w_log)): title = 'it: {}, i: {}, y: {}, y_pred: {}, y - y_pred:{}'\ .format(*info_log[step]) print(title) w = w_log[step] it = info_log[step][0] i = info_log[step][1] print('w:', w) plot_flag = False if (it >= 3) and (it % 5 != 0) else True if plot_flag: plot_2dline_with_data(X, y, w, title, i) plt.show() |
図12 決定境界を可視化するための関数を定義するPythonコード
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 |
import seaborn as sns import matplotlib as mpl def plot_decision_boundary(X, y, clf, xylabels=None, palette=None, fig=None, ngrid=50): """ 分類器 clf の決定境界を描画 """ if fig is None: fig = plt.figure(figsize=(5, 5)) else: plt.figure(fig.number) # 2次元空間にグリッド点を準備 xmin = X.min(axis=0) # 各列の最小値 xmax = X.max(axis=0) # 各列の最大値 xstep = [(xmax[j]-xmin[j]) / ngrid for j in range(2)] # グリッドのステップ幅 xmin = [xmin[j] - 8*xstep[j] for j in range(2)] # 少し広めに xmax = [xmax[j] + 8*xstep[j] for j in range(2)] aranges = [np.arange(xmin[j], xmax[j] + xstep[j], xstep[j]) for j in range(2)] x0grid, x1grid = np.meshgrid(*aranges) # 各グリッド点でクラスを判定 y_pred = clf.predict(np.c_[x0grid.ravel(), x1grid.ravel()]) y_pred = y_pred.reshape(x0grid.shape) # 2次元へ y_pred = np.searchsorted(np.unique(y_pred), y_pred) # 値をindexへ clist = palette.values() if type(palette) is dict else palette cmap = mpl.colors.ListedColormap(clist) if palette is not None else None plt.contourf(x0grid, x1grid, y_pred, alpha=0.3, cmap=cmap) sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=y, palette=palette) plt.legend() plt.xlim([xmin[0], xmax[0]]) plt.ylim([xmin[1], xmax[1]]) if xylabels is not None: plt.xlabel(xylabels[0]) plt.ylabel(xylabels[1]) return fig |
図13 多層パーセプトロンによる学習をするPythonコード
1 2 3 4 5 6 7 8 9 10 |
from sklearn.neural_network import MLPClassifier clf = MLPClassifier(hidden_layer_sizes=3, learning_rate_init=0.05, random_state=0) # XORデータの準備 xor_flag = True # True: XOR, False: AND X, y = get_fourpoints(xor_flag) # 学習データを取得 print(f' X:\n{X},\n y: {y}') clf.fit(X, y) # 学習(誤差逆伝播法)の実行 plot_decision_boundary(X, y, clf) # 決定境界 plt.show() |
図15 学習曲線や結合荷重などを表示するPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 |
# 学習曲線の表示 plt.plot(range(1, clf.n_iter_ + 1), clf.loss_curve_) plt.xlabel('Epoch') plt.ylabel('Loss') plt.ylim(0,) plt.show() # 構造を確認 print('n_outputs:', clf.n_outputs_) # 出力層の素子数 print('n_layers (with input layer):', clf.n_layers_) # 入力層を含めた層の数 # 結合荷重 print('coefs:\n', clf.coefs_) # バイアス項以外の結合荷重(行列のリスト) print('intercepts:\n', clf.intercepts_) # バイアス項(ベクトルのリスト) |
図17 Breast Cancerデータセットの準備をするPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split from sklearn.metrics import roc_curve, roc_auc_score from sklearn.preprocessing import StandardScaler data = load_breast_cancer() # データセット読み込み X_orig = data['data'] y = 1 - data['target'] # 悪性を1、良性を0とする正解ラベル X = X_orig[:, :10] # 一部の特徴量を利用する # 訓練データとテストデータに2分割する(検証データは今回は切り分けない) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1) # ROCカーブの表示用に関数を準備 def plot_roc(fpr, tpr, marker=None): plt.figure(figsize=(5, 5)) plt.plot(fpr, tpr, marker=marker) plt.xlabel('False Positive Rate') plt.ylabel('True Positive Rate') plt.grid() |
図18 Breast Cancerデータセットによる学習と評価をするPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
clf = MLPClassifier(hidden_layer_sizes=(5, 3), learning_rate_init=0.005, random_state=1) with_scaling = False # (★)標準化するか否か if with_scaling: # 標準化によるスケーリングあり scaler = StandardScaler().fit(X_train) # 訓練データで平均と標準偏差を求める Xscaled_train = scaler.transform(X_train) # X_train -> Xscaled_train Xscaled_test = scaler.transform(X_test) # X_test -> Xscaled_test clf.fit(Xscaled_train, y_train) # 学習 y_proba_train = clf.predict_proba(Xscaled_train) # 訓練データで事後確率予測 y_proba_test = clf.predict_proba(Xscaled_test) # テストデータ事後確率予測 else: # 標準化なし(そのまま) clf.fit(X_train, y_train) # 学習 y_proba_train = clf.predict_proba(X_train) # 訓練データで事後確率予測 y_proba_test = clf.predict_proba(X_test) # テストデータ事後確率予測 # テストデータに対するROCカーブ fpr, tpr, threshold = roc_curve(y_test, y_proba_test[:, 1]) plot_roc(fpr, tpr) plt.show() # 訓練・テストデータのAUC(Area Under the Curve)スコア print('AUC (train):', roc_auc_score(y_train, y_proba_train[:, 1])) print('AUC (test):', roc_auc_score(y_test, y_proba_test[:, 1])) |
図21 PyTorchによる画像認識の準備をするPythonコード
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 |
import torch import torch.nn as nn import torch.nn.functional as F import torchvision from torchvision import datasets from torchvision import transforms as transforms import matplotlib.pyplot as plt import numpy as np # GPUが使えるか否かでデータの転送先を指定 device = 'cuda:0' if torch.cuda.is_available() else 'cpu' img_mean, img_std = (0.1307, 0.3081) # 画素値の標準化用 # 標準化などの前処理を設定 trans = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((img_mean,), (img_std,)) ]) # データセットのダウンロード rootdir = './data' # ダウンロード先 train_dataset = datasets.MNIST(root=rootdir, train=True, transform=trans, download=True) test_dataset = datasets.MNIST(root=rootdir, train=False, transform=trans, download=True) # データを読み込む専用のクラスDataLoaderを準備 batch_size = 32 train_loader = torch.utils.data.DataLoader( dataset=train_dataset, batch_size=batch_size, shuffle=True) test_loader = torch.utils.data.DataLoader( dataset=test_dataset, batch_size=batch_size, shuffle=False) # クラス情報 num_classes = len(train_dataset.classes) cls_names = [f'{i}' for i in range (num_classes)] # 画像表示用の関数を定義しておく def tensor_imshow(img, title=None): """ Tensorを画像として表示 """ img = img.numpy().transpose((1, 2, 0)) # (C,H,W)->(H,W,C) img = img_std * img + img_mean plt.imshow(np.clip(img, 0, 1)) if title is not None: plt.title(title) plt.show() def test_with_examples(model, loader): """ loaderのミニバッチの画像を予測結果と共にグリッド表示 model: 予測に用いるニューラルネット loader: DataLoader """ model.eval() # 推論モード with torch.no_grad(): # 推論のみ(勾配計算なし) imgs, labels = next(iter(loader)) # ミニバッチ取得 imgs_in = imgs.view(-1, imgs.shape[2]*imgs.shape[3]) outputs = model(imgs_in.to(device)) # 順伝播による推論 _, pred = torch.max(outputs, 1) # 予測ラベル grid = torchvision.utils.make_grid(imgs) # グリッド画像生成 title_str1 = '\n'.join( [', '.join([cls_names[y] for y in x]) for x in pred.view(-1, 8).tolist()]) title_str2 = '\n'.join( [', '.join([cls_names[y] for y in x]) for x in labels.view(-1, 8).tolist()]) tensor_imshow(grid, title='Predicted classes:\n' + f'{title_str1}\n\nTrue classes:\n{title_str2}\n') # 訓練データ例の表示 print('\n--- Training data (example) ---\n') imgs, labels = next(iter(train_loader)) grid = torchvision.utils.make_grid(imgs) title_str = '\n'.join([', '.join([cls_names[y] for y in x]) for x in labels.view(-1, 8).tolist()]) tensor_imshow(grid, title=f'True classes:\n{title_str}\n') # ニューラルネットの構造を定義 class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.fc1 = nn.Linear(28*28, 512) self.fc2 = nn.Linear(512, 256) self.fc3 = nn.Linear(256, num_classes) def forward(self, x): x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) # 活性化関数なし(損失関数側でsoftmax) return x net = Net() # インスタンス生成 net = net.to(device) # モデルをGPUへ転送(もしGPUがあれば) # 学習前にテストデータを入力してみる print('\n--- Result BEFORE training ---\n') test_with_examples(net, test_loader) |
図22 PyTorchによるニューラルネットの学習と結果表示をするPythonコード
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 |
# 損失関数(softmax + cross entropy) criterion = nn.CrossEntropyLoss() # 最適化手法の選択 optimizer = torch.optim.SGD(net.parameters(), lr=0.01) # 学習のループ num_epochs = 5 train_loss_log = [] train_acc_log = [] for epoch in range(num_epochs): print(f'Epoch {epoch + 1}/{num_epochs}\n', '-' * 10) # 訓練フェーズ sum_loss, sum_ok = [0.0, 0] for inputs, labels in train_loader: # ミニバッチ入力 inputs = inputs.view(-1, 28*28).to(device) labels = labels.to(device) # deviceに転送 outputs = net(inputs) # 順伝播 loss = criterion(outputs, labels) # 損失計算 optimizer.zero_grad() # 勾配をクリア loss.backward() # 誤差逆伝播 optimizer.step() # パラメータ更新 # ミニバッチの評価値(1バッチ分)を計算 _, preds = torch.max(outputs, 1) # 予測ラベル sum_loss += loss.item() * inputs.size(0) # 損失 sum_ok += torch.sum(preds == labels.data) # 正解数 # 1エポック分の評価値を計算 e_loss = sum_loss / len(train_dataset) # 平均損失 e_acc = sum_ok.cpu().double() / len(train_dataset) # 正解率 print(f'Loss: {e_loss:.4f} Acc: {e_acc:.4f}') train_loss_log.append(e_loss) train_acc_log.append(e_acc) # 学習曲線をプロット fig = plt.figure(figsize=(12, 6)) fig.add_subplot(121) # 損失 plt.plot(range(1, num_epochs + 1), train_loss_log, '.-') plt.title('Training data') plt.xlabel('Epoch') plt.ylabel('Loss') plt.ylim(0) fig.add_subplot(122) # 正解率 plt.plot(range(1, num_epochs + 1), train_acc_log, '.-') plt.title('Training data') plt.xlabel('Epoch') plt.ylabel('Accuracy') plt.show() # テストデータを入力してみる print('\n--- Result AFTER training ---\n') test_with_examples(net, test_loader) |
Pythonあれこれ(Vol.77掲載)
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温
かく見守ってください。皆さんと共に勉強していきましょう。第7回は、Pythonで実装されたWebアプリケーションフレームワーク「Flask」の使い方と、Pythonのユニークな文法である「デコレータ」について紹介します。
シェルスクリプトマガジン Vol.77は以下のリンク先でご購入できます。
図5 ルーティングのサンプルWebアプリケーションのコード
1 2 3 4 5 6 7 8 |
from flask import Flask app = Flask(__name__) @app.route('/hello') def hello_flask(): return '<h1>Hello, Flask!</h1>' @app.route('/goodbye') def goodbye_flask(): return '<h1>Good bye, Flask!</h1>' |
図7 簡単なメッセージを表示するプログラム
1 2 3 4 5 6 7 8 9 10 11 |
#!/usr/bin/env python def hello(): print('Hello, World') def goodbye(): print('Goodbye, World') def main(): hello() goodbye() if __name__ == '__main__': main() |
図9 デコレータを追加したプログラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#!/usr/bin/env python def deco(func): def decorator(): print('-- start --') func() print('-- end --') return decorator @deco def hello(): print('Hello, World') @deco def goodbye(): print('Goodbye, World') def main(): hello() goodbye() if __name__ == '__main__': main() |
図11 Flaskのルーティング設定処理を模したプログラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#!/usr/bin/env python class Flask(): def __init__(self): self.url_map = {} print(f'url_map on init: {self.url_map}') def add_url_rule(self, rule, func): self.url_map[rule] = func def route(self, rule): def decorator(f): self.add_url_rule(rule, f) return f return decorator def dispatch_request(self, rule): return self.url_map[rule]() app = Flask() print(f'url_map after create: {app.url_map}') @app.route('/index') def index(): print("hello world") print(f'url_map after function def: {app.url_map}') app.dispatch_request('/index') |
図13 図9のプログラムを修正した「greeting_deco2.py」
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#!/usr/bin/env python def deco(rule): def decorator(func): print(f'rule = \'{rule}\', func = {func}') return func return decorator @deco('/hello') def hello(): print('Hello, World') @deco('/goodbye') def goodbye(): print('Goodbye, World') |
図15 デコレータを使わないように修正した「greeting_deco3.py」
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#!/usr/bin/env python def deco(rule): def decorator(func): print(f'rule = \'{rule}\', func = {func}') return func return decorator def hello(): print('Hello, World') hello = deco('/hello')(hello) def goodbye(): print('Goodbye, World') goodbye = deco('/goodbye')(goodbye) |
図16 関数の挙動を変えるデコレータの使用例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#!/usr/bin/env python def plus_n(n): def decorator(func): def func_modified(*args, **kwargs): return (func(*args, **kwargs) + n) return func_modified return decorator @plus_n(3) def square(x): return x*x def main(): print(square(2)) if __name__ == '__main__': main() |
図17 関数に複数のデコレータを適用したプログラム「greeting_deco4.py」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#!/usr/bin/env python def DECO(func): def decorator(): print('-- START --') func() print('-- END --') return decorator def deco(func): def decorator(): print('-- start --') func() print('-- end --') return decorator @DECO @deco def hello(): print('Hello, World') def main(): hello() if __name__ == '__main__': main() |
香川大学SLPからお届け!(Vol.77掲載)
著者:石上椋一
毎年年末に開催されている競馬の「有馬記念」というレースをご存知でしょうか。今回は、その有馬記念の順位を予測するAIを開発した話を紹介します。2021年12月には、開発したAIを使って第66回有馬記念の順位を予測してみました。結果がどうだったのかについては、記事の最後に書いています。
シェルスクリプトマガジン Vol.77は以下のリンク先でご購入できます。
図2 RankNetを実装するPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from tensorflow.keras import layers, Model, Input from tensorflow.nn import leaky_relu class RankNet(Model): def __init__(self): super().__init__() self.dense = [layers.Dense(16, activation=leaky_relu), layers.Dense(8, activation=leaky_relu)] self.o = layers.Dense(1, activation='linear') self.oi_minus_oj = layers.Subtract() def call(self, inputs): xi, xj = inputs densei = self.dense[0](xi) densej = self.dense[0](xj) for dense in self.dense[1:]: densei = dense(densei) densej = dense(densej) oi = self.o(densei) oj = self.o(densej) oij = self.oi_minus_oj([oi, oj]) output = layers.Activation('sigmoid')(oij) return output |
図3 データの整理とラベル付けをするPythonコード
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 pandas as pd import numpy as np from itertools import combinations years = [2020,2019,2018,2017,2016,2015,2014,2013,2012,2011] # ここで任意のCSVファイルを指定する # 今回はCSVにデータを残しているため、CSVから読み込んでいる df = pd.read_csv(CSVFile) df["タイム指数2-3"] = df["タイム指数2"] - df["タイム指数3"] index_num = 0 xi = xj = pij = pair_ids = pair_query_id = [] for year in years: one_year_Data = df[df['年数'] == year] index_list = [i for i in range(len(one_year_Data))] random.shuffle(index_list) for pair_id in combinations(index_list, 2): pair_query_id.append(year) pair_ids.append(pair_id) i = pair_id[0] j = pair_id[1] xi.append([one_year_Data.at[i+index_num,"タイム指数2"], one_year_Data.at[i+index_num,"タイム指数2-3"], one_year_Data.at[i+index_num,"上り"]]) xj.append([one_year_Data.at[j+index_num,"タイム指数2"], one_year_Data.at[j+index_num,"タイム指数2-3"], one_year_Data.at[j+index_num,"上り"]]) if one_year_Data.at[i+index_num,"順位"] == one_year_Data.at[j+index_num,"順位"] : pij_com = 0.5 elif one_year_Data.at[i+index_num,"順位"] > one_year_Data.at[j+index_num,"順位"] : pij_com = 0 else: pij_com = 1 pij.append(pij_com) index_num += len(one_year_Data) index_list.clear() xi = np.array(xi) xj = np.array(xj) pij = np.array(pij) pair_query_id = np.array(pair_query_id) |
図4 学習用データと評価用データを仕分けるPythonコード
1 2 3 4 5 |
from sklearn.model_selection import train_test_split xi_train, xi_test, xj_train, xj_test, pij_train, pij_test, \ pair_id_train, pair_id_test = train_test_split( xi, xj, pij, pair_ids, test_size=0.2, stratify=pair_query_id) |
図5 コンパイルと学習を実施するPythonコード
1 2 3 4 5 6 7 |
ranknet = RankNet() # コンパイル ranknet.compile(optimizer='sgd', loss='binary_crossentropy',metrics=['accuracy']) # 学習 ranknet.fit([xi_train, xj_train], pij_train, epochs=85, batch_size=4, validation_data=([xi_test, xj_test], pij_test)) |
図8 ResNet50を使った学習モデルを実装するPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from tensorflow.keras.applications.resnet50 import ResNet50 from keras.layers import Dense, Dropout, Input, Flatten # ResNetの準備 input_tensor = Input(shape=(64, 64, 3)) resnet50 = ResNet50(include_top=False, weights='imagenet', input_tensor=input_tensor) # FC層の準備 fc_model = Sequential() fc_model.add(Flatten(input_shape=resnet50.output_shape[1:])) fc_model.add(Dense(512, activation='relu')) fc_model.add(Dropout(0.3)) fc_model.add(Dense(2, activation='sigmoid')) # モデルの準備 resnet50_model = Model(resnet50.input, fc_model(resnet50.output)) # ResNet50の一部の重みを固定 for layer in resnet50_model.layers[:100]: layer.trainable = False |
図9 コンパイルと学習を実施するPythonコード
1 2 3 4 5 6 7 8 9 |
# コンパイル resnet50_model.compile(optimizer='sgd', loss='categorical_crossentropy', metrics=['accuracy']) # 学習 history = resnet50_model.fit(train_generator, batch_size=4, epochs=50, verbose=1, validation_data=val_generator) |
シェルスクリプトマガジンvol.76 Web掲載記事まとめ
004 レポート Visual Studio 2022と.NET 6
005 レポート 超高速リンカー「mold」
008 特集1 Linuxカーネルの仕組み/平田豊 コード掲載
024 特集2 micro:bit v2を使おう/斉暁
034 特集3 国産のGroupSession/大場雄一郎
048 Raspberry Piを100%活用しよう/米田聡 コード掲載
050 機械学習ことはじめ/川嶋宏彰 コード掲載
061 Hello Nogyo!
062 香川大学SLPからお届け!/三枝泰士
066 法林浩之のFIGHTING TALKS/法林浩之
068 タイ語から分かる現地生活/つじみき
072 中小企業手作りIT化奮戦記/菅雄一
076 デジタル庁/桑原滝弥、イケヤシロウ
078 Pythonあれこれ/飯尾淳 コード掲載
084 特別レポート/松浦智之 コード掲載
090 Bash入門/大津真
098 Techパズル/gori.sh
099 コラム「ユニケージ流プロジェクトの初期段階」/シェル魔人
特集1 Linuxカーネルの仕組み(Vol.76記載)
著者:平田 豊
LinuxというOSの中核となるのが「Linuxカーネル」というソフトウエアです。Linuxカーネルの動作を理解するには、ソースコードを追いかける必要があります。しかし、いきなりソースコードを読んでもよく分からないものです。本特集記事では、学習の「入り口」となるように、学習用の環境を構築する方法を紹介します。また、Linuxカーネルの一部の機能をピックアップして、その実装について解説します。本特集をきっかけにLinuxカーネルのソースコード読解を始めましょう。
シェルスクリプトマガジン Vol.76は以下のリンク先でご購入できます。
図1 OS レスプログラムのイメージ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
for (;;) { // スイッチが押された if (isSwitchOn()) { // 要因クリア ClearSwitch(); // LED点灯 TurnOnLED(); } // スイッチが離された if (isSwitchOff()) { // 要因クリア ClearSwitch(); // LED点灯 TurnOffLED(); } // ディレー delay(); } |
図7 QEMU の起動用シェルスクリプト
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#!/bin/sh BUILD_PATH="${HOME}/buildroot-2021.08.1" IMAGE_DIR="${BUILD_PATH}/output/images" exec qemu-system-aarch64 \ -M virt \ -cpu cortex-a53 \ -nographic \ -smp 2 \ -m 512 \ -kernel ${IMAGE_DIR}/Image \ -append "rootwait root=/dev/vda console=ttyAMA0" \ -drive file=${IMAGE_DIR}/rootfs.ext4,if=none,format=raw,id=hd0 \ -device virtio-blk-device,drive=hd0 \ -netdev user,id=eth0 \ -device virtio-net-device,netdev=eth0 \ -object rng-random,filename=/dev/urandom,id=rng0 \ -device virtio-rng-pci,rng=rng0 |
図8 Linux カーネルに追加するコード
1 2 3 4 5 6 7 |
(略) static int run_init_process(const char *init_filename) { const char *const *p; printk("hoge 0x%016lx (%zu)\n", jiffies, sizeof(jiffies)); argv_init[0] = init_filename; (略) |
図11 init デーモンを起動する箇所のソースコード
1 2 3 4 5 6 7 8 |
if (!try_to_run_init_process("/sbin/init") || !try_to_run_init_process("/etc/init") || !try_to_run_init_process("/bin/init") || !try_to_run_init_process("/bin/sh")) return 0; panic("No working init found. Try passing init= option to kernel. " "See Linux Documentation/admin-guide/init.rst for guidance."); |
図12 buildroot 環境のC ライブラリにおけるsyscall 関数の実装
1 2 3 4 5 6 7 8 9 10 11 12 13 |
ENTRY (syscall) uxtw x8, w0 mov x0, x1 mov x1, x2 mov x2, x3 mov x3, x4 mov x4, x5 mov x5, x6 mov x6, x7 svc 0x0 cmn x0, #4095 b.cs 1f RET |
図13 システムコールに対応する関数を呼び出すためのマクロ定義の例
1 2 3 4 5 6 7 8 |
#define __SYSCALL_DEFINEx(x, name, ...) \ (略) asmlinkage long __arm64_sys##name(const struct pt_regs *regs) \ { \ return __se_sys##name(SC_ARM64_REGS_TO_ARGS(x,__VA_ARGS__)); \ } \ (略) static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) |
図14 machine_halt() 関数のコード
1 2 3 4 5 6 |
void machine_halt(void) { local_irq_disable(); smp_send_stop(); while (1); } |
図15 machine_power_off() 関数のコード
1 2 3 4 5 6 7 |
void machine_power_off(void) { local_irq_disable(); smp_send_stop(); if (pm_power_off) pm_power_off(); } |
図17 schedule() 関数のコード
1 2 3 4 5 6 7 8 9 10 11 12 13 |
asmlinkage __visible void __sched schedule(void) { struct task_struct *tsk = current; sched_submit_work(tsk); do { preempt_disable(); __schedule(false); sched_preempt_enable_no_resched(); } while (need_resched()); sched_update_worker(tsk); } EXPORT_SYMBOL(schedule); |
図18 FIFO からのデータを出力するプログラムのコード
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 |
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char **argv){ int i, fd = -1; ssize_t sz; char *filename, buf[64]; if (argc == 1) { printf("Usage: %s fifo\n", argv[0]); exit(1); } filename = argv[1]; fd = open(filename, O_RDWR); if (fd == -1) { perror("open"); goto error; } sz = read(fd, buf, sizeof(buf)); if (sz == -1) { perror("read"); goto error; } for (i = 0 ; i < sz ; i++) { printf("%02x ", buf[i]); } printf("\n"); error: if (fd != -1) close(fd); } |
図19 wait_event_interruptible_exclusive() 関数のマクロ定義
1 2 3 4 5 6 7 8 9 10 11 12 |
#define __wait_event_interruptible_exclusive(wq, condition) \ ___wait_event(wq, condition, TASK_INTERRUPTIBLE, 1, 0, \ schedule()) #define wait_event_interruptible_exclusive(wq, condition) \ ({ \ int __ret = 0; \ might_sleep(); \ if (!(condition)) \ __ret = __wait_event_interruptible_exclusive(wq, condition); \ __ret; \ }) |
図20 msleep() 関数のコード
1 2 3 4 5 6 7 |
void msleep(unsigned int msecs) { unsigned long timeout = msecs_to_jiffies(msecs) + 1; while (timeout) timeout = schedule_timeout_uninterruptible(timeout); } |
図21 process_timeout() 関数とschedule_timeout() 関数のコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
static void process_timeout(struct timer_list *t) { struct process_timer *timeout = from_timer(timeout, t, timer); wake_up_process(timeout->task); } (略) signed long __sched schedule_timeout(signed long timeout) { (略) expire = timeout + jiffies; timer.task = current; timer_setup_on_stack(&timer.timer, process_timeout, 0); __mod_timer(&timer.timer, expire, MOD_TIMER_NOTPENDING); schedule(); (略) } |
図22 mutex_lock() 関数のコード
1 2 3 4 5 6 7 |
void __sched mutex_lock(struct mutex *lock) { might_sleep(); if (!__mutex_trylock_fast(lock)) __mutex_lock_slowpath(lock); } |
図23 preempt_enable() 関数のマクロ定義
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#ifdef CONFIG_PREEMPTION #define preempt_enable() \ do { \ barrier(); \ if (unlikely(preempt_count_dec_and_test())) \ __preempt_schedule(); \ } while (0) #else /* !CONFIG_PREEMPTION */ #define preempt_enable() \ do { \ barrier(); \ preempt_count_dec(); \ } while (0) |
図24 ヘッダーファイルでのjiffies 変数の宣言
1 2 |
extern u64 jiffies_64; extern unsigned long volatile jiffies; |
図25 jiffies_64 変数の実体の定義
1 |
u64 jiffies_64 = INITIAL_JIFFIES; |
図26 INITIAL_JIFFIES 定数の定義
1 |
#define INITIAL_JIFFIES ((unsigned long)(unsigned int) (-300*HZ)) |
Raspberry Piを100%活用しよう(Vol.76掲載)
著者:米田 聡
小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第9回は、人感センサーを搭載する拡張基板を扱います。
シェルスクリプトマガジン Vol.76は以下のリンク先でご購入できます。
図3 焦電赤外線センサ拡張基板のサンプルプログラム(ADRSZPY.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 |
import RPi.GPIO as GPIO import time IR_PORT=5 GPIO.setmode(GPIO.BCM) GPIO.setup(IR_PORT, GPIO.IN) try: while True: if GPIO.input(IR_PORT) == GPIO.HIGH: print("There is human") else: print("Nothing") time.sleep(1) except KeyboardInterrupt: pass GPIO.cleanup() |
機械学習ことはじめ(Vol.76掲載)
著者:川嶋 宏彰
本連載では、機械学習の基礎となるさまざまな手法の仕組みや、それらの手法のPythonでの利用方法を解説していきます。今回は、複数のモデルを束ねて用いるアンサンブル学習について解説します。
シェルスクリプトマガジン Vol.76は以下のリンク先でご購入できます。
図2 データセットの読み込みから、散布図のプロットまでを実施するPythonコード
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 |
import seaborn as sns import matplotlib.pyplot as plt plt.rcParams['font.size'] = 14 penguins = sns.load_dataset('penguins') # 取り出す特徴量 features = ['bill_depth_mm', 'body_mass_g'] # (★) # features = ['bill_depth_mm', 'bill_length_mm'] # 取り出す特徴量を変える # 対象とするペンギン種 target_species = ['Adelie', 'Gentoo'] # (★) # target_species = ['Adelie', 'Chinstrap', 'Gentoo'] # 3種のペンギンにする # 今回用いる特徴量をクラスラベルと共に取り出す df = penguins[['species'] + features].copy() df.dropna(inplace=True) # NaN が含まれる行は削除 # 今回用いるペンギン種のみとする df2 = df[df['species'].isin(target_species)].copy() print(df2.shape) # (274, 3) と表示される # 現在のパレットから用いる種 (target_species) に合わせたパレットを作成 palette = {c: sns.color_palette()[k] for k, c in enumerate(df['species'].unique()) if c in target_species} plt.figure(figsize=(5, 5)) sns.scatterplot(data=df2, x=features[0], y=features[1], hue='species', palette=palette) plt.show() X = df2[features].values # 各個体の2次元特徴量(行数=個体数) y = df2['species'].values # 各個体のクラスラベル |
図3 決定木による2クラス分類をするPythonコード
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 matplotlib as mpl import numpy as np from sklearn.tree import DecisionTreeClassifier, export_text, plot_tree def plot_decision_boundary(X, y, clf, xylabels=None, palette=None, fig=None, ngrid=50): """ 分類器 clf の決定境界を描画 """ if fig is None: fig = plt.figure(figsize=(5, 5)) else: plt.figure(fig.number) # 2次元空間にグリッド点を準備 xmin = X.min(axis=0) # 各列の最小値 xmax = X.max(axis=0) # 各列の最大値 xstep = [(xmax[j]-xmin[j]) / ngrid for j in range(2)] # グリッドのステップ幅 xmin = [xmin[j] - 8*xstep[j] for j in range(2)] # 少し広めに xmax = [xmax[j] + 8*xstep[j] for j in range(2)] aranges = [np.arange(xmin[j], xmax[j] + xstep[j], xstep[j]) for j in range(2)] x0grid, x1grid = np.meshgrid(*aranges) # 各グリッド点でクラスを判定 y_pred = clf.predict(np.c_[x0grid.ravel(), x1grid.ravel()]) y_pred = y_pred.reshape(x0grid.shape) # 2次元に y_pred = np.searchsorted(np.unique(y_pred), y_pred) # 値をインデックスに clist = palette.values() if type(palette) is dict else palette cmap = mpl.colors.ListedColormap(clist) if palette is not None else None plt.contourf(x0grid, x1grid, y_pred, alpha=0.3, cmap=cmap) sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=y, palette=palette) plt.legend() plt.xlim([xmin[0], xmax[0]]) plt.ylim([xmin[1], xmax[1]]) if xylabels is not None: plt.xlabel(xylabels[0]) plt.ylabel(xylabels[1]) return fig # 決定木 max_depth = 2 #(★)木の深さ clf_dt = DecisionTreeClassifier(max_depth=max_depth) clf_dt.fit(X, y) # 学習 # 決定境界を可視化 plot_decision_boundary(X, y, clf_dt, features, palette) plt.show() # 決定木をテキストで表示 tree_text = export_text(clf_dt) print(tree_text) # 木の可視化 plt.figure(figsize=(10, 8)) plot_tree(clf_dt, class_names=target_species, feature_names=features) plt.show() |
図13 アンサンブル学習された決定木の表示用関数を定義するPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
from sklearn.base import is_classifier def plot_trees_and_boundaries(clf, X, y): # 決定境界を可視化 plot_decision_boundary(X, y, clf, features, palette) plt.show() # 各ベースモデル(決定木)を表示 for i, clf_dt in enumerate(clf.estimators_[:3]): print('-' * 10, i, '-' * 10) plt.figure(figsize=(10, 8)) plot_tree(clf_dt, class_names=target_species, feature_names=features) plt.show() if is_classifier(clf_dt): # 分類木ならば決定境界を可視化 plot_decision_boundary(X, y, clf_dt, features, palette) plt.show() |
図14 ランダムフォレストによる分類器の学習をするPythonコード
1 2 3 4 5 |
from sklearn.ensemble import RandomForestClassifier clf_rf = RandomForestClassifier(n_estimators=100, random_state=1) clf_rf.fit(X, y) plot_trees_and_boundaries(clf_rf, X, y) |
図16 構成モデルに決定木を用いたバギングをするPythonコード
1 2 3 4 5 |
from sklearn.ensemble import BaggingClassifier clf_bag = BaggingClassifier(DecisionTreeClassifier(), n_estimators=100, random_state=1) clf_bag.fit(X, y) |
図18 構成モデルに決定木を用いたアダブーストをするPythonコード
1 2 3 4 5 6 |
from sklearn.ensemble import AdaBoostClassifier clf_ada = AdaBoostClassifier(DecisionTreeClassifier(max_depth=2), n_estimators=100) clf_ada.fit(X, y) plot_trees_and_boundaries(clf_ada, X, y) |
図20 構成モデル(回帰木)の数を変えながら勾配ブースティングをするPythonコード
1 2 3 4 5 6 7 8 |
from sklearn.ensemble import GradientBoostingClassifier for m in [2, 4, 10, 20, 50]: clf_gbdt = GradientBoostingClassifier(n_estimators=m) clf_gbdt.fit(X, y) # 学習 plot_decision_boundary(X, y, clf_gbdt, features, palette) plt.title(f'GradientBoosting m = {m}') plt.show() |
図22 XGBoost による学習をするPythonコード
1 2 3 4 5 6 7 8 |
from xgboost import XGBClassifier # クラスラベルを整数0、1、2に変換 y_int = df2['species'].map({'Adelie': 0, 'Chinstrap': 1, 'Gentoo': 2}).values clf_xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss') clf_xgb.fit(X, y_int) plot_decision_boundary(X, y, clf_xgb, features, palette) plt.show() |
Pythonあれこれ(Vol.76掲載)
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第6回は、やや趣向を変えてアルゴリズムを解説します。前半では関数の再帰的定義とその効率化、後半ではソーティングアルゴリズムについて紹介します。
シェルスクリプトマガジン Vol.76は以下のリンク先でご購入できます。
図1 再帰を使わずに階乗を求める関数を定義した例
1 2 3 4 5 |
def factorial(n): retvar = 1 for i in range(n): retvar = retvar * i return retvar |
図4 メモ化アルゴリズムを使って効率化したfib() 関数の定義例
1 2 3 4 5 6 7 |
memo = [0] * 100 memo[0] = memo[1] = 1 def fib(n): if memo[n-1] == 0: memo[n-1] = fib(n-1) if memo[n-2] == 0: memo[n-2] = fib(n-2) if memo[n] == 0: memo[n] = memo[n-1] + memo[n-2] return memo[n] |
図7 バブルソートを実施する関数の実装例
1 2 3 4 5 6 7 8 |
def bubble_sort(x): y = x[:] for i in range(len(y)-1): for j in range(len(y)-1,i,-1): if y[j-1] > y[j]: tmp = y[j-1]; y[j-1] = y[j]; y[j] = tmp print(y) return y |
図9 マージソートを実施する関数の実装例
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def merge_sort(x): retary = [] if len(x) <= 1: retary.extend(x) else: m = len(x) // 2 first = merge_sort(x[:m]) second = merge_sort(x[m:]) while len(first) > 0 and len(second) > 0: retary.append(first.pop(0) \ if first[0] < second[0] else second.pop(0)) retary.extend(first if len(first) > 0 else second) return retary |
図11 ヒープソートを実施する関数の実装例
1 2 3 4 5 6 7 8 |
from heapq import heappush, heappop def heap_sort(x): retary = [] heap = [] for i in x: heappush(heap, i) while len(heap) > 0: retary.append(heappop(heap)) return retary |
特別レポート(Vol.76掲載)
著者:松浦智之
2021年6月2~4日に技術カンファレンス「ソフトウェア・シンポジウム 2021 in 大分」がオンラインで開催されました。筆者が所属するユニバーサル・シェル・プログラミング研究所は、そのカンファレンスに二つの論文を投稿しました。その中で最優秀発表賞に選ばれた「UNIX機におけるIoT機器制御のためのタイミング管理」を紹介します。
シェルスクリプトマガジン Vol.76は以下のリンク先でご購入できます。
図2 シェルスクリプト(Accurate_interval.sh)
1 2 3 4 5 6 7 8 9 |
#!/bin/bash i=1 yes | valve -l 5s | while read dummy; do wget WebサイトのURL/${i}.txt [ $i -ge 100 ] && break i=$((i+1)) done |
Pythonあれこれ(Vol.75掲載)
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第5回は、「Google Colaboratory」というクラウドサービスで、対話的にPythonコードを実行する方法を紹介します。
シェルスクリプトマガジン Vol.75は以下のリンク先でご購入できます。
図13 タートルグラフィックスで幾何学模様を描くPythonコード
1 2 3 4 5 6 7 8 9 10 11 |
from ColabTurtle.Turtle import * initializeTurtle(initial_speed=10) color('red') bgcolor('white') width(1) x0 = pos()[0] y0 = pos()[1] while True: forward(200) left(170) if abs((pos()[0]-x0)**2 + (pos()[1]-y0)**2) < 1: break |
図19 指定ファイルの各行をリストとして読み込み、その内容を表示するPythonコード
1 2 3 4 5 |
with open('/content/drive/MyDrive/TSdata.csv') as f: while True: line = f.readline().rstrip() if line == '': break print(line.split(',')) |
シェルスクリプトマガジンvol.75 Web掲載記事まとめ
004 レポート Windows 11の導入メディア作成
005 レポート YouTube配信に便利なOBS Studio
006 NEWS FLASH
008 特集1 Windows 11 徹底解説/三沢友治
022 特集2 Javaの最新動向/伊藤智博 コード掲載
036 特集3 Debian 11の新機能&改善点/やまねひでき
044 特別企画 ラズパイで作る簡単VPN環境/魔法少女 コード掲載
058 Raspberry Piを100%活用しよう/米田聡 コード掲載
061 Hello Nogyo!
062 機械学習ことはじめ/川嶋宏彰 コード掲載
072 テレワーク/桑原滝弥、イケヤシロウ
074 Pythonあれこれ/飯尾淳 コード掲載
080 タイ語から分かる現地生活/つじみき
084 法林浩之のFIGHTING TALKS/法林浩之
086 中小企業手作りIT化奮戦記/菅雄一
090 香川大学SLPからお届け!/平西宏彰 コード掲載
096 Bash入門/大津真
104 Techパズル/gori.sh
105 コラム「システムエンジニアを育てる」/シェル魔人
特集2 Javaの最新動向(Vol.75記載)
著者:伊藤 智博
エンタープライズシステムの分野で活用されている、プログラミング言語および開発・実行環境の「Java」。開発元である米旧Sun Microsystems社の買収、OpenJDKとしてのオープンソース化など、さまざまな変化がありました。本特集では、Javaの最新動向について分かりやすく解説します。
シェルスクリプトマガジン Vol.75は以下のリンク先でご購入できます。
図13 HelloWorld.javaのソースコード
1 2 3 4 5 6 7 |
public class HelloWorld { public static void main(String... args) { String message = "Hello World"; System.out.println(message); } } |
図17 GreetingResource.javaのソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package org.acme; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("/hello") public class GreetingResource { @GET @Produces(MediaType.TEXT_PLAIN) public String hello() { return "Hello RESTEasy"; } } |
図20 変更後のソースコード
1 2 3 4 5 |
@GET @Produces(MediaType.TEXT_PLAIN) public String hello() { return "Hello World"; } |
図23 NullPointerExceptionが発生するコード
1 2 3 4 5 6 7 8 9 10 |
public class NullPo { public static void main(String[] args) { Object o = createObject(); o.toString(); } private static Object createObject(){ return null; } } |
図26 HTMLによるコード
1 2 3 4 5 |
<html> <body> <p>Hello, world</p> </body> </html> |
図27 HTMLによるコードをJavaでエスケープと結合で実装
1 2 3 4 5 6 |
String html = "<html>\n" + " <body>\n" + " <p>Hello, world</p>\n" + " </body>\n" + "</html>\n"; |
図28 HTMLによるコードをテキストブロックで実装
1 2 3 4 5 6 7 8 |
String html = """ <html> <body> <p>Hello, world</p> </body> </html> """; |
特別企画 ラズパイで作る簡単VPN環境(Vol.75記載)
著者:魔法少女
職場と自宅のネットワークをインターネット経由で安全につなぐ方法として「VPN」(Virtual Private Network)があります。本企画では、小型コンピュータボード「Raspberry Pi」を利用して誰でも簡単にできるVPN環境構築方法を紹介します。
シェルスクリプトマガジン Vol.75は以下のリンク先でご購入できます。
図43 NAPTの追記
1 2 3 4 5 6 7 |
[Interface] PrivateKey = XXXXXXXXXXXXXXXXXXXXX= Address = 10.6.0.1/24 MTU = 1420 ListenPort = 51820 PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE |
Raspberry Piを100%活用しよう(Vol.75掲載)
著者:米田 聡
小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第8回は、色を認識できるカラーセンサーを搭載する拡張基盤を扱います。
シェルスクリプトマガジン Vol.75は以下のリンク先でご購入できます。
図4adrszCS_CSmode_sample.pyの変更箇所
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
(略) import json import RPi.GPIO as GPIO LED = 18 i2c = smbus.SMBus(1) (略) #while True: GPIO.setmode(GPIO.BCM) GPIO.setup(LED, GPIO.OUT) GPIO.output(LED, GPIO.HIGH) time.sleep(1) # LED点灯 (略) print(out_msg) GPIO.cleanup(LED) (略) |
機械学習ことはじめ(Vol.75掲載)
著者:川嶋 宏彰
本連載では、機械学習の基礎となるさまざまな手法の仕組みや、それらの手法のPythonでの利用方法を解説していきます。今回は、入力データがどのようなクラス(カテゴリ)であるかを予測する分類の問題を扱います。
シェルスクリプトマガジン Vol.75は以下のリンク先でご購入できます。
図2 データセットの読み込みから、散布図のプロットまでを実施するPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import seaborn as sns import matplotlib.pyplot as plt plt.rcParams['font.size'] = 14 penguins = sns.load_dataset('penguins') # 取り出す特徴量 features = ['bill_depth_mm', 'body_mass_g'] # (★) # features = ['bill_depth_mm', 'bill_length_mm'] # 取り出す特徴量を変える # 対象とするペンギン種 target_species = ['Adelie', 'Gentoo'] # (★) # target_species = ['Adelie', 'Chinstrap', 'Gentoo'] # 3種のペンギンにする # 今回用いる特徴量をクラスラベルと共に取り出す df = penguins[['species'] + features].copy() df.dropna(inplace=True) # NaN が含まれる行は削除 # 今回用いるペンギン種のみとする df2 = df[df['species'].isin(target_species)].copy() print(df2.shape) # (274, 3) と表示される # 現在のパレットから用いる種 (target_species) に合わせたパレットを作成 palette = {c: sns.color_palette()[k] for k, c in enumerate(df['species'].unique()) if c in target_species} fig = plt.figure(figsize=(5, 5)) sns.scatterplot(data=df2, x=features[0], y=features[1], hue='species', palette=palette) plt.show() |
図3 ロジスティック回帰の準備をするPythonコード
1 2 3 4 5 6 |
from sklearn.linear_model import LogisticRegression import numpy as np X = df2[features].values # 各個体の2次元特徴量(行数 = 個体数) y = df2['species'].values # 各個体のクラスラベル clf_lr = LogisticRegression(penalty='none') # 分類モデルの準備 |
図4 特徴量一つ(体重)のみでロジスティック回帰をするPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
f_idx = 1 # 用いる特徴量のインデックス # 指定した列(特徴量)だけ取り出して学習 clf_lr.fit(X[:, [f_idx]], y) # 学習されたパラメータを表示 w0 = clf_lr.intercept_[0] w1 = clf_lr.coef_[0][0] print(f'w_0 = {w0:.2f}, w_1 = {w1:.4f}, b = -w0/w1 = {-w0/w1:.2f}') # 予測値および分類結果の可視化用グリッド xmin = np.min(X[:, f_idx]) xmax = np.max(X[:, f_idx]) xs = np.linspace(xmin - (xmax - xmin) / 10, xmax + (xmax - xmin) / 10, 100) # 等間隔に100点用意 fig = plt.figure(figsize=(10, 5)) # 元データのプロット sns.scatterplot(x=X[:, f_idx], y=(y=='Gentoo'), hue=y, palette=palette) # 予測値(事後確率)の曲線をプロット y_pred = clf_lr.predict_proba(xs.reshape(-1, 1))[:, 1] plt.plot(xs, y_pred, c=palette['Gentoo']) plt.ylim(-0.1, 1.1) plt.xlabel(f'x ({features[f_idx]})') plt.ylabel('y') plt.grid() plt.show() |
図6 特徴量二つ(くちばしの高さと体重)でロジスティック回帰をするPythonコード
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 |
import matplotlib as mpl from sklearn.preprocessing import StandardScaler def plot_decision_boundary(X, y, clf, xylabels=None, palette=None, fig=None, ngrid=50): """ 分類器 clf の決定境界を描画 """ if fig is None: fig = plt.figure(figsize=(5, 5)) else: plt.figure(fig.number) # 2次元空間にグリッド点を準備 xmin = X.min(axis=0) # 各列の最小値 xmax = X.max(axis=0) # 各列の最大値 xstep = [(xmax[j]-xmin[j]) / ngrid for j in range(2)] # グリッドのステップ幅 xmin = [xmin[j] - 8*xstep[j] for j in range(2)] # 少し広めに xmax = [xmax[j] + 8*xstep[j] for j in range(2)] aranges = [np.arange(xmin[j], xmax[j] + xstep[j], xstep[j]) for j in range(2)] x0grid, x1grid = np.meshgrid(*aranges) # 各グリッド点でクラスを判定 y_pred = clf.predict(np.c_[x0grid.ravel(), x1grid.ravel()]) y_pred = y_pred.reshape(x0grid.shape) # 2次元に y_pred = np.searchsorted(np.unique(y_pred), y_pred) # 値をインデックスに clist = palette.values() if type(palette) is dict else palette cmap = mpl.colors.ListedColormap(clist) if palette is not None else None plt.contourf(x0grid, x1grid, y_pred, alpha=0.3, cmap=cmap) sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=y, palette=palette) plt.legend() plt.xlim([xmin[0], xmax[0]]) plt.ylim([xmin[1], xmax[1]]) if xylabels is not None: plt.xlabel(xylabels[0]) plt.ylabel(xylabels[1]) return fig Xs = StandardScaler().fit_transform(X) # 標準化によるスケーリング clf_lr.fit(Xs, y) # 二つの特徴量を両方用いて学習 print('w0:', clf_lr.intercept_) print('w1, w2:', clf_lr.coef_) xylabels = [s + ' (standardized)' for s in features] # 軸ラベル変更 plot_decision_boundary(Xs, y, clf_lr, xylabels, palette, ngrid=100) # 決定境界 plt.show() |
図9 ロジスティック回帰による各点での予測値をプロットするPythonコード
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 matplotlib import cm from mpl_toolkits.mplot3d import axes3d # メッシュで def gen_2dgrid(X): """ 2次元メッシュグリッドの生成 """ d = X.shape[1] xmin = X.min(axis=0) # 各列の最小値 xmax = X.max(axis=0) # 各列の最大値 xstep = [(xmax[j]-xmin[j]) / 100 for j in range(d)] # グリッドのステップ幅 xmin = [xmin[j] - 10*xstep[j] for j in range(d)] # 少し広めに xmax = [xmax[j] + 10*xstep[j] for j in range(d)] aranges = [np.arange(xmin[j], xmax[j] + xstep[j], xstep[j]) for j in range(2)] return np.meshgrid(*aranges) # d=2のときは (x0grid, x1grid) が返る # 2次元の場合の予測値 (proba) def proba_2dgrid(clf, x0grid, x1grid): """ 2次元メッシュの各点での予測値を計算 """ return clf.predict_proba(np.c_[x0grid.ravel(), x1grid.ravel()])[:, 1] .reshape(x0grid.shape) xgrid_2d = gen_2dgrid(Xs) # xgrid_2d: (x0grid, x1grid) のような二つ組 proba_2d = proba_2dgrid(clf_lr, *xgrid_2d) # 予測値 # 3次元プロット fig = plt.figure(figsize=(14, 8)) ax = fig.add_subplot(111, projection='3d') cmap = cm.winter # カラーマップを設定 ax.plot_surface(*xgrid_2d, proba_2d, cmap=cmap) # ax.set_box_aspect((3, 3, 1)) # matplotlib のバージョンが3.3以上で利用可能 ax.set_zlim(-0.1, 1.1) ax.set_xlabel(xylabels[0]) ax.set_ylabel(xylabels[1]) ax.set_zlabel('y') ax.set_zticks([0, 0.5, 1.0]) ax.view_init(elev=60, azim=-120) # 3次元プロットの表示角度の設定 plt.show() |
図12 線形SVM による学習と決定境界の表示をするPythonコード
1 2 3 4 5 6 7 8 9 10 11 |
from sklearn.svm import SVC C = 10 # 大きいほどハードマージンに近い clf_lin = SVC(kernel='linear', C=C) clf_lin.fit(Xs, y) # 決定境界とサポートベクターを可視化 plot_decision_boundary(Xs, y, clf_lin, xylabels, palette, ngrid=100) plt.scatter(clf_lin.support_vectors_[:, 0], clf_lin.support_vectors_[:, 1], s=50, facecolors='none', edgecolors='r') plt.title(f'SVM(linear) C = {C}') plt.show() |
図17 非線形SVMとパラメータCの関係を示すPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from sklearn.datasets import make_moons X_moon, y_moon = make_moons(n_samples=100, noise=0.2, random_state=6) palette_o = {k: sns.color_palette()[k] for k in range(2)} # moonデータの散布図をプロット plt.figure(figsize=(5, 5)) sns.scatterplot(x=X_moon[:, 0], y=X_moon[:, 1], hue=y_moon, palette=palette_o) plt.show() # 異なるCでSVM(RBFカーネル)の学習と決定境界の表示 for C in [0.1, 10, 10000]: clf_rbf = SVC(C=C, kernel='rbf') # kernel='rbf'は省略可能 clf_rbf.fit(X_moon, y_moon) # 決定境界をプロット plot_decision_boundary(X_moon, y_moon, clf_rbf, None, palette_o, ngrid=100) plt.title(f'SVM(rbf) C = {C}') plt.show() |
図20 グリッドサーチをするPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from sklearn.model_selection import cross_val_score best_score = -1 for gamma in [0.1, 0.5, 1, 2, 10]: # 5通り for C in [0.1, 1, 10, 100, 1000, 10000]: # 6通り print(f'gamma: {gamma},\tC: {C}', end='') svm = SVC(gamma=gamma, C=C) cv_scores = cross_val_score(svm, X_moon, y_moon, cv=5) score = np.mean(cv_scores) print(f'\t | average: {score:.2} <- {cv_scores}') if score > best_score: best_score = score best_params = {'C': C, 'gamma': gamma} print('best_score:', best_score) print('best_params:', best_params) clf_rbf = SVC(**best_params) clf_rbf.fit(X_moon, y_moon) plot_decision_boundary(X_moon, y_moon, clf_rbf, None, palette_o, ngrid=100) title = f"C = {best_params['C']}, gamma = {best_params['gamma']}" plt.title(title) plt.show() |
香川大学SLPからお届け!(Vol.75掲載)
著者:平西 宏彰
プログラミング言語で記述されたプログラムを解釈して、実行可能な形式に変換するのがコンパイラの役割です。コンパイラは、簡単なものであれば200行程度のコードで作成できます。今回は、整数の四則演算用の数式を処理できるコンパイラを作成します。
シェルスクリプトマガジン Vol.75は以下のリンク先でご購入できます。
図4 「main.go.y」ファイルに最初に記述するコード
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 |
%{ package main import ( "fmt" "log" "strconv" "github.com/llir/llvm/ir" "github.com/llir/llvm/ir/constant" "github.com/llir/llvm/ir/types" "github.com/llir/llvm/ir/value" ) %} %% %% type Node struct { // 構文木 ID string // 構文の種類 Value string // 構文が持つ値 Left *Node Right *Node } type Lexer struct { // 字句解析器 src string // ソースコード index int // 調査中のインデックス node *Node // 構文木 } func (l *Lexer) Error(err string) { // エラー処理 log.Fatal(err) } func main() { var code string fmt.Scan(&code) // コード入力 lexer := &Lexer{src: code, index: 0} // 字句解析 yyParse(lexer) // 構文解析 generator(lexer.node) // コード生成 } |
図5 「main.go.y」ファイルの宣言部に追加するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
%union { num int ope string node *Node } %type<node> program expr %token<num> NUMBER %token<ope> ADD SUB MUL DIV %left ADD, SUB %left MUL, DIV |
図6 「main.go.y」ファイルのプログラム部に追加するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
func (l *Lexer) Lex(lval *yySymType) int { if len(l.src) <= l.index { return -1 } c := l.src[l.index] l.index++ if c == '+' { return ADD } if c == '-' { return SUB } if c == '*' { return MUL } if c == '/' { return DIV } if '0' <= c && c <= '9' { la := l.index for la < len(l.src) && '0' <= l.src[la] && l.src[la] <= '9' { la++ } num, _ := strconv.Atoi(l.src[l.index-1:la]) lval.num = num l.index = la return NUMBER } return -1 } |
図7 「main.go.y」ファイルの規則部に記述するコード
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 |
program // 構文解析を開始するための構文 : expr { $$ = $1 yylex.(*Lexer).node = &Node{} // nodeの初期化 // 式の構文解析で生成された構文木をprogram構文に yylex.(*Lexer).node = $$ } expr // 式の構文 : NUMBER { // 生成規則が数値のときの動作を記述 $$ = &Node{ ID: "NUMBER", Value: strconv.Itoa($1), } } | expr ADD expr { $$ = &Node{ ID: "expr", Value: "+", Left: $1, // 左の式 Right: $3, // 右の式 } } | expr SUB expr { $$ = &Node{ ID: "expr", Value: "-", Left: $1, Right: $3, } } | expr MUL expr { $$ = &Node{ ID: "expr", Value: "*", Left: $1, Right: $3, } } | expr DIV expr { $$ = &Node{ ID: "expr", Value: "/", Left: $1, Right: $3, } } |
図8 「main.go.y」ファイルのプログラム部に追加するコード
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 |
func generator(node *Node) { m := ir.NewModule() main := m.NewFunc("main", types.I32) block := main.NewBlock("") block.NewRet(calc(block, node)) fmt.Println(m.String()) } func calc(block *ir.Block, node *Node) value.Value { if node.ID == "expr" { return expr(block, node) } if node.ID == "NUMBER" { return number(node) } log.Fatal("error: generator") return nil } func number(node *Node) value.Value { val, ok := strconv.Atoi(node.Value) if ok != nil { log.Fatal("error: generator") return nil } return constant.NewInt(types.I32, int64(val)) } func expr(block *ir.Block, node *Node) value.Value { if node.Left == nil || node.Right == nil { log.Fatal("error: generator") return nil } left := calc(block, node.Left) right := calc(block, node.Right) if node.Value == "+" { return block.NewAdd(left, right) } if node.Value == "-" { return block.NewSub(left, right) } if node.Value == "*" { return block.NewMul(left, right) } if node.Value == "/" { return block.NewUDiv(left, right) } log.Fatal("error: generator") return nil } |
シェルスクリプトマガジンvol.74 Web掲載記事まとめ
004 レポート UNIX基本コマンド集の新版
005 レポート IBMのフリー日本語ソフト
006 NEWS FLASH
008 特集1 本格的なホームサーバーを構築 アプリ編/麻生二郎 コード掲載
023 コラム「ユニケージは従来のやり方と何が違うのか(前編)」/シェル魔人
024 特集2 オープンソースのJitsiで構築しよう/橋本知里
034 特集3 キーボードを自作しよう/東峰沙希
044 特別企画 MDSを始めよう!(応用編)/生駒眞知子
054 Raspberry Piを100%活用しよう/米田聡 コード掲載
057 Hello Nogyo!
058 機械学習ことはじめ/川嶋宏彰 コード掲載
068 タイ語から分かる現地生活/つぎみき
072 MySQLのチューニング/稲垣大助
080 PPAP/桑原滝弥、イケヤシロウ
082 中小企業手作りIT化奮戦記/菅雄一
086 法林浩之のFIGHTING TALKS/法林浩之
088 Pythonあれこれ/飯尾淳 コード掲載
094 香川大学SLPからお届け!/増田嶺 コード掲載
102 Bash入門/大津真
110 Techパズル/gori.sh
111 コラム「ユニケージは従来のやり方と何が違うのか(後編)」/シェル魔人
特集1 本格的なホームサーバーを構築(Vol.74記載)
著者:麻生 二郎
小型コンピュータボードの最上位モデルである「Raspberry Pi 4 Model B」の4G/8Gバイト版と、人気のLinuxディストリビューション「Ubuntu」のサーバー版を組み合わせて、本格的なサーバーを構築しましょう。本特集では、テレワークに役立つサーバーアプリの導入方法を紹介します。
シェルスクリプトマガジン Vol.74は以下のリンク先でご購入できます。
図11 Sambaをインストールして実行するシェルスクリプト(samba_install.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 |
#!/bin/sh ##初期設定 WORKGROUP_NAME="SHMAG" SHARE_NAME="share" SHARE_FOLDER="/var/share" ##Sambaのインストール sudo apt update sudo apt -y install samba ##旧設定バックアップ mkdir -p ~/old_settings sudo mv /etc/samba/smb.conf ~/old_settings/. ##Sambaの共有設定 cat << EOF | sudo tee /etc/samba/smb.conf > /dev/null [global] workgroup = workgroup_name dos charset = CP932 unix charset = UTF8 [share_name] comment = Raspberry Pi share path = share_folder browsable = yes writable = yes create mask = 0777 directory mask = 0777 EOF sudo sed -i -e "s%workgroup_name%'$WORKGROUP_NAME'%" /etc/samba/smb.conf sudo sed -i -e "s%share_name%'$SHARE_NAME'%" /etc/samba/smb.conf sudo sed -i -e "s%share_folder%'$SHARE_FOLDER'%" /etc/samba/smb.conf ##共有フォルダ作成 sudo mkdir -p $SHARE_FOLDER sudo chmod 777 $SHARE_FOLDER ##Sambaの設定反映 sudo systemctl restart smbd sudo systemctl restart nmbd |
図14 MediaWikiを導入するシェルスクリプト(mediawiki_install.sh)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#!/bin/sh ##初期設定 DB_PASSWORD="shmag" ##必要なパッケージのインストール sudo apt update sudo apt -y install mediawiki imagemagick ##データベースの作成 sudo mysql -e "create database my_wiki;" sudo mysql -e "create user 'mediawiki'@'%' identified by '$DB_PASSWORD';" sudo mysql -e "grant all privileges on my_wiki.* to 'mediawiki'@'%';" |
図25 ownCloudをインストールするシェルスクリプト(owncloud_install.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 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 |
#!/bin/sh ##初期設定 DB_PASSWORD="shmag" ADMIN_NAME="admin" ADMIN_PASSWORD="admin" OWNCLOUD_FILE="owncloud-10.8.0.tar.bz2" ##ヘルパースクリプト「occ」の作成 cat << EOM | sudo tee /usr/local/bin/occ #! /bin/bash cd /var/www/owncloud sudo -E -u www-data /usr/bin/php /var/www/owncloud/occ "\$@" EOM sudo chmod +x /usr/local/bin/occ ##必要・推奨パッケージのインストール sudo apt update sudo apt -y install apache2 libapache2-mod-php mysql-server php-imagick php-common php-curl php-gd php-imap php-intl php-json php-mbstring php-mysql php-ssh2 php-xml php-zip php-apcu php-redis redis-server sudo apt -y install jq inetutils-ping ##ownCloudの設定ファイル作成 sudo sed -i "s%html%owncloud%" /etc/apache2/sites-available/000-default.conf cat << EOM | sudo tee /etc/apache2/sites-available/owncloud.conf Alias /owncloud "/var/www/owncloud/" <Directory /var/www/owncloud/> Options +FollowSymlinks AllowOverride All <IfModule mod_dav.c> Dav off </IfModule> SetEnv HOME /var/www/owncloud SetEnv HTTP_HOME /var/www/owncloud </Directory> EOM ##Apache HTTP Serverの設定 sudo a2ensite owncloud.conf sudo a2enmod dir env headers mime rewrite setenvif sudo systemctl restart apache2 ##ownCloudの入手と展開 wget https://download.owncloud.org/community/$OWNCLOUD_FILE tar -jxf $OWNCLOUD_FILE sudo mv owncloud /var/www/. sudo chown -R www-data /var/www/owncloud ##データベースの作成 sudo mysql -e "create database owncloud;" sudo mysql -e "create user 'owncloud'@'%' identified by '$DB_PASSWORD';" sudo mysql -e "grant all privileges on owncloud.* to 'owncloud'@'%';" ##ownCloudのインストール echo "しばらくおまちください。" occ maintenance:install --database "mysql" --database-name "owncloud" --database-user "owncloud" --database-pass $DB_PASSWORD --admin-user "$ADMIN_NAME" --admin-pass "$ADMIN_PASSWORD" myip=$(hostname -I|cut -f1 -d ' ') occ config:system:set trusted_domains 1 --value="$myip" ##バックグラウンド処理の設定 occ background:cron sudo sh -c 'echo "*/15 * * * * /var/www/owncloud/occ system:cron" > /var/spool/cron/crontabs/www-data' sudo chown www-data.crontab /var/spool/cron/crontabs/www-data sudo chmod 0600 /var/spool/cron/crontabs/www-data ##キャッシュとロックファイルの作成 occ config:system:set memcache.local --value '\OC\Memcache\APCu' occ config:system:set memcache.locking --value '\OC\Memcache\Redis' occ config:system:set redis --value '{"host": "127.0.0.1", "port": "6379"}' --type json ##ログローテーションの設定 cat << EOM | sudo tee /etc/logrotate.d/owncloud /var/www/owncloud/data/owncloud.log { size 10M rotate 12 copytruncate missingok compress compresscmd /bin/gzip } EOM |
図29 RainLoopをインストールするシェルスクリプト(rainloop_install.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 |
#!/bin/sh ##初期設定 SIZE="100M" DB_PASSWORD="admin" ##必要なパッケージの導入 sudo apt update sudo apt -y install apache2 php php-curl php-xml php-mysql mysql-server unzip ##RainLoop Webmailの導入 wget https://www.rainloop.net/repository/webmail/rainloop-community-latest.zip sudo mkdir -p /var/www/html/rainloop sudo unzip rainloop-community-latest.zip -d /var/www/html/rainloop/. sudo chown -R www-data /var/www/html/rainloop cat << EOF | sudo tee /etc/apache2/sites-available/rainloop.conf >> /dev/null <Directory /var/www/html/rainloop/data> Require all denied </Directory> EOF sudo a2ensite rainloop ##連絡先データベースの作成とApacheに反映 sudo mysql -e "create database rainloop;" sudo mysql -e "create user 'rainloop'@'%' identified by '$DB_PASSWORD';" sudo mysql -e "grant all privileges on rainloop.* to 'rainloop'@'%';" ##添付ファイルサイズの拡大 sudo sed -i -e "s%upload_max_filesize = 2M%upload_max_filesize = '$SIZE'%" /etc/php/7.4/apache2/php.ini sudo sed -i -e "s%post_max_size = 8M%post_max_size = '$SIZE'%" /etc/php/7.4/apache2/php.ini sudo systemctl restart apache2 |
図36 Mattermostをインストールするシェルスクリプト(mattermost_install.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 52 53 54 55 56 57 |
#!/bin/sh ##初期設定 DB_PASSWORD="shmag" MATTERMOST="v5.39.0/mattermost-v5.39.0-linux-arm64.tar.gz" SITE_URL="http://192.168.11.100/" ##データベースの作成 sudo apt update sudo apt -y install mysql-server sudo mysql -uroot -e "create user 'mmuser'@'%' identified by '$DB_PASSWORD';" sudo mysql -uroot -e "create database mattermost;" sudo mysql -uroot -e "grant all privileges on mattermost.* to 'mmuser'@'%';" ##mattermostの入手と展開 wget https://github.com/SmartHoneybee/ubiquitous-memory/releases/download/$MATTERMOST tar -xvzf mattermost*.gz sudo mv mattermost /opt sudo mkdir /opt/mattermost/data sudo useradd --system --user-group mattermost sudo chown -R mattermost:mattermost /opt/mattermost sudo chmod -R g+w /opt/mattermost ##設定ファイルの書き換え sudo sed -i -e 's%"postgres"%"mysql"%' /opt/mattermost/config/config.json sudo sed -i -e 's%postgres://mmuser:mostest@localhost/mattermost_test?sslmode=disable\\u0026connect_timeout=10%mmuser:'$DB_PASSWORD'@tcp(localhost:3306)/mattermost?charset=utf8mb4,utf8\&writeTimeout=30s%' /opt/mattermost/config/config.json sudo sed -i -e 's%"SiteURL": "",%"SiteURL": "'$SITE_URL'",%' /opt/mattermost/config/config.json ##起動・停止ファイルの作成 cat << EOF | sudo tee /lib/systemd/system/mattermost.service > /dev/null [Unit] Description=Mattermost After=network.target After=mysql.service BindsTo=mysql.service [Service] Type=notify ExecStart=/opt/mattermost/bin/mattermost TimeoutStartSec=3600 KillMode=mixed Restart=always RestartSec=10 WorkingDirectory=/opt/mattermost User=mattermost Group=mattermost LimitNOFILE=49152 [Install] WantedBy=mysql.service EOF ##mattermostの起動と自動起動設定 sudo systemctl daemon-reload sudo systemctl start mattermost sudo systemctl enable mattermost |
図43 MosP勤怠管理をインストールするシェルスクリプト(mosp_install.sh)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#!/bin/sh ##初期設定 MOSP="time4.war" ##必要なパッケージのインストール sudo apt update sudo apt -y install tomcat9 tomcat9-admin postgresql ##Mosp勤怠管理の導入 sudo chown tomcat:tomcat $MOSP sudo chmod 775 $MOSP sudo mv $MOSP /var/lib/tomcat9/webapps/. ##データベース管理者に切り替え sudo -i -u postgres ##初期設定 DBADMIN_PASSWORD="shmag" ##管理者パスワード設定 psql -c "alter role postgres with password '$DBADMIN_PASSWORD';" |
図A3 ラズパイサーバーの初期設定(ubuntu_init1.sh)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#!/bin/sh ##日本のタイムゾーン設定 sudo timedatectl set-timezone Asia/Tokyo ##全ソフトウエア更新 sudo apt update sudo apt -y upgrade sudo apt -y autoremove sudo apt clean ##ファームウエアアップデート sudo apt -y install rpi-eeprom sudo rpi-eeprom-update ##完了後の再起動 read -p "再起動しますか [y/N]:" YN if [ " $YN" = " y" ] || [ " $YN" = " Y" ]; then sudo reboot fi |
図A4 ラズパイサーバーの初期設定(ubuntu_init2.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 |
#!/bin/sh ##固定IPアドレス IP_ADDRESS="192.168.10.100" ##旧設定バックアップ mkdir -p ~/old_settings sudo mv /etc/netplan/50-cloud-init.yaml ~/old_settings/. ##新ネットワーク設定作成 cat << EOF | sudo tee /etc/netplan/50-cloud-init.yaml > /dev/null network: ethernets: eth0: dhcp4: false addresses: [ip_address/24] gateway4: 192.168.10.1 nameservers: addresses: [8.8.8.8] version: 2 EOF sudo sed -i -e "s%ip_address%$IP_ADDRESS%" /etc/netplan/50-cloud-init.yaml ##ネットワーク設定反映 sudo netplan apply |
図A3 データ領域を拡張するシェルスクリプト(storage_expand.sh)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#!/bin/sh ##パーティション作成とフォーマット sudo parted -s /dev/sda rm 1 sudo parted -s /dev/sda mklabel msdos sudo parted -s /dev/sda mkpart primary 0% 100% sudo mke2fs -t ext4 -F /dev/sda1 ##/varディレクトリに自動マウント sudo e2label /dev/sda1 usbssd sudo sh -c "echo 'LABEL=usbssd /var ext4 defaults 0 0' >> /etc/fstab" ##読み書き許可と/varディレクトリコピー sudo mount /dev/sda1 /mnt sudo chmod 777 /mnt sudo cp -a /var/* /mnt ##完了後の再起動 read -p "再起動しますか [y/N]:" YN if [ " $YN" = " y" ] || [ " $YN" = " Y" ]; then sudo reboot fi |
Raspberry Piを100%活用しよう(Vol.74掲載)
著者:米田 聡
小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第7回は、ボリュームスイッチのような操作を実現する拡張基板を扱います。
シェルスクリプトマガジン Vol.74は以下のリンク先でご購入できます。
図4 サンプルプログラム(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 |
import smbus import struct import time ADRSZRE_ADDR = 0x54 VALUE_HI = 0x00 RESET = 0x02 STEPS = 80 # 1回転=80カウント bus = smbus.SMBus(1) degree = 0.0 try: while True: temp = bus.read_word_data(ADRSZRE_ADDR, VALUE_HI) value = struct.unpack(">H", struct.pack("<H", temp))[0] if value == 0: bus.write_byte_data(ADRSZRE_ADDR, RESET, True) # Zero Reset degree = (value % STEPS) * 360.0 / STEPS print("%.1f" % degree, end='\r', flush=True) time.sleep(0.1) except KeyboardInterrupt: pass |
機械学習ことはじめ
著者:川嶋 宏彰
本連載では、機械学習の基礎となるさまざまな手法の仕組みや、それらの手法のPythonでの利用方法を解説していきます。今回は、入力から予測値を出力する「回帰モデル」と、その教師あり学習について紹介します。
シェルスクリプトマガジン Vol.74は以下のリンク先でご購入できます。
図5 気温に対するチョコレート菓子支出金額の散布図をプロットするPython コード
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 pandas as pd import matplotlib.pyplot as plt import japanize_matplotlib # pip install japanize-matplotlibが必要 import re plt.rcParams['font.size'] = 14 # 家計調査データの読み込み beg_col = 5 # 品目の始まる列 end_col = 6 # 品目の終わる列 usecols = [3] + list(range(beg_col, end_col + 1)) # 用いる列リスト # 年月の列をインデックスとするデータフレームに df_estat = pd.read_csv('estat.csv', header=0, encoding='cp932', usecols=usecols, index_col=0, parse_dates=True, date_parser=lambda x: pd.to_datetime(x, format='%Y年%m月')) # 各品目の支出金額を各月の日数で割る(★) for col in df_estat: new_col = re.sub(r'.* (.*)【円】', r'\1 (円/日)', col) df_estat[new_col] = df_estat[col] / df_estat.index.days_in_month # 気象庁の気温データの読み込み df_jma = pd.read_csv('jma.csv', skiprows=5, header=None, encoding='cp932', usecols=[0, 1], index_col=0, parse_dates=True) hitemp_col = '平均最高気温 (℃)' # 気温の列名を変える df_jma.columns = [hitemp_col] # データフレームの結合 df = pd.merge(df_estat, df_jma, left_index=True, right_index=True) df.index.name = 'y/m' # インデックスの名前を変更 # 年と月の列を追加(後で利用) df['year'] = df_estat.index.year df['month'] = df_estat.index.month print('df (merged):\n', df) # 散布図をプロット target_col = 'チョコレート菓子 (円/日)' df.plot(kind='scatter', x=hitemp_col, y=target_col, figsize=(5, 5)) plt.show() |
図7 チョコレート菓子支出金額の単回帰の結果を示すPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from sklearn.linear_model import LinearRegression reg = LinearRegression() # モデルを準備 # データを準備 X = df[[hitemp_col]].values # 平均最高気温(2次元配列)。「.values」は省略可 y = df[target_col].values # 支出金額(1次元配列)。「.values」は省略可 reg.fit(X, y) # 学習(データに直線を当てはめる) print('intercept:', reg.intercept_) # 切片 print('coef:', reg.coef_[0]) # 直線の傾き print('R2:', reg.score(X, y)) # 決定係数 df.plot(kind='scatter', x=hitemp_col, y=target_col, figsize=(5, 5)) plt.plot(X, reg.predict(X), 'r') plt.show() |
図10 チョコレート菓子支出金額の単回帰における残差をプロットする
Pythonコード
1 2 3 4 5 6 7 |
fig = plt.figure(figsize=(5, 5)) plt.scatter(X, y - reg.predict(X)) # 残差をプロット plt.axhline(y=0, c='r', linestyle='-') # 水平線をプロット plt.ylim([-3, 3]) plt.xlabel(hitemp_col) plt.ylabel('残差') plt.show() |
図13 年を説明変数とした単回帰のためのPythonコード
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 |
from matplotlib import cm from matplotlib.colors import ListedColormap def ListedGradation(cmapname, num=10): """ LinearSegmentedColormap から ListedColormap を作成 """ color_list = [] cmap = cm.get_cmap(cmapname) for i in range(num): color = list(cmap(i/num)) color_list.append(color) return ListedColormap(color_list) # 年ごとに散布図の色を変える df.plot(kind='scatter', x=hitemp_col, y=target_col, c=df['year'], cmap=ListedGradation('cividis', 11), sharex=False, figsize=(6, 5)) plt.show() # 「年」を説明変数とする単回帰 reg_year = LinearRegression() X = df[['year']].values # 年 y = df[target_col].values # 支出金額 reg_year.fit(X, y) # 学習(データに直線を当てはめる) print('intercept:', reg_year.intercept_) # 切片 print('coef:', reg_year.coef_[0]) # 直線の傾き print('R2:', reg_year.score(X, y)) # 決定係数 df.plot(kind='scatter', x='year', y=target_col, figsize=(5, 5)) plt.plot(X[:, 0], reg_year.predict(X), 'r') plt.show() |
図15 平均最高気温と年の二つを説明変数とした重回帰のためのPythonコード
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 |
from mpl_toolkits.mplot3d import Axes3D import numpy as np reg_multi = LinearRegression() X = df[[hitemp_col, 'year']].values # 二つの説明変数(2次元配列) y = df[target_col].values # 支出金額(1次元配列) reg_multi.fit(X, y) # 学習(データに平面を当てはめる) print('intercept:', reg_multi.intercept_) # 切片 print('coef:', reg_multi.coef_) # 平面の傾き print('R2:', reg_multi.score(X, y)) # 決定係数 # 3次元散布図のプロット def scatter3d(X, y, xlabels, ylabel): fig = plt.figure(figsize=(9, 6)) ax = fig.add_subplot(111, projection='3d') ax.scatter(X[:, 0], X[:, 1], y) ax.set_xlabel(xlabels[0]) ax.set_ylabel(xlabels[1]) ax.set_zlabel(ylabel) ax.view_init(elev=30, azim=-60) return ax scatter3d(X, y, [hitemp_col, '年'], target_col) plt.show() # 平面(wireframe)を合わせてプロット ax = scatter3d(X, y, [hitemp_col, '年'], target_col) xlim = ax.get_xlim() ylim = ax.get_ylim() mesh_x = np.meshgrid(np.arange(*xlim, (xlim[1] - xlim[0]) / 20), np.arange(*ylim, (ylim[1] - ylim[0]) / 20)) mesh_y = reg_multi.intercept_ + reg_multi.coef_[0] * mesh_x[0] \ + reg_multi.coef_[1] * mesh_x[1] # 回帰式より予測 ax.plot_wireframe(*mesh_x, mesh_y, color='r', linewidth=0.5) plt.show() |
図18 平均最高気温と年の二つを説明変数とした重回帰の残差をプロットするPythonコード
1 2 3 4 5 6 |
fig = plt.figure(figsize=(5, 5)) plt.scatter(X[:, 0], y - reg_multi.predict(X)) # 残差をプロット plt.axhline(y=0, c='r', linestyle='-') # 水平線をプロット plt.ylim([-3, 3])plt.xlabel(hitemp_col) plt.ylabel('残差') plt.show() |
図20 平均最高気温とアイスクリーム支出金額の散布図をプロットするPythonコード
1 2 3 |
target_col = 'アイスクリーム・シャーベット (円/日)' # 目的変数を変える df.plot(kind='scatter', x=hitemp_col, y=target_col, figsize=(5, 5)) plt.show() |
図22 多項式回帰モデルを使ったPythonコード
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 |
from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import Ridge def poly_fit(df, xlabel, ylabel, degree=2, ylim=None): """ 多項式回帰 """ y = df[ylabel] pf = PolynomialFeatures(degree=degree, include_bias=False) Xpf = pf.fit_transform(df[[xlabel]]) reg_pf = LinearRegression(normalize=True) # reg_pf = Ridge(normalize=True, alpha=0.01) # (★) reg_pf.fit(Xpf, y) print('intercept:', reg_pf.intercept_) # 切片 print('coef:', reg_pf.coef_) # 直線の傾き print('R2:', reg_pf.score(Xpf, y)) # 決定係数 fig = plt.figure(figsize=(5, 5)) plt.plot(X, y, '.', ms=8) plt.ylim(ylim) plt.xlabel(xlabel) plt.ylabel(ylabel) # x軸に等間隔に点を配置して回帰曲線を描く xlim = plt.xlim() X_lin = np.arange(*xlim, (xlim[1] - xlim[0])/50).reshape(-1, 1) Xpf_lin = pf.fit_transform(X_lin) ypf_pred = reg_pf.predict(Xpf_lin) plt.plot(X_lin, ypf_pred, color='r') X = df[[hitemp_col]].values # 平均最高気温 y = df[target_col].values # 支出金額 for p in [1, 2, 3, 20]: # 多項式の次数 poly_fit(df, hitemp_col, target_col, degree=p) plt.title(f'p = {p}') plt.show() |
Pythonあれこれ(Vol.74掲載)
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第4回は、フラクタル図形の一種である「ヒルベルト曲線」を描く方法を解説します。
シェルスクリプトマガジン Vol.74は以下のリンク先でご購入できます。
図1 1次のヒルベルト曲線を描くPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#!/usr/bin/env python from turtle import * SIZE = 300 p = [[-0.5, 0.5], [-0.5, -0.5], [0.5, -0.5], [0.5, 0.5]] def screen_pos(x): return (x[0]*SIZE, x[1]*SIZE) setup(width = 2*SIZE, height = 2*SIZE) color('red') width(5) hideturtle() for x in p: goto(screen_pos(x)) mainloop() |
図4 1 次のヒルベルト曲線を描くPythonコードの改良版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#!/usr/bin/env python from turtle import * SIZE = 300 penup_flag = True p = [[-0.5, 0.5], [-0.5, -0.5], [0.5, -0.5], [0.5, 0.5]] def screen_pos(x): return (x[0]*SIZE, x[1]*SIZE) setup(width = 2*SIZE, height = 2*SIZE) color('red') width(5) hideturtle() penup() for x in p: goto(screen_pos(x)) if penup_flag: pendown() penup_flag = False onkey(exit, 'q') listen() mainloop() |
図7 2 次のヒルベルト曲線を描くPythonコード
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 |
#!/usr/bin/env python from turtle import * SIZE = 300 penup_flag = True tm = [ [ [0.0, -0.5, -0.5], [-0.5, 0.0, 0.5], [0.0, 0.0, 1.0] ], [ [0.5, 0.0, -0.5], [ 0.0, 0.5, -0.5], [0.0, 0.0, 1.0] ], [ [0.5, 0.0, 0.5], [ 0.0, 0.5, -0.5], [0.0, 0.0, 1.0] ], [ [0.0, 0.5, 0.5], [ 0.5, 0.0, 0.5], [0.0, 0.0, 1.0] ] ] e = [ [ 1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0] ] p = [ [-0.5, 0.5], [-0.5, -0.5], [0.5, -0.5], [0.5, 0.5] ] def 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] ] def 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]] ] def screen_pos(x): return (x[0]*SIZE, x[1]*SIZE) setup(width = 2*SIZE, height = 2*SIZE) color('red') width(5) hideturtle() penup() for m in tm: p2 = [ affine_transform(mat_mul(e, m), x) for x in p ] for x in p2: goto(screen_pos(x)) if penup_flag: pendown() penup_flag = False onkey(exit, 'q') listen() mainloop() |
図9 書き換えた2 次のヒルベルト曲線を描くPythonコード
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 |
#!/usr/bin/env python from turtle import * import numpy as np SIZE = 300 penup_flag = True tm = [ np.matrix(x) for x in [ [ [0.0, -0.5, -0.5], [-0.5, 0.0, 0.5], [0.0, 0.0, 1.0] ], [ [0.5, 0.0, -0.5], [ 0.0, 0.5, -0.5], [0.0, 0.0, 1.0] ], [ [0.5, 0.0, 0.5], [ 0.0, 0.5, -0.5], [0.0, 0.0, 1.0] ], [ [0.0, 0.5, 0.5], [ 0.5, 0.0, 0.5], [0.0, 0.0, 1.0] ] ] ] e = np.eye(3) # 3次の単位行列 p = [ np.matrix(x).T for x in [ [-0.5, 0.5, 1.0], [-0.5, -0.5, 1.0], [ 0.5, -0.5, 1.0], [ 0.5, 0.5, 1.0] ] ] def screen_pos(x): return (x[0,0]*SIZE, x[1,0]*SIZE) setup(width = 2*SIZE, height = 2*SIZE) color('red', 'yellow') width(5) hideturtle() penup() for m in tm: for x in [ e * m * x for x in p ]: goto(screen_pos(x)) if penup_flag: pendown() penup_flag = False onkey(exit, 'q') listen() mainloop() |
図10 図9 の赤枠部分を置き換えるPythonコード
1 2 3 4 5 6 7 8 9 10 11 |
def hilbert(n, m): if n > 1: for m_tm in tm: hilbert(n-1, m * m_tm) else: for x in [ m * x.T for x in p ]: goto(screen_pos(x)) global penup_flag if penup_flag: pendown() penup_flag = False hilbert(3, e) |
図13 1 ~ 7 次のヒルベルト曲線を重ねて描画するPythonコード
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 |
#!/usr/bin/env python from turtle import * import numpy as np SIZE = 300 MARGIN = 50 penup_flag = True settings = [ { 'color': 'forestgreen', 'width': 5 }, { 'color': 'navy', 'width': 4 }, { 'color': 'purple', 'width': 3 }, { 'color': 'brown', 'width': 2 }, { 'color': 'red', 'width': 2 }, { 'color': 'orange', 'width': 1 }, { 'color': 'yellowgreen', 'width': 1 } ] tm = [ np.matrix(x) for x in [ [ [0.0, -0.5, -0.5], [-0.5, 0.0, 0.5], [0.0, 0.0, 1.0] ], [ [0.5, 0.0, -0.5], [ 0.0, 0.5, -0.5], [0.0, 0.0, 1.0] ], [ [0.5, 0.0, 0.5], [ 0.0, 0.5, -0.5], [0.0, 0.0, 1.0] ], [ [0.0, 0.5, 0.5], [ 0.5, 0.0, 0.5], [0.0, 0.0, 1.0] ] ] ] e = np.eye(3) p = [ np.matrix(x).T for x in [ [-0.5, 0.5, 1.0], [-0.5, -0.5, 1.0], [ 0.5, -0.5, 1.0], [ 0.5, 0.5, 1.0] ] ] def draw_line_to(x): goto(x[0,0]*SIZE, x[1,0]*SIZE) global penup_flag if penup_flag: pendown() penup_flag = False def reset(): penup() global penup_flag penup_flag = True def hilbert(n, m): if n > 1: for m_ in tm: hilbert(n-1, m * m_) else: for q in [ m * p_ for p_ in p ]: draw_line_to(q) setup(width = 2*SIZE+MARGIN, height = 2*SIZE+MARGIN) hideturtle() for i in range(len(settings)): reset() color(settings[i]['color'], 'white') width(settings[i]['width']) hilbert(i+1, e) onkey(exit, 'q') listen() mainloop() |
香川大学SLPからお届け!(Vol.74掲載)
著者:増田 嶺
前回に引き続き、SLPで開発したリズムゲームを紹介します。前回はゲームで使用する譜面作成ツールについて解説しました。今回は、ゲーム本体について解説します。ゲーム本体は「Visual Studio Code」(VSCode)で開発します。
図5 「js/game/singleNote.js」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class SingleNote { constructor(speed, reachTime) { this.height = note.height; this.width = note.width; this.frameColor = 'rgb(' + note.frameColor + ')'; // 枠の色 this.bodyColor = 'rgba(' + note.bodyColor + ')'; // 中の色 this.this.centerY = -this.height; // ノーツ中心のY座標 this.speed = speed * note.speedRatio; // px/ms // ゲーム開始から判定ラインに到達するまでの時間 this.reachTime = reachTime + note.delay; // canvasに入る時間 this.appearTime = this.reachTime - (JUDGE_LINE.centerY + this.height / 2) / this.speed; // canvasから出る時間 this.hideTime = this.reachTime + (CANVAS_H - JUDGE_LINE.centerY + this.height / 2) / this.speed; this.act = true; // 自身がヒット処理対象かどうか this.show = false; // 自身がcanvasに描画されるかどうか } } |
図6 「js/game/backLane.js」ファイルに追加するコード
1 2 3 4 5 |
(略) generateNote(data) { this.note = data.map((val) => new SingleNote(val[2], val[3])); } (略) |
図7 「js/game/singleNote.js」ファイルに追加するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class SingleNote { constructor(speed, reachTime) { (略) } draw(x) { if (this.show) { this.centerY = JUDGE_LINE.centerY - (this.reachTime - time.elapsed) * this.speed; CTX.fillStyle = this.bodyColor; CTX.fillRect(x, this.centerY - this.height / 2, this.width, this.height); CTX.strokeStyle = this.frameColor; CTX.strokeRect( x + LINE_WIDTH / 2, this.centerY - this.height / 2 + LINE_WIDTH / 2, this.width - LINE_WIDTH, this.height - LINE_WIDTH ); } } } |
図8 「js/game/backLane.js」ファイルに追加するコード
1 2 3 4 5 |
(略) drawNote() { this.note.forEach(val => val.draw(this.x)); } (略) |
図9 「js/game/singleNote.js」ファイルに追加するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class SingleNote { constructor(speed, reachTime) { (略) } draw(x) { (略) } update() { this.updateShow(); } updateShow() { if (this.act || this.show) { if (this.appearTime <= time.elapsed && time.elapsed <= this.hideTime) { this.show = true; } else { this.show = false; } } } } |
図11 「js/game/backLane.js」ファイルに追加するコード
1 2 3 4 5 |
(略) update() { this.note.forEach(val => val.update()); } (略) |
図13 「js/game/backLane.js」ファイルに追加するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
(略) judge() { calcElapsedTime(); // 経過時間を更新 // ヒットしているノーツを抽出 const TARGET = this.note.filter(val => val.checkHit(note.hitRange[3])); // TARGETの先頭から処理、先頭ノーツのグレードがBadだった場合のみ // 二つ目以降のノーツを処理し、それらのノーツがBadだった場合は中断 const GRADE = [3] for (let i = 0; TARGET[i] && GRADE[0] === 3; i++) { GRADE[i] = TARGET[i].getGrade(); // 二つ目以降のノーツがBadの場合はそこで中断 if (i > 0 && GRADE[i] === 3) { break; } JUDGE_LINE.setGrade(GRADE[i]); // ノーツヒットのグレードを描画 TARGET[i].close(); // 判定済みのノーツ処理を停止 } } (略) |
図15 「js/game/singleNote.js」ファイルに追加するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class SingleNote { (略) updateShow() { (略) } checkHit(range) { if (this.act && Math.abs(time.elapsed - this.reachTime) <= range) { return true; } else { return false; } } getGrade() { let grade = 3; for (let i = 2; i >= 0; i--) { if (this.checkHit(note.hitRange[i])) { grade = i; } } return grade; } } |
図16 「js/game/singleNote.js」ファイルに追加するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class SingleNote { (略) update() { this.updateShow(); if (!this.act) { return; } // 判定ラインから自身の判定ゾーンが // 過ぎた時点で処理 if (this.reachTime < time.elapsed && !this.checkHit(note.hitRange[3])) { JUDGE_LINE.setGrade(3); this.act = false; } } (略) close() { this.act = false; this.show = false; } } |
図18 「js/game/gameScoreManager.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 |
(略) calcScore(grade) { switch (grade) { case 0: this.point += 100 + this.combo; this.perfect++; this.combo++; break; case 1: this.point += 80 + this.combo * 0.8; this.great++; this.combo++; break; case 2: this.point += 50 + this.combo * 0.5; this.good++; this.combo++; break; case 3: this.bad++; this.combo = 0; break; } if (this.maxCombo < this.combo) { this.maxCombo = this.combo; } } (略) |
図19 「js/game/backLane.js」ファイルに追加するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
(略) judge() { (略) if (GRADE[i] < 3) { // Bad以外の判定ならヒットSEを鳴らす sound.playSE(sound.seList[0]); } else { sound.playSE(sound.seList[1]); // Bad判定ならバッドSEを鳴らす } gameScore.calcScore(GRADE[i]); // スコアを更新 JUDGE_LINE.setGrade(GRADE[i]); // ノーツヒットのグレードを描画 TARGET[i].close(); // 判定済みのノーツ処理を停止 } } (略) |
図20 「js/game/singleNote.js」ファイルに追加するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class SingleNote { (略) update() { (略) if (this.reachTime < time.elapsed && !this.checkHit(note.hitRange[3])) { gameScore.calcScore(3); JUDGE_LINE.setGrade(3); this.act = false; } } (略) } |
図22 「js/game/backLane.js」ファイルに追加するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 |
(略) draw() { (略) if (inputKey.status[this.key]) { const GRAD = CTX.createLinearGradient(this.x, JUDGE_LINE.centerY, this.x, CANVAS_H / 3); GRAD.addColorStop(0.0, 'rgba(' + this.actColor + ', 0.6)'); GRAD.addColorStop(1.0, 'rgba(' + this.actColor + ', 0)'); CTX.fillStyle = GRAD; CTX.fillRect(this.x, 0, this.width, JUDGE_LINE.centerY); } } (略) |
機械学習ことはじめ(Vol.73掲載)
筆者:川嶋 宏彰
本連載では、機械学習の基礎となるさまざまな手法の仕組みや、それらの手法のPythonでの利用方法を解説していきます。今回は「確率モデル」による機械学習である、ガウス分布を用いた教師あり学習と教師なし学習の手法を紹介します。
シェルスクリプトマガジン Vol.73は以下のリンク先でご購入できます。
図2 二つの特徴量を抽出して散布図をプロットするPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import seaborn as sns import matplotlib.pyplot as plt plt.rcParams['font.size'] = 14 penguins = sns.load_dataset('penguins') # 取り出す特徴量 features = ['bill_depth_mm', 'body_mass_g'] # ★ # 対象とするペンギン種 target_species = ['Adelie', 'Gentoo'] # ★ # 今回用いる特徴量をクラスラベルと共に取り出す df = penguins[['species'] + features].copy() df.dropna(inplace=True) # NaN が含まれる行は削除 # 今回用いるペンギン種のみとする df2 = df[df['species'].isin(target_species)].copy() print(df2.shape) # (274, 3) と表示される # 種(target_species)に合わせたパレットを作成 palette = {c: sns.color_palette()[k] for k, c in enumerate(df['species'].unique()) if c in target_species} fig = plt.figure(figsize=(5, 5)) sns.scatterplot(data=df2, x=features[0], y=features[1], hue='species', palette=palette) plt.show() |
図4 各クラス(ペンギン種)の体重分布をプロットするPythonコード
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 |
import numpy as np import scipy X = df2[features].values # 各個体の2次元特徴量(行数=個体数) y = df2['species'].values # 各個体のクラスラベル prob_y = {c: np.sum(y==c)/y.size for c in target_species} # 事前確率 print('Prior:', prob_y) # 平均と分散は2次元にまとめてベクトルや行列として計算しておく mu = {c: X[y==c, :].mean(axis=0) for c in target_species} # 平均 sigma2 = {c: X[y==c, :].var(axis=0, ddof=1) for c in target_species} # 不偏分散 f_idx = 1 # 用いる特徴量のindex(これは1次元の場合) fig = plt.figure(figsize=(10, 5)) ax1 = fig.add_subplot(111) ax2 = ax1.twinx() # 右の縦軸 # ヒストグラムをプロット (体重のみ) sns.histplot(ax=ax1, x=X[:, f_idx], hue=y, palette=palette, alpha=0.2) xmin = np.min(X[:, f_idx]) xmax = np.max(X[:, f_idx]) xs = np.linspace(xmin-(xmax-xmin)/10, xmax+(xmax-xmin)/10, 100) # 等間隔に100点用意 # 確率密度関数を準備 (体重のみ) norm_dist1d = {c: scipy.stats.multivariate_normal(mu[c][f_idx], sigma2[c][f_idx]) for c in target_species} for c in target_species: # 各クラスの確率密度関数をプロット sns.lineplot(ax=ax2, x=xs, y=norm_dist1d[c].pdf(xs)*prob_y[c], hue=[c]*len(xs), palette=palette) # 各データを小さな縦線でプロット sns.rugplot(x=X[:, f_idx], hue=y, palette=palette, height=0.05) ax2.set_ylabel('Probability density') ax1.set_xlabel(features[f_idx]) plt.show() |
図6 1次元での決定境界を求めるPythonコード
1 2 3 4 5 6 |
# 曲線の上下が変化するおおよその点を求める diff = norm_dist1d['Adelie'].pdf(xs)*prob_y['Adelie'] - norm_dist1d['Gentoo'].pdf(xs)*prob_y['Gentoo'] # 符号の変化点を見つけるために隣り合う要素の積を計算 ddiff = diff[1:] * diff[:-1] print('boundary:', xs[np.where(ddiff < 0)[0][0]]) |
図8 2次元ガウス分布とその等高線を表示するPythonコード
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 |
from mpl_toolkits.mplot3d import axes3d from matplotlib import cm # 共分散行列を求めておく Sigma = {c: np.cov(X[y==c, :].T, ddof=1) for c in target_species} def gen_2dgrid(X): """ 2次元メッシュグリッドの生成 """ d = X.shape[1] xmin = X.min(axis=0) # 各列の最小値 xmax = X.max(axis=0) # 各列の最大値 xstep = [(xmax[j]-xmin[j]) / 100 for j in range(d)] # グリッドのステップ幅 xmin = [xmin[j] - 10*xstep[j] for j in range(d)] # 少し広めに xmax = [xmax[j] + 10*xstep[j] for j in range(d)] aranges = [np.arange(xmin[j], xmax[j] + xstep[j], xstep[j]) for j in range(2)] return np.meshgrid(*aranges) # d=2のときは (x0grid, x1grid) が返る def gaussian_2dgrid(m, S, x0grid, x1grid): """ 2次元ガウス分布の密度関数の値を2次元メッシュで計算 """ gaussian = scipy.stats.multivariate_normal(m, S) return gaussian.pdf(np.c_[x0grid.ravel(), x1grid.ravel()]).reshape(x0grid.shape) c = 'Adelie' # プロットするクラスを設定 xgrid_2d = gen_2dgrid(X) # xgrid_2d: (x0grid, x1grid) のような二つ組 px_adelie = gaussian_2dgrid(mu[c], Sigma[c], *xgrid_2d) # *xgrid_2d で2引数に展開 fig = plt.figure(figsize=(8, 5)) # 3次元プロット ax = fig.add_subplot(111, projection='3d') # 2次元ガウシアンの3次元的プロット cmap = cm.coolwarm # カラーマップを設定 ax.plot_surface(*xgrid_2d, px_adelie, cmap=cmap) # 等高線のプロット z_offset = -np.max(px_adelie) ax.contour(*xgrid_2d, px_adelie, zdir='z', offset=z_offset, cmap=cmap) ax.set_zlim(z_offset, ax.get_zlim()[1]) ax.view_init(elev=40, azim=-60) # 3次元プロットの表示角度の設定 plt.show() |
図10 2次元ガウス分布の等高線と元データの散布図を表示するPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
def plot_ellipses(ms, Ss, pys, xgrids, xylabels=None, fig=None): """ 各クラスの確率だ円をプロット """ if fig is None: fig = plt.figure(figsize=(5, 5)) # 新たに作成 else: plt.figure(fig.number) # 既存のfigureを利用 levels = None # クラス名を辞書キーから取得 for c in ms.keys() if type(ms) is dict else range(len(ms)): cs = plt.contour(*xgrids, gaussian_2dgrid(ms[c], Ss[c], *xgrids)*pys[c], cmap=cmap, levels=levels) levels = cs.levels # 二つ目以降は一つ目のlevelsを利用 # 密度(山の標高)をだ円に表示 # plt.clabel(cs, inline=True, fontsize=8) if xylabels is not None: plt.xlabel(xylabels[0]) plt.ylabel(xylabels[1]) return fig plot_ellipses(mu, Sigma, prob_y, xgrid_2d, xylabels=features) sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=y, palette=palette) plt.show() |
図12 2次の識別による決定境界を表示するPythonコード
1 2 3 4 5 6 7 8 9 10 11 |
from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis # plot_decision_boundary()は # 注釈のリンク先を参照して別途定義 clf_qda = QuadraticDiscriminantAnalysis() clf_qda.fit(X, y) # 学習 # 決定境界の表示 fig = plot_decision_boundary(X, y, clf_qda, features, palette) # 確率だ円の表示 plot_ellipses(mu, Sigma, prob_y, xgrid_2d, fig=fig) plt.show() |
図15 ガウス分布によるデータの生成をするPythonコード
1 2 3 4 5 6 7 8 9 10 11 |
fig = plt.figure(figsize=(5, 5)) Ngen_each = 120 # 各クラスで120個体生成する場合 # 各クラスの割合を変化させてもよい X_gen = np.vstack( [np.random.multivariate_normal(mu[c], Sigma[c], Ngen_each) for c in target_species]) y_gen = np.hstack([[c] * Ngen_each for c in target_species]) sns.scatterplot(x=X_gen[:, 0], y=X_gen[:, 1], hue=y_gen, palette=palette) plt.xlabel(features[0]) plt.ylabel(features[1]) plt.show() |
図18 混合ガウス分布によるソフトクラスタリングをするPythonコード
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 |
from sklearn.mixture import GaussianMixture from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler k = 2 # クラスタ数(非階層的クラスタリングなので決める必要がある) use_gmm = True # Falseにするとk-meansになる if use_gmm: gmm = GaussianMixture(n_components=k, covariance_type='full') # full、diagはスケーリング無しも可(sphericalでは必要) Xu = X # GMM推定による各データのクラスタID y_pred = gmm.fit_predict(Xu) else: kmeans = KMeans(n_clusters=k) # 標準化によるスケーリング Xu = StandardScaler().fit_transform(X) # k-meansによる各データのクラスタID y_pred = kmeans.fit_predict(Xu) fig = plt.figure(figsize=(11, 5)) fig.add_subplot(121) sns.scatterplot(x=X[:, 0], y=X[:, 1], color='k') plt.xlabel(features[0]) plt.ylabel(features[1]) plt.title('Unlabeled data') fig.add_subplot(122) sns.scatterplot(x=Xu[:, 0], y=Xu[:, 1], hue=y_pred, palette='bright') method_str = 'GMM' if use_gmm else 'k-means' plt.title(f'{method_str} clustering') if use_gmm: plot_ellipses(gmm.means_, gmm.covariances_, gmm.weights_, xgrid_2d, fig=fig) plt.show() |
香川大学SLPからお届け!(Vol.73掲載)
著者:岩本 和真
SLPでは最近、Webブラウザで動作するリズムゲームをチームで開発しました。さまざまな曲でプレーできるように、リズムゲーム本体と並行して、ゲームで使用する譜面を作成するツールも開発しました。このツールもWebブラ
ウザで動作します。今回は、この譜面作成ツールの実装について紹介します。
シェルスクリプトマガジン Vol.73は以下のリンク先でご購入できます。
図3 最初に実行されるコード
1 2 3 4 5 6 7 8 9 10 11 |
window.onload = async function() { const {bpm, musicL} = getMusicInfoViaElement() await getWidth(); await numberQLine(bpm, musicL); await setCanvas(); await setQLine(); await setXLine(); await update(); await draw(); await scrollBottom(); } |
図4 getMusicInfoViaElement()関数のコード
1 2 3 4 5 6 |
function getMusicInfoViaElement() { const bpm = document.getElementById('bpm').value; const musicL = document.getElementById('musicL').value; const musicBody = document.getElementById('musicBody').value; return {bpm, musicL, musicBody} } |
図5 getWidth()関数のコード
1 2 3 4 5 6 7 8 9 10 11 |
const can = document.getElementById("can2"); const ctx = can.getContext("2d"); can.setAttribute('style', 'background-color: #f6f7d7'); function getWidth() { return new Promise(function(resolve) { cst = window.getComputedStyle(can); canvasW = parseInt(cst.width); can.width = canvasW; resolve(); }) } |
図6 numberQLine()関数のコード
1 2 3 4 5 6 |
function numberQLine(bpm, musicL) { return new Promise(function(resolve) { qLineQty = Math.floor(musicL / (60 / bpm) + 1); resolve(); }) } |
図7 setCanvas()関数のコード
1 2 3 4 5 6 7 8 9 10 11 |
let qLineMargin = 80; function setCanvas() { return new Promise(function(resolve) { canvasH = qLineQty * qLineMargin; can.height = canvasH; ctx.translate(0, can.height); ctx.scale(1, -1); can.style.height = canvasH * 2.5 +'px'; resolve(); }) } |
図8 setQLine()関数のコード
1 2 3 4 5 6 7 8 |
function setQLine() { return new Promise(function(resolve) { for (let i = quarterLine.length; i < qLineQty; i++) { quarterLine[i] = new QuarterLine(i); } resolve(); }) } |
図9 QuarterLineクラスのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class QuarterLine { constructor(no) { this.no = no; this.y = qLineMargin * this.no + qLineMargin / 4; } update() { this.y = qLineMargin * this.no + qLineMargin / 4; } draw() { ctx.beginPath(); ctx.strokeStyle = "#3ec1d3"; ctx.lineWidth = Q_LINE_T; ctx.moveTo(0, Math.round(this.y)); ctx.lineTo(canvasW, Math.round(this.y)); ctx.stroke(); } } |
図10 setXLine()関数のコード
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function setXLine() { return new Promise(function(resolve) { for (let i = 0; i < 4; i++) { for (let j = xLine[i].length; j < qLineQty; j++) { xLine[i][j] = []; for (let k = 0; k < 14; k++) { xLine[i][j][k] = new XthLine(i, j, k); } } } resolve(); }) } |
図11 update()関数のコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function update() { return new Promise(function (resolve) { // ノーツの縦の長さを更新 noteH = (qLineMargin / 9 >= Q_LINE_T || divValue == 8) ? qLineMargin / 9 : Q_LINE_T // ノーツの位置と各拍、各連符の横線の位置を更新 const laneMargin = canvasW / 5; laneSet = [laneMargin / 2, laneMargin * 1.5, laneMargin * 2.5, laneMargin * 3.5, laneMargin * 4.5]; noteW = laneMargin / 3; quarterLine.forEach((val) => val.update() ) editLane.forEach((val) => val.update() ) for (let i = 0; i < 4; i++) { for (let j = 0; j < qLineQty; j++) { for (let k = 0; k < 14; k++) { xLine[i][j][k].update(); } } } resolve(); }) } |
図12 draw()関数のコード
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 draw() { return new Promise(function(resolve) { ctx.clearRect(0, 0, can.width, can.height); for (let i = 0; i < 4; i++) { for (let j = 0; j < qLineQty; j++) { for (let k = 0; k < 14; k++) { xLine[i][j][k].draw(); } } } for (let i = 0; i < qLineQty; i++) { quarterLine[i].draw(); } editLane.forEach((val) => val.draw()) for (let i = 0; i < 4; i++) { for (let j = 0; j < qLineQty; j++) { for (let k = 0; k < 14; k++) { xLine[i][j][k].drawNote(); } } } resolve(); }) } |
図13 draw()関数のコード
1 2 3 4 5 6 7 |
function scrollBottom() { return new Promise(function(resolve) { let target = document.getElementById('scroll'); target.scrollTop = target.scrollHeight; resolve(); } ) |
図14 レーン上のノーツをマウスクリックで制御するためのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
let mouseDown; can.addEventListener('mousedown', async function(e) { mouseDown = true; await pos(e); await update(); await draw(); }); can.addEventListener('mouseup', function(e) { mouseDown = false; }); function pos(e) { return new Promise(function(resolve) { mouseDownX = (e.clientX - can.getBoundingClientRect().left); mouseDownY = -(e.clientY - can.getBoundingClientRect().bottom) / 2.5; resolve(); }) } |
図15 スライダ機能のコード
1 2 3 4 5 6 7 8 |
let canScale = document.getElementById('canScale'); canScale.onchange = async function() { qLineMargin = this.value; await setCanvas(); await update(); await draw(); await scrollBottom(); } |
図16 apply()関数のコード
1 2 3 4 5 6 7 8 9 10 |
async function apply() { const {bpm, musicL} = getMusicInfoViaElement() await numberQLine(bpm, musicL); await setCanvas(); await setQLine(); await setXLine(); await update(); await draw(); return Promise.resolve(); } |
図17 convert()関数のコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function calcNote(jnoteValue, calced, musicBody) { return Math.round((jnoteValue+calced+musicBody*1000)*100)/100; } async function convert() { await apply(); const fileName = document.getElementById('fileName').value; const speed = document.getElementById("speed").value; const {bpm, musicL, musicBody} = getMusicInfoViaElement() const {noteValue, note32Value, note6Value} = calcNoteValue(bpm) const outInfo = Array() xLine.forEach((val1, i) => { val1.forEach((val2, j) => { val2.forEach((val3, k) => { if (val3.note) { const tmp = k < 8 ? calcNote(j*noteValue, k*note32Value, musicBody) : calcNote(j*noteValue, k%8*note6Value, musicBody) outInfo.push(Array(1, i+1, speed, tmp)) } }) }) }) createAndDownloadCsv(fileName, outInfo, bpm, musicL, musicBody); } |
Vol.73 補足情報
特集1 PowerShellを学ぼう
pp.18-21の図33~図34のキャプション内容に誤りがありました。キャプションの内容が一つずつずれています。正しくは、以下の通りです。お詫びして訂正いたします。
図33 テーマ「powerlevel10k_rainbow」を適用した画面
図34 フォントのインストール
図35 インストールしたフォントをPowerShellに適用する
図36 プロファイルの配置を確認する
図37 Windows PowerShell ISEの起動
図38 ISEの初期画面
図39 インテリセンスにより候補を表示する
図40 コマンド アドオンからコマンドレットを調べる
図41 コマンド アドオンからコマンドレットを選択したら、パラメータを付けてそのまま実行できる
図42 ISEのデバックメニュー
図43 ブレークポイントは茶色で表示される
図44 スニペットを表示して挿入できる
図45 ISEが意図しない状態で停止した場合に表示されるダイアログ
図46 復元されたスクリプトはタブに「回復済み」と表示される
図47 時刻を取得するスクリプト(writeTime.ps1)
p.22の「図48 ランダムな名前、ランダムな作成日でファイルを作成するスクリプト(CreateRandomFiles.ps1)」の最終行として「}」が抜けていました。 お詫びして訂正いたします。
特集3 ラズパイでコロナ感染症対策
p.59の「図2 pirm_sensor.pyのソースコード」の先頭にある「1 !/usr/bin/env python3」は「2 #!/usr/bin/env python3」の誤りです。お詫びして訂正します。
コラム「ユニケージは従来のやり方と何が違うのか(前編)」
2021年6月号(Vol.72)と同じ内容でした。2021年8月号(Vol.73)のコラムはこちらから閲覧できます(Kindle版やPDF版は反映済みです)。次号となる2021年10月号(Vol.74)にも掲載する予定です。お詫びして訂正します。
情報は随時更新致します。
シェルスクリプトマガジンvol.73 Web掲載記事まとめ
004 レポート Windows 11発表
005 レポート HE C++ Transpiler
006 NEWS FLASH
008 特集1 PowerShellを学ぼう/三沢友治 コード掲載
024 特集2 JenkinsによるCI/CD/長久保篤、酒井利治 コード掲載
042 特集3 ラズパイでコロナ感染症対策/麻生二郎 コード掲載
066 特別企画 新・地球シミュレータ(ES4)への招待/今任嘉幸、上原均
074 Raspberry Piを100%活用しよう/米田聡
076 機械学習ことはじめ/川嶋宏彰 コード掲載
085 Hello Nogyo!
086 CRM/桑原滝弥、イケヤシロウ
088 レッドハットのプロダクト/宇都宮卓也
096 MySQLのチューニング/稲垣大助
104 法林浩之のFIGHTING TALKS/法林浩之
106 中小企業手作りIT化奮戦記/菅雄一
110 Pythonあれこれ/飯尾淳 コード掲載
114 香川大学SLPからお届け!/岩本和真 コード掲載
120 Bash入門/大津真
130 Techパズル/gori.sh
131 コラム「ユニケージは従来のやり方と何が違うのか(前編)」/シェル魔人
特集1 PowerShellを学ぼう(Vol.73記載)
著者:三沢 友治
PowerShellとは、米Microsoft社が開発したシェルおよびスクリプト言語です。Windows OSや、Microsoft Office製品に関連するサービス「Microsoft 365」を管理するのに活用されています。本特集では、これからPowerShellに取り組み始めたい人を対象に、PowerShellについて基本から分かりやすく解説します。
シェルスクリプトマガジン Vol.73は以下のリンク先でご購入できます。
図47 時刻を取得するスクリプト(writeTime.ps1)
1 2 3 4 5 6 7 8 |
# 現在の時刻で時間オブジェクトを取得 # 時間オブジェクトは文字列ではなく時間の情報を保持している # そのため一部の情報を容易に取得できる $date = Get-Date # 取得した時間から指定のフォーマットでファイルを上書き # 時間を取得するhh:mmを指定し、その内容をfile.txtに上書きしている。追記したい場合は >> file.txtとする $date.ToString("hh:mm") > file.txt |
図48 ランダムな名前、ランダムな作成日でファイルを作成するスクリプト(CreateRandomFiles.ps1)
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 |
# 指定したディレクトリにランダムな内容のファイルを指定サイズで作成する # 成功時はTrue、失敗時はFalseを返す Function CreateRandomBiteArrayFile([string]$Path, [int]$sizeMB) { try { # ランダムな10文字のファイル名を作成 $fileName = -join ((1..10) | %{(65..90) + (97..122) | Get-Random} | foreach-object {[char]$_}) # フルパスのファイル名を作る $fullFileName = $Path + "\" + $fileName + ".tmp" # ファイルの存在を確認 if(Test-Path $fullFileName) { # 同一ファイル名が失敗として終了する return $false } # 指定サイズでバイト配列を作成する $file = new-object byte[] ($sizeMB * 1024 * 1024) # バイト配列にランダムデータを入れる (new-object Random).NextBytes($file) # IO APIを利用してファイルに書き込む [IO.File]::WriteAllBytes($fullFileName, $file) # 100日の間でランダムな日を決める $day = Get-Random -maximum 100 # ファイルの作成日付を変更する(100日の間でランダム) Set-ItemProperty $fullFileName -Name LastWriteTime -Value (Get-Date).addDays($day * -1) return $true } catch { # サンプルスクリプトのため例外を大きく割愛している # 実務的なコードでは例外発生個所が分かる範囲でCatchするのが理想となる(同じ例外が発生しない粒度で) return $false } } # ファイルを格納するディレクトリ(今回の指定はカレントディレクトリ) $fileDirectoryPath = pwd # ファイルサイズ(Mバイト指定) $size = 1 # 作成ファイル数 $num = 10 # num個のファイルが作成される # ただし、ランダムなファイル名が被る場合、作成されるファイルが減る @(1..$num) | Foreach{ CreateRandomBiteArrayFile $fileDirectoryPath $size } |
特集2 JenkinsによるCI/CD(Vol.73記載)
著者:長久保 篤、酒井 利治
ソフトウエアの開発・生産性を高めるには「CI(Continuous Integration)/CD(Continuous Delivery)」が不可欠となっています。本特集では、CI/CDとは何か、オープンソースソフトウエアの「Jenkins」を用いてCI/CD環境を構築する方法を分かりやすく解説します。
シェルスクリプトマガジン Vol.73は以下のリンク先でご購入できます。
図9 Dockerfileの内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
FROM jenkins/jenkins:2.277.4-lts-jdk11 USER root RUN apt update && apt install -y apt-transport-https \ ca-certificates curl gnupg2 \ software-properties-common RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - RUN apt-key fingerprint 0EBFCD88 RUN add-apt-repository \ "deb [arch=amd64] https://download.docker.com/linux/debian \ $(lsb_release -cs) stable" RUN apt update && apt install -y docker-ce-cli USER jenkins RUN jenkins-plugin-cli --plugins "blueocean:1.24.6 docker-workflow:1.26" |
図21 Jenkinsfileの内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
pipeline { agent any stages { stage('ビルド') { steps { sh 'mvn -B -DskipTest clean package' } } stage('テスト') { steps { sh 'mvn -B test' } } stage('デプロイ') { steps { sh './jenkins/scripts/deliver.sh' } } } } |
図41 説明した内容を反映したJenkinsfile
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 |
pipeline { agent { label 'mvn3' } stages { stage('ビルド') { steps { sh 'mvn -B -DskipTest clean package' archiveArtifacts artifacts: 'target/*.jar' } } stage('テスト') { steps { sh 'mvn -B test' } post { always { junit 'target/surefire-reports/*.xml' } } } stage('デプロイ') { when { branch 'master' beforeInput true } input { message "デプロイしますか?" } steps { sh './jenkins/scripts/deliver.sh' } } } post { failure { emailext ( subject: "失敗: プロジェクト '${env.JOB_NAME} [${env.BUILD_NUMBER}]'", body: """次のページでコンソールの出力を確認してください: ${env.BUILD_URL}""", recipientProviders: [developers()] ) } } } |
特集3 ラズパイでコロナ感染症対策(Vol.73記載)
著者:麻生 二郎
新型コロナウイルス感染症対策として、日々の手洗いと検温は重要です。ただ、1人暮らしの人など、それらを習慣化するのは難しいかもしれません。本特集では、小型コンピュータボード「Raspberry Pi」(ラズパイ)と電子回路を用いて、新型コロナウイルス感染症対策を習慣化できるような支援システムを構築します。
シェルスクリプトマガジン Vol.73は以下のリンク先でご購入できます。
Part2 感染症対策システムを完成する
図2 pirm_sensor.pyのソースコード
1 2 3 4 5 6 7 8 9 |
#!/usr/bin/env python3 import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) GPIO.setup(21,GPIO.IN) pir = GPIO.input(21) print(pir) |
図3 temp_sensor.pyのソースコード
1 2 3 4 5 6 7 8 9 |
#!/usr/bin/env python3 from smbus2 import SMBus from mlx90614 import MLX90614 bus = SMBus(1) sensor = MLX90614(bus, address=0x5A) print(sensor.get_ambient(),sensor.get_object_1()) bus.close() |
図4 go_out.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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
#!/bin/bash ##外出確認の初期値と玄関にいる時間(秒) GOOUT_CHECK=0 STAY_TIME=3 ##表面体温の初期値と基準値 OUTSIDE_TEMP=0 OUTSIDE_TEMP_BASE=32 ##周辺温度の初期値と基準値 AMBIENT_TEMP=35 AMBIENT_TEMP_BASE=28 ##体内温度の基準値 INTSIDE_TEMP_BASE=36.7 ##体内温度との差分 INSIDE_BODY_DIFF=4.2 ##メッセージ集 MESSAGE1="おはようございます。出かける前に検温をしてください" MESSAGE2="周辺温度が${AMBIENT_TEMP}であり、検温に適していません。エアコンをかけてください" MESSAGE3="周辺温度が${AMBIENT_TEMP}です。検温を開始します。おでこをセンサーに向けてください" MESSAGE4="今朝の体温は${INSIDE_TEMP}です。正常値なので外出可能です。マスクを着用し、予備のマスクも持って出かけてください" MESSAGE5="今朝の体温は${INSIDE_TEMP}です。熱がありますので、正確に測れる体温計で再度測定してください" ##音声メッセージ関数 function voice_message(){ echo $1 | open_jtalk -m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice -x /var/lib/mecab/dic/open-jtalk/naist-jdic -ow /dev/stdout | aplay --quiet } ##外出の確認 while [ ${GOOUT_CHECK} -lt ${STAY_TIME} ] do if [ $(pirm_sensor.py) -eq 1 ];then GOOUT_CHECK=$(echo "${GOOUT_CHECK} + 1" | bc) else GOOUT_CHECK=0 fi sleep 1 done voice_message ${MESSAGE1} ##周辺温度の確認 while [ ${AMBIENT_TEMP} -ge ${AMBIENT_TEMP_BASE} ] do AMBIENT_TEMP=$(/usr/bin/python3 temp_sensor.py | cut -d " " -f 1) voice_message ${MESSAGE2} sleep 5 done voice_message ${MESSAGE3} ##検温 while [ ${OUTSIDE_TEMP} -gt ${OUTSIDE_TEMP_BASE} ] do OUTSIDE_TEMP=$(/usr/bin/python3 temp_sensor.py | cut -d " " -f 2) sleep 1 done echo "$OUTSIDE_TEMP,$(date +%Y%m%d-%H:%M)" >> ${HOME}/body_temp.csv ##測定結果 INSIDE_TEMP=$(echo "scale=1; ${OUTSIDE_TEMP} + ${INSIDE_BODY_DIFF}" | bc) if [ ${INSIDE_TEMP} -lt ${OUTSIDE_TEMP_BASE} ]; then voice_message ${MESSAGE4} else voice_message ${MESSAGE5} fi |
図5 handwash_mouthwash.pyのソースコード
1 2 3 4 5 6 7 8 9 10 11 |
#!/usr/bin/env python3 import board import busio i2c = busio.I2C(board.SCL, board.SDA) ads = ADS.ADS1115(i2c) chan1 = AnalogIn(ads, ADS.P0) chan2 = AnalogIn(ads, ADS.P1) print(chan1.voltage,chan2.voltage) |
図6 go_home.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 52 53 54 55 |
#!/bin/sh ##帰宅確認の初期値 GOHOME_CHECK=0 ##手洗いとうがいの初期値 HAND_WASH=1 MOUTH_WASH=1 ##ハンドソープのポンプ圧力基準値 POMP_PUSH_BASE=3 ##うがい用コップの重量基準値 CUP_WEIGHT_BASE=3 ##メッセージ集 MESSAGE1="おかえりなさい。帰ってきたら,手を洗って、うがいをしましょう" MESSAGE2="手洗いを確認しました" MESSAGE3="うがいを確認しました" ##音声メッセージ関数 function voice_message(){ echo $1 | open_jtalk -m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice -x /var/lib/mecab/dic/open-jtalk/naist-jdic -ow /dev/stdout | aplay --quiet } ##帰宅の確認 while [ ${GOHOME_CHECK} == 1 ] do GOHOME_CHECK=$(pirm_sensor.py) sleep 1 done voice_message ${MESSAGE1} ##手洗いとうがいの検出 while [ ${HANDWASH} == 1 -o ${MOUTHWASH} == 1 ] do ##手洗い検出の処理 HANDWASH_MOUTHWASH=$(handwash_mouthwash.py) if [ $(cut -d " " -f 1 ${HANDWASH_MOUTHWASH}) -gt ${POMP_PUSH_BASE} ]; then if [ $(HANDWASH) == 1 ]; then voice_message ${MESSAGE2} echo $(date +%Y%m%d-%H%M-%S) >> ${HOME}/handwash.csv fi HANDWASH=0 fi ##うがい検出の処理 if [ $(cut -d " " -f 2 ${HANDWASH_MOUTHWASH}) -lt ${CUP_WEIGHT_BASE} ]; then if [ $(MOUTHWASH) == 1 ]; then voice_message ${MESSAGE3} echo $(date +%Y%m%d-%H%M-%S) >> ${HOME}/mouthwash.csv fi MOUTHWASH=0 fi sleep 0.5 done |
図7 open_close.pyのソースコード
1 2 3 4 5 6 7 8 9 10 |
#!/usr/bin/env python3 import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) GPIO.setup(20,GPIO.IN) window = GPIO.input(20) print(window) |
図8 ventilation.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 ##メッセージ MESSAGE="窓を開けて空気を入れ換えましょう" ##音声メッセージ関数 function voice_message(){ echo $1 | open_jtalk -m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice -x /var/lib/mecab/dic/open-jtalk/naist-jdic -ow /dev/stdout | aplay --quiet } ##在宅の確認 STAY_HOME=$(pirm_sensor.py) ##開閉の確認 OPEN_CHECK=$(open_close.py) ##換気の通知 if [ ${STAY_HOME} == 1 -a ${OPEN_CHECK} == 0 ]; then voice_message ${MESSAGE} sleep 180 if [ ${OPEN_CHECK} == 1 ]; then echo $(date +%Y%m%d-%H%M-%S) >> ${HOME}/ventilation.csv fi fi |
図9 spo2_sensor.pyのソースコード
1 2 3 4 5 6 7 8 9 10 11 |
#!/usr/bin/env python3 import max30102 import hrcalc m = max30102.MAX30102() red, ir = m.read_sequential() hr_spo2 =hrcalc.calc_hr_and_spo2(ir, red) print(hr_spo2[2], hr_spo2[3]) |
図10 spo2.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/bash ##在宅状態の初期値 STAY_HOME=0 ##メッセージ集 MESSAGE1="寝るときは血中酸素飽和度を測定しましょう" MESSAGE2="酸素が不足しています。医療機器で正確な値を測定してください。同じ値を示せば、至急病院に連絡しましょう" ##音声メッセージ関数 function voice_message(){ echo $1 | open_jtalk -m /usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice -x /var/lib/mecab/dic/open-jtalk/naist-jdic -ow /dev/stdout | aplay --quiet } ##在宅の確認 While [ ${STAY_HOME} == 0 ] do STAY_HOME=$(pirm_sensor.py) sleep 180 done ##血中酸素飽和度測定の通知 voice_message ${MESSAGE1} sleep 300 ##血中酸素飽和度の測定開始 while [ 1 ] do SPO2=$(/usr/bin/python3 spo2_sensor.py) SPO2_VALUE=$(echo ${SPO2} | cut -d " " -f 1) SPO2_JUDGE=$(echo ${SPO2} | cut -d " " -f 2) if [ ${SPO2_JUDGE} == "True" ]; then echo "${SPO2_VALUE} $(date +%Y%m%d-%H%M)" >> ${HOME}/spo2.csv if [ ${SPO2_VALUE} -le 93 ]; then voice_message ${MESSAGE2} exit 0 fi fi sleep 300 done |
Pythonあれこれ(Vol.73掲載)
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第3回は、Pythonの言語機能である「ジェネレータ」に親しむための活用例を紹介します。
シェルスクリプトマガジン Vol.73は以下のリンク先でご購入できます。
図2 「jugem.txt」の内容を行単位で反転して表示するPythonコード
1 2 3 4 5 6 7 8 9 |
#!/usr/bin/env python def readJugemu(): with open('jugemu.txt', 'r') as f: return f.readlines() lines = readJugemu() for l in lines: print(l.rstrip()[::-1]) |
図5 lines変数を使わないコード例
1 2 3 4 5 6 7 8 |
#!/usr/bin/env python def readJugemu(): with open('jugemu.txt', 'r') as f: return f.readlines() for l in readJugemu(): print(l.rstrip()[::-1]) |
図6 ジェネレータを使用したPythonコード「reverse2.py」
1 2 3 4 5 6 7 8 9 |
#!/usr/bin/env python def readJugemu(): with open('jugemu.txt', 'r') as f: for line in f: yield line for l in readJugemu(): print(l.rstrip()[::-1]) |
図7 関数readJugemu()が返すデータの種類を調べるPythonコード「test.py」
1 2 3 4 5 6 7 8 |
#!/usr/bin/env python def readJugemu(): with open('jugemu.txt', 'r') as f: return f.readlines() lines = readJugemu() print(type(lines)) |
図8 ジェネレータ関数readJugemu()が返すデータの種類を調べるPythonコード「test2.py」
1 2 3 4 5 6 7 8 9 |
#!/usr/bin/env python def readJugemu(): with open('jugemu.txt', 'r') as f: for line in f: yield line lines = readJugemu() print(type(lines)) |
図9 ジェネレータ関数readJugemu()をfor文で使用するPythonコード「reverse3.py」
1 2 3 4 5 6 7 8 9 10 11 |
#!/usr/bin/env python def readJugemu(): with open('jugemu.txt', 'r') as f: for line in f: print('readJugemu') yield line for l in readJugemu(): print('main: ', end='') print(l.rstrip()[::-1]) |
図11 ハノイの塔の解を求めるPythonコード「hanoi.py」
1 2 3 4 5 6 7 8 9 10 11 12 |
#!/usr/bin/env python def hanoi(n, src, via, dst): if n <= 1: yield src, dst else: yield from hanoi(n-1, src, dst, via) yield src, dst yield from hanoi(n-1, via, src, dst) for src, dst in hanoi(3, 'A', 'B', 'C'): print(f'{src}->{dst}') |
図12 yield from構文を使わない場合のコード
1 2 3 4 5 6 7 8 9 10 11 12 |
#!/usr/bin/env python def hanoi(n, src, via, dst): if n <= 1: yield src, dst else: for s, d in hanoi(n-1, src, dst, via): yield s, d yield src, dst for s, d in hanoi(n-1, via, src, dst): yield s, d for src, dst in hanoi(3, 'A', 'B', 'C'): print(f'{src}->{dst}') |
シェルスクリプトマガジンvol.72 Web掲載記事まとめ
004 レポート eBPF for Windows
005 レポート TechLIONが10周年
006 NEWS FLASH
008 特集1 本格的なホームサーバーを構築/麻生二郎 コード掲載
020 特集2 MDSを始めよう!(基礎編)/生駒眞知子
029 Hello Nogyo!
030 特別企画 ローコード開発基盤 OutSystems/阿島哲夫
040 Raspberry Piを100%活用しよう/米田聡 コード掲載
042 機械学習ことはじめ/川嶋宏彰 コード掲載
052 インタプリタ/桑原滝弥、イケヤシロウ
054 レッドハットのプロダクト/暮林達也
066 Pythonあれこれ 飯尾淳
070 香川大学SLPからお届け!/重松亜夢 コード掲載
076 法林浩之のFIGHTING TALKS/法林浩之
078 中小企業手作りIT化奮戦記/菅雄一
082 MySQLのチューニング/稲垣大助
090 Bash入門/大津真
098 Techパズル/gori.sh
099 コラム「ロールオーバーラップのススメ」/シェル魔人
特集1 本格的なホームサーバーを構築(Vol.72記載)
著者:麻生 二郎
小型コンピュータボードの最上位モデルである「Raspberry Pi 4 Mobel B」の4G/8Gバイト版と、人気のLinuxディストリビューションのサーバー版「Ubuntu Server」を組み合わせて、本格的なサーバーを構築しましょう。本特集では、サーバー向けにハードウエアを強化する方法を紹介します。
シェルスクリプトマガジン Vol.72は以下のリンク先でご購入できます。
図20 無線LANの設定
1 2 3 4 5 6 |
wifis: wlan0: access-points: SSID: password: パスワード dhcp4: true |
図27 固定IPアドレスを割り当てる
1 2 3 4 5 6 7 8 9 10 |
wifis: wlan0: dhcp4: false addresses: [192.168.10.100/24] gateway4: 192.168.10.1 nameservers: addresses: [192.168.10.1] access-points: SSID: password: パスワード |
Raspberry Piを100%活用しよう(Vol.72掲載)
著者:米田 聡
小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第3回は、温度、湿度、気圧を測定する拡張基板を扱います。
シェルスクリプトマガジン Vol.72は以下のリンク先でご購入できます。
図4 温度、湿度、気圧を測定するサンプルプログラム(sample.py)
1 2 3 4 5 6 7 8 9 10 |
import smbus2 import bme280 BME_ADDRESS = 0x76 # BME280のI2Cアドレス bus = smbus2.SMBus(1) data = bme280.sample(bus, BME_ADDRESS) # 測定データを得る print("温度: %.2f ℃" % data.temperature) print("気圧: %.2f hPa" % data.pressure) print("湿度: %.2f %%" % data.humidity) |
機械学習ことはじめ(Vol.72掲載)
著者:川嶋 宏彰
本連載では、機械学習の基礎となるさまざまな手法の仕組みや、それらの手法のPythonでの利用方法を解説していきます。第1回となる今回は、機械学習の概要についても解説します。また、「k近傍法」という手法を使いながら機械学習の重要な概念をいくつか紹介します。
シェルスクリプトマガジン Vol.72は以下のリンク先でご購入できます。
図7 データセットを読み込んで散布図行列をプロットするPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import seaborn as sns import matplotlib.pyplot as plt plt.rcParams['font.size'] = 14 # seabornより読み込む場合 penguins = sns.load_dataset('penguins') # seabornではなくpalmerpenguinsから読み込む場合 # from palmerpenguins import load_penguins # penguins = load_penguins() # 散布図行列をプロット sns.pairplot(penguins, hue='species') plt.show() # 相関係数を計算 print(penguins.corr()) |
図9 二つの特徴量を抽出して散布図をプロットするPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
features = ['bill_depth_mm', 'body_mass_g'] target_species = ['Adelie', 'Gentoo'] # 特徴量をクラスラベルと共に取り出す df = penguins[['species'] + features].copy() df.dropna(inplace=True) # NaN が含まれる行は削除 # 読み込むデータセットを対象種のものだけにする df2 = df[df['species'].isin(target_species)].copy() print(df2.shape) # (274, 3) と表示される # カラーパレットの取得と設定 palette = sns.color_palette() palette2 = {'Adelie':palette[0], 'Gentoo':palette[2]} fig = plt.figure(figsize=(5, 5)) sns.scatterplot(data=df2, x=features[0], y=features[1], hue='species', palette=palette2) # plt.axis('equal') # ★この行は後で利用 plt.show() |
図11 k-NNによる判定をするシンプルなPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import numpy as np import scipy X = df2[features].values # 2次元特徴量 y = df2['species'].values # クラスラベル x_test = np.array([16, 4000]) # 判定したいデータ k = 5 # 近い順に何個のデータまで見るか # x_testとXの各点(各行)との距離の二乗 dist2 = ((X - x_test) ** 2).sum(axis=1) # 距離の小さいk個の点のラベル k_labels = y[np.argsort(dist2)][:k] result = scipy.stats.mode(k_labels)[0][0] # 最頻ラベル |
図12 k-NNによる判定をする改良版のPythonコード
1 2 3 4 5 6 |
from sklearn.neighbors import KNeighborsClassifier clf = KNeighborsClassifier(n_neighbors=k) clf.fit(X, y) # 学習 y_pred = clf.predict([[16, 4000], [16, 5000]]) # 一度に複数判定 print(y_pred) |
図13 分類器の決定境界を描画するPythonコード
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 matplotlib as mpl def plot_decision_boundary(X, y, clf, xylabels=features, palette=None): # 分類器clfの決定境界を描画 fig = plt.figure(figsize=(5, 5)) # 2次元空間にグリッド点を準備 xmin = X.min(axis=0) # 各列の最小値 xmax = X.max(axis=0) # 各列の最大値 xstep = [(xmax[i]-xmin[i]) / 50 for i in range(2)] # グリッドのステップ幅 xmin = [xmin[i] - 8*xstep[i] for i in range(2)] # 少し広めに xmax = [xmax[i] + 8*xstep[i] for i in range(2)] aranges = [np.arange(xmin[i], xmax[i] + xstep[i], xstep[i]) for i in range(2)] x0grid, x1grid = np.meshgrid(*aranges) # 各グリッド点でクラスを判定 y_pred = clf.predict(np.c_[x0grid.ravel(), x1grid.ravel()]) y_pred = y_pred.reshape(x0grid.shape) # 2次元に y_pred = np.searchsorted(np.unique(y_pred), y_pred) # 値をindexへ clist = palette.values() if type(palette) is dict else palette cmap = mpl.colors.ListedColormap(clist) plt.contourf(x0grid, x1grid, y_pred, alpha=0.3, cmap=cmap) sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=y, palette=palette) plt.legend() plt.xlim([xmin[0], xmax[0]]) plt.ylim([xmin[1], xmax[1]]) plt.xlabel(xylabels[0]) plt.ylabel(xylabels[1]) clf_orig = KNeighborsClassifier(n_neighbors=k) clf_orig.fit(X, y) plot_decision_boundary(X, y, clf_orig, palette=palette2) plt.show() |
図16 スケーリング後に決定境界を描画するPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() Xs = scaler.fit_transform(X) # StandardScaler を用いず以下のようにしてもよい # Xs = (X - X.mean(axis=0))/X.std(axis=0) clf_scaled = KNeighborsClassifier(n_neighbors=k) clf_scaled.fit(Xs, y) # 軸ラベル変更 xylabels = [s.replace('_mm', '_s').replace('_g', '_s') for s in features] # スケーリング後の決定境界を描く plot_decision_boundary(Xs, y, clf_scaled, xylabels=xylabels, palette=palette2) plt.show() |
図21 kの値を変えた場合の3種のペンギンの決定境界を描画するPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# 取り出す特徴量を変える features2 = ['bill_depth_mm', 'bill_length_mm'] df3 = penguins[['species'] + features2].copy() df3.dropna(inplace=True) # NaN が含まれる行は削除 Xs = scaler.fit_transform(df3[features2]) y = df3['species'] palette3 = dict(zip(y.unique(), palette)) # 3種用カラーパレット xylabels = [s.replace('_mm', '_s') for s in features2] for k in [1, 5, 11]: clf_scaled = KNeighborsClassifier(n_neighbors=k) clf_scaled.fit(Xs, y) plot_decision_boundary(Xs, y, clf_scaled, xylabels=xylabels, palette=palette3) plt.title(f'k = {k}') plt.legend(loc='upper left', borderaxespad=0.2, fontsize=12) plt.show() |
香川大学SLPからお届け!(Vol.72掲載)
著者:重松 亜夢
今回は、「gRPC」という通信プロトコルを使った、Webアプリケーションの作成方法を紹介します。gRPCを使うことで、通信量が減らせます。最近注目のマイクロサービスの連携にも活用できます。
シェルスクリプトマガジン Vol.72は以下のリンク先でご購入できます。
図3 「pb/picture.proto」ファイルに記述する内容
1 2 3 4 5 6 7 8 9 10 11 12 |
syntax = "proto3"; option go_package = "example.com/user_name/sample/pb/go/picture"; package picture; service Picture { rpc GetPictures (GetPicturesRequest) returns (GetPicturesReply) {} } message GetPicturesRequest { uint32 num = 1; } message GetPicturesReply { repeated bytes pictures = 1; } |
図4 「pb/protoc-web/Dockerfile」ファイルに記述する内容
1 2 3 4 5 6 7 |
FROM node:15-buster WORKDIR /pb RUN npm i rimraf -g RUN curl -L -O https://github.com/protocolbuffers/protobuf/releases/download/v3.15.8/protoc-3.15.8-linux-x86_64.zip RUN curl -L -O https://github.com/grpc/grpc-web/releases/download/1.2.1/protoc-gen-grpc-web-1.2.1-linux-x86_64 RUN unzip protoc-3.15.8-linux-x86_64.zip && cp ./bin/protoc /usr/local/bin/. && chmod +x /usr/local/bin/protoc RUN cp protoc-gen-grpc-web-1.2.1-linux-x86_64 /usr/local/bin/protoc-gen-grpc-web && chmod +x /usr/local/bin/protoc-gen-grpc-web |
図5 「pb/scripts/picture-compile.sh」ファイルに追記する内容
1 2 3 4 5 6 7 8 |
docker build protoc-web -t streaming-protoc-web mkdir -p js/picture docker run -v "$(pwd):/pb" -w /pb --rm streaming-protoc-web \ protoc --proto_path=. picture.proto \ --js_out=import_style=commonjs:js/picture \ --grpc-web_out=import_style=typescript,mode=grpcwebtext:js/picture mkdir -p ../services/client/src/pb cp -r ./js/* ../services/client/src/pb/ |
図6 「Makefil e」ファイルの変更内容
1 2 3 4 |
proto: pb/js/picture/picture_pb.js # make .proto pb/js/picture/picture_pb.js: pb/picture.proto bash ./pb/scripts/picture-compile.sh |
図8 「docker-compose.yaml」ファイルに追加する内容
1 2 3 4 5 6 7 |
proxy: container_name: sample-proxy-container image: envoyproxy/envoy-dev:1f642ab20b8975654482411537a6bdc5e2f6c4f6 ports: - "8080:8080" volumes: - ./services/proxy/envoy.yaml:/etc/envoy/envoy.yaml |
図9 「services/client/src/components/Picture.tsx」ファイルの内容
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 |
import { useState } from 'react'; import { GetPicturesRequest, GetPicturesReply } from "../pb/picture/picture_pb"; import { PictureClient } from "../pb/picture/PictureServiceClientPb"; import { Error } from 'grpc-web'; export const Picture = () => { const [num, setNumber] = useState(1); // 枚数の指定 const [pictures, setPictures] = useState<JSX.Element[]>([]); const jspb = require('google-protobuf'); const client = new PictureClient(`http://${window.location.hostname}:8080/server`, {}, {}); const getPictures = () => { if (num <= 0) return; const request = new GetPicturesRequest(); request.setNum(num); client.getPictures(request, {}, (err: Error, response: GetPicturesReply) => { if (err || response === null) { throw err; } setPictures(jspb.Message.bytesListAsB64(response.getPicturesList()) .map((images: string, index: number) => ( <img key={`${index}`} width="200px" src={`data:image/jpg;base64,${window.atob(images)}`} alt="pictures" /> ))); }); } const onChange = (event: React.ChangeEvent<HTMLInputElement>) => { const n = event.target.valueAsNumber; if (!isNaN(n)) { setNumber(n); } }; return ( <div> <input type="number" min="1" defaultValue="1" onChange={onChange} /> <button onClick={getPictures}>GetPictures</button> <div className="getPictures">{pictures}</div> </div> ); } |
図10 「services/client/src/App.tsx」ファイルに記述する内容
1 2 3 4 5 6 7 8 9 10 11 12 |
import {Picture} from './components/Picture' import './App.css'; function App() { return ( <div className="App"> <header className="App-header"> <Picture/> </header> </div> ); } export default App; |
特別企画 ゼロから始めるEthereum(Vol.71記載)
著者 志茂博、高畑祐輔
「インターネット誕生以来の革命」と称されることもあるブロックチェーン。そのブロックチェーン技術を利用したものの中で、幅広い用途での活用が注目されている「Ethereum(イーサリアム)」という分散アプリケーション基盤について解説します。後半では、実際に自分専用のブロックチェーンを立ち上げて操作する方法を紹介します。Ethereumとブロックチェーンの世界を体験してみてください。
シェルスクリプトマガジン Vol.71は以下のリンク先でご購入できます。
図4 「genesis.json」ファイルに記述する内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "config": { "chainId": 15 }, "nonce": "0x0000000000000042", "timestamp": "0x0", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "", "gasLimit": "0x8000000", "difficulty": "0x4000", "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x3333333333333333333333333333333333333333", "alloc": {} } |
Pythonあれこれ(Vol.71掲載)
著者:飯尾淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第1回は、アンケート結果から非定型なデータを取り出して集計します。
シェルスクリプトマガジン Vol.71は以下のリンク先でご購入できます。
図5 形態素解析の処理をするPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#!/usr/bin/env python import spacy import sys nlp = spacy.load('ja_ginza') for line in sys.stdin: # 一行ずつ処理 for sent in nlp(line.strip()).sents: # 一文ずつに分解 for token in sent: # さらに文中のトークンごとに処理 # i:トークン番号, orth_:表層形, lemma_:基本形, # pos_:品詞(英語), tag_:品詞細分類(日本語) sys.stdout.write(f'{token.i}\t{token.orth_}\t{token.lemma_}\t' f'{token.pos_}\t{token.tag_}\n') sys.stdout.write('EOS\n') |
図7 名詞の連接処理と抽出をするPythonコード
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 |
#!/usr/bin/env python import sys import re def sequence_gen(): sequence = [] for line in sys.stdin: if line == 'EOS\n': yield sequence sequence = [] continue word_info = line.strip().split('\t') pos = word_info[4].split('-') sequence.append({'surface': word_info[1], 'base': word_info[2], 'pos': pos[0]}) pattern = re.compile('N+') for seq in sequence_gen(): encode_str = ''.join('N' if w['pos'] in ('名詞') else '?' for w in seq) for m in pattern.finditer(encode_str): print(''.join(w['surface'] for w in seq[m.start():m.end()])) |
図9 データを分析してプロットするPythonコード
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 |
#!/usr/bin/env python import sys import numpy as np import matplotlib.pyplot as plt from sklearn.decomposition import PCA import spacy from matplotlib import rcParams rcParams['font.family'] = 'sans-serif' rcParams['font.sans-serif'] = ['Hiragino Maru Gothic Pro', 'Yu Gothic', 'Meiryo', 'Takao', 'IPAexGothic', 'IPAPGothic', 'VL PGothic', 'Noto Sans CJK JP'] nlp = spacy.load('ja_ginza') with open(sys.argv[1]) as f: texts = [s.strip() for s in f.readlines()] vectors = [nlp(t).vector for t in texts] pca = PCA(n_components=2).fit(vectors) trans = pca.fit_transform(vectors) pc_ratio = pca.explained_variance_ratio_ plt.figure() plt.scatter(trans[:,0], trans[:,1]) i = 0 for txt in texts: plt.text(trans[i,0]+0.02, trans[i,1]-0.02, txt) i += 1 plt.hlines(0, min(trans[:,0]), max(trans[:,0]), linestyle='dashed', linewidth=1) plt.vlines(0, min(trans[:,1]), max(trans[:,1]), linestyle='dashed', linewidth=1) plt.xlabel('PC1 ('+str(round(pc_ratio[0]*100,2))+'%)') plt.ylabel('PC2 ('+str(round(pc_ratio[1]*100,2))+'%)') plt.tight_layout() plt.show() |
特集3 ラズパイでQ&Aサイトを構築(Vol.71記載)
著者 麻生二郎
小型コンピュータボード「Raspberry Pi」(ラズパイ)の最大の特徴(Picoを除く)は、LinuxなどのOSが動作することです。そのOSとして「Ubuntu」という、パソコンやサーバー向けで人気の高いLinuxディストリビューションが利用できます。本特集では、ラズパイとUbuntuを組み合わせて、Stack Overflowに似たサービスを提供できるQ&Aサイトを構築します。
シェルスクリプトマガジン Vol.71は以下のリンク先でご購入できます。
図20 無線LANの設定
1 2 3 4 5 6 |
wifis: wlan0: access-points: SSID: password: パスワード dhcp4: true |
図27 固定IPアドレスを割り当てる
1 2 3 4 5 6 7 8 9 10 |
wifis: wlan0: dhcp4: false addresses: [192.168.10.100/24] gateway4: 192.168.10.1 nameservers: addresses: [192.168.10.1] access-points: SSID: password: パスワード |
香川大学SLPからお届け!(Vol.71掲載)
著者:樋口史弥
COVID-19の流行により、香川大学でもサークル活動に制限がかかっています。そこで、ICカードと非接触ICカードリーダーを用いて、部室の入退室をモニタリングするシステムを作成しました。部室の利用状況をリアルタイムに確認できれば密を避ける目安になりますし、何かあったときのための記録としても有用です。
シェルスクリプトマガジン Vol.71は以下のリンク先でご購入できます。
図6 IDmを読み取って表示するPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import nfc import binascii def on_connect(tag): data={'idm': binascii.hexlify(tag.idm).decode('utf-8')} print(data) return True def main(): with nfc.ContactlessFrontend('usb:ベンダーID:プロダクトID') as clf: while clf.connect(rdwr={'targets': ['212F'], 'on-connect': on_connect}): pass if __name__ == "__main__": main() |
図7 サーバーと連携させるために挿入するPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import json from os import getenv import urllib.request def post_data(data): headers = { 'Content-Type': 'application/json', } req = urllib.request.Request(getenv('REQUEST_URI'), data=json.dumps(data).encode('utf-8'), headers=headers, method='POST') try: with urllib.request.urlopen(req) as res: if res.code == 200: result = json.loads(res.read().decode('utf-8'))['result'] if result == 'in': print('IN') else: print('OUT') except urllib.error.HTTPError as err: print("err: ", err.read()) |
図12 退出かどうかを調べるSQL 文
1 2 3 4 5 6 7 |
$sql = 'SELECT * '. 'FROM log INNER JOIN user '. 'ON log.user_id=user.id '. 'WHERE exit_time IS NULL '. 'AND enter_time >= DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 1 DAY) '. 'AND user.idm=? '. 'ORDER BY log.enter_time DESC LIMIT 1'; |
図13 入室情報を表示するコード(index.js)により追加される情報の例
1 2 3 4 5 6 7 8 9 10 |
<div class="use_now"> <div> <div class="time">2021-03-12 12:54:31</div> <div class="name">guest</div> </div> <div> <div class="time">2021-03-12 12:42:27</div> <div class="name">user1</div> </div> </div> |
図15 音を鳴らすために挿入するPythonコード
1 2 3 4 5 6 7 8 9 |
from gpiozero import TonalBuzzer from gpiozero.tones import Tone from time import sleep def beep(hertz): bz = TonalBuzzer(4) bz.play(Tone(frequency=hertz)) sleep(0.2) bz.stop() |
シェルスクリプトマガジンvol.71 Web掲載記事まとめ
004 レポート Linuxシス管向け日本語講座
005 レポート Windows 10用無償RPAツール
006 NEWS FLASH
008 特集1 はじめてのExcel VBA/松井元
018 特集2 1Rでも快適なテレワーク環境/北谷明日香
030 特集3 ラズパイ4でQ&Aサイトを構築する/麻生二郎 コード掲載
040 特別企画 ゼロから始めるEthereum/志茂博、高畑祐輔 コード掲載
051 Hello Nogyo!
052 Raspberry Piを100%活用しよう/米田聡
056 レッドハットのプロダクト/田中司恩
065 MySQLのチューニング/稲垣大助
064 Webhook/桑原滝弥、イケヤシロウ
074 香川大学SLPからお届け!/樋口史弥 コード掲載
080 法林浩之のFIGHTING TALKS/法林浩之
082 中小企業手作りIT化奮戦記/菅雄一
086 Pythonあれこれ/飯尾淳 コード掲載
092 Bash入門/大津真
098 Techパズル/gori.sh
100 コラム『ユニケージの作法は「生き方」に通じる』/シェル魔人
シェルスクリプトマガジンvol.70 Web掲載記事まとめ
004 レポート RHEL互換OSのCentOS終了
005 NEWS FLASH
008 特集1 第4世代Ryzenプロセッサ/麻生二郎
014 特集2 FIDO2の最新動向/五味秀仁、板倉景子 コード掲載
026 特別企画 Forguncyを使ってみよう/須山亜紀
040 Raspberry Piを100%活用しよう/米田聡 コード掲載
044 レッドハットのプロダクト/森和哉 コード掲載
054 MySQLのチューニング/稲垣大助
059 バーティカルバーの極意/飯尾淳 コード掲載
064 CI/CD/桑原滝弥、イケヤシロウ
066 中小企業手作りIT化奮戦記/菅雄一
070 法林浩之のFIGHTING TALKS/法林浩之
072 香川大学SLPからお届け!/高嶋真輝 コード掲載
082 円滑コミュニケーションが世界を救う!/濱口誠一
084 Webアプリの正しい作り方/しょっさん コード掲載
096 シェルスクリプトの書き方入門/大津真 コード掲載
102 Techパズル/gori.sh
104 コラム「ユニケージとその将来」/シェル魔人
特集2 FIDO2の最新動向(Vol.70記載)
著者:五味秀仁、板倉景子
パスワード課題の解決に注力する業界団体FIDO(ファイド)アライアンスは、
フィッシング攻撃に耐性のある、シンプルで堅牢な認証の展開を推進してい
ます。その新しい仕様「FIDO2」は、標準化団体W3Cで策定されるWeb認証仕
様「WebAuthn」(ウェブオースン)を包含し、さらにAndroidとWindowsに加えて、iOSやmacOSにも対応するなど、プラットフォーム拡大を進めています。本特集では、FIDO2の概要や仕組み、最新動向について解説します。
シェルスクリプトマガジン Vol.70は以下のリンク先でご購入できます。
図8 publicKeyデータの例
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 |
{ "publicKey": { "attestation": "direct", "authenticatorSelection": { "authenticatorAttachment": "platform", "requireResidentKey": false, "userVerification": "required" }, "challenge": "bWhMbDgxc1h4Z3B...",, "excludeCredentials": [], "pubKeyCredParams": [ { "alg": -7, "type": "public-key" } ], "rp": { "id": "example.com", "name": "Example corporation" }, "timeout": 60000, "user": { "displayName": "Ichiro Suzuki", "id": [70, 60, ...], "name": "ichiro.suzuki@example.com” } } } |
図9 認証器に登録要求を出すJavaScriptプログラムの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
if (!window.PublicKeyCredential) { WebAuthn APIが使えない場合の処理 } // プラットフォーム認証器が使える場合 PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() .then(function () { // 以下で設定するのがPublicKeyCredentialCreationOptions var options = { "publicKey": ... }; return navigator.credentials.create(options); }).then(function (newCredentialInfo) { 生成されたクレデンシャルの処理 }).catch( function(err) { エラー処理 }); |
図10 PublicKeyCredentialデータの例
1 2 3 4 5 6 7 8 9 |
{ "id": "sL39APyTmisrjh11vghaqNfuru...", "rawId": "sL39APyTmisrjh11vghaqNf...", "response": { "attestationObject": "eyJjaGFsbGVuZ2...", "clientDataJSON": "o2NmbXRmcGFja2..." }, "type": "public-key" } |
図11 attestationObject項目に設定される情報の例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
{ "fmt": "packed", "attStmt": { "alg": -7, "sig": "MEYCIQDyCG+pKJmcV...", "x5c": [ "MIICQjCCAcmgAwIBA..." ] }, "authData": { "credentialData": { "aaguid": "vfNjNcR0U8...", "credentialId": "Y0GeiMghzi...", (略) }, (略) } } |
図12 clientDataJSON項目に設定される情報の例
1 2 3 4 5 |
{ "challenge": "bWhMbDgxc1h4Z3B...", "origin": "https://example.com", "type": "webauthn.create" } |
図14 publicKeyデータの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "publicKey": { "allowCredentials": [ { "id": "Y0GeiMghzi...", "type": "public-key" } ], "challenge": "lZgXWOY0Go6HxmQP03...", "rpId": "example.com", "timeout": 60000, "userVerification": "required" } } |
図15 認証器に認証要求を出すJavaScriptプログラムの例
1 2 3 4 5 6 7 8 |
// 以下で設定するのがPublicKeyCredentialRequestOptions var options = { "publicKey": ... }; navigator.credentials.get(options) .then(function (assertion) { アサーションをRPサーバーに返す処理 }).catch(function (err) { エラー処理 }); |
図16 PublicKeyCredentialデータの例
1 2 3 4 5 6 7 8 9 |
{ "id": "Y0GeiMghzi...", "rawId":"Y0GeiMghzi...", "response": { "authenticatorData": "yHxbnq0iOEP3QN0K...", "clientDataJSON": "eyJjaGFsbG...", "signature": "NRUVOWSMtH..." }, } |
Raspberry Piを100%活用しよう(Vol.70掲載)
著者:米田聡
小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第3回は、電源断でもラズパイを正常終了するための拡張基板を扱います。
シェルスクリプトマガジン Vol.70は以下のリンク先でご購入できます。
図3 ADRSZUPコマンドのソースコード(ADRSZUP.c)
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 |
#include <stdio.h> #include <fcntl.h> #include <stdlib.h> #include <unistd.h> #include <poll.h> #include <string.h> #include <time.h> #include <linux/reboot.h> #define TRUE 1 #define FALSE 0 /* エラーメッセージ */ void error(char *s) { fputs(s, stderr); } /* GPIO初期化 */ int initGpio(unsigned int gpio) { char buf[256]; int i, fd; // export fd = open("/sys/class/gpio/export", O_WRONLY); if( fd < 0 ) { error("/sys/class/gpio/export cant be opened \n"); return FALSE; } sprintf(buf,"%d",gpio); write(fd, buf, strlen(buf)); close(fd); // direction sprintf(buf, "/sys/class/gpio/gpio%d/direction", gpio); for( i=0; i < 10000; i++) { fd = open(buf,O_WRONLY ); if(fd >= 0) break; } if(fd < 0) { error("Direction cant opened\n"); return FALSE; } sprintf(buf,"in"); write(fd, buf, strlen(buf)); close(fd); // High -> Low falling edge sprintf(buf, "/sys/class/gpio/gpio%d/edge", gpio); for( i=0; i < 10000; i++) { fd = open(buf,O_WRONLY ); if(fd >= 0) break; } if( fd < 0 ) { error("Edge cant opended\n"); return FALSE; } sprintf(buf, "falling"); write(fd, buf, strlen(buf)); close(fd); return TRUE; } /* GPIO開放 */ int deinitGpio(unsigned int gpio) { char buf[256]; int fd; sprintf(buf, "%d", gpio); fd = open("/sys/class/gpio/unexport", O_WRONLY); if(fd < 0 ){ error("GPIO cant opened"); return FALSE; } write(fd, buf, strlen(buf)); close(fd); return TRUE; } /* シャットダウン */ int shutdown(void) { sync(); sync(); return reboot(LINUX_REBOOT_CMD_POWER_OFF); } #define PWDN_GPIO 6 // 電源断通知GPIO番号 int main(int argc, char *argv[]) { int retval = 0; int fd; char buf[256]; char c; if(! initGpio(PWDN_GPIO) ) { error("GPIO cant be initialized\n"); return 0; } // GPIOオープン sprintf(buf,"/sys/class/gpio/gpio%d/value", PWDN_GPIO ); fd = open(buf, O_RDONLY); if(fd < 0) { error("Value cant opened"); return 0; } // 空読み read(fd, &c, 1); while(1) { struct pollfd pfd; pfd.fd = fd; pfd.events = POLLPRI; pfd.revents = 0; // GPIO fallingイベント待ち lseek(fd, 0, SEEK_SET); int r = poll(&pfd, 1, -1); fputs("Power down detected\nWait for 5 seconds\n", stdout); // 5秒待つ sleep(5); read(fd, &c, 1); if( c == '0' ) { close(fd); deinitGpio(PWDN_GPIO); fputs("Shutdown now\n",stdout); retval = shutdown(); break; } } return retval; } |
図4 /etc/systemd/system/adrszup.serviceファイルの内容
1 2 3 4 5 6 7 8 9 10 11 12 |
[Unit] Description=Auto shutdown process for ADRSZUP [Service] Type=simple Restart=no User=root ExecStart=/usr/local/bin/ADRSZUP [Install] WantedBy=multi-user.target |
レッドハットのプロダクト(Vol.70記載)
著者:森和哉
近年、ビジネスのさまざまな分野において、業務の熟練者の高齢化と、後継者不足が深刻化しています。中でも、「計画策定」業務は、豊富な経験や特殊なスキルが必要とされることから、特に属人化が進行しています。「Red Hat Business Optimizer」は、そのような企業の計画策定業務を標準化し、より効率的な計画の立案を可能にします。
シェルスクリプトマガジン Vol.70は以下のリンク先でご購入できます。
図8 制約条件「すべてのシフトに従業員が割り当てられていること」のコード
■employeeRosteringScoreRules.drl
1 2 3 4 5 6 7 8 |
(略) rule "Assign every shift" when Shift(employee == null) then scoreHolder.penalize(kcontext); end (略) |
■RosterConstraintConfiguration.java
1 2 3 4 5 6 |
(略) public static final String CONSTRAINT_ASSIGN_EVERY_SHIFT = "Assign every shift"; (略) @ConstraintWeight(CONSTRAINT_ASSIGN_EVERY_SHIFT) private HardMediumSoftLongScore assignEveryShift = HardMediumSoftLongScore.ofMedium(1); (略) |
図9 制約条件「従業員が勤務を希望している時間帯へのシフトの割り当て」のコード
■employeeRosteringScoreRules.drl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
(略) rule "Desired time slot for an employee" when $availability: EmployeeAvailability( state == EmployeeAvailabilityState.DESIRED, $e : employee, $startDateTime : startDateTime, $endDateTime : endDateTime) Shift(employee == $e, $startDateTime < endDateTime, $endDateTime > startDateTime) then scoreHolder.reward(kcontext, $availability.getDuration().toMinutes()); end (略) |
■RosterConstraintConfiguration.java
1 2 3 4 5 6 7 |
(略) public static final String CONSTRAINT_DESIRED_TIME_SLOT_FOR_AN_EMPLOYEE = "Desired time slot for an employee"; (略) @ConstraintWeight(CONSTRAINT_DESIRED_TIME_SLOT_FOR_AN_EMPLOYEE) private HardMediumSoftLongScore desiredTimeSlot = HardMediumSoftLongScore.ofSoft(10); (略) |
バーティカルバーの極意(Vol.70掲載)
著者:飯尾淳
コロナ禍の影響を受けて、2020年度は大学の講義の多くがオンライン講義になりました。対面での講義に比べると、情報伝達の面でオンライン講義はかなり不利です。また、受講状況がどうだったかについても気になります。最終回となる今回は、久しぶりに棒グラフが登場します。棒グラフで、講義コンテンツがいつ視聴されたのかを可視化します。
シェルスクリプトマガジン Vol.70は以下のリンク先でご購入できます。
図4 未読率を計算するRubyスクリプト「midoku.rb」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#!/usr/bin/env ruby # # for n in `seq -w 1 13`;do # ./midoku.rb Rawdata/rawdata_$n.csv $n; # done m1 = m2 = m3 = 0 File.open(ARGV[0], "r") {|f| f.each_line {|line| a = line.chomp.split(',') m1 += a.count('未読') m2 += a.count('閲覧済') m3 += a.count('更新後未読') } } printf "%s,%d,%d,%d,%04.1f\n", ARGV[1],m1,m2,m3,m1.to_f*100/(m1+m2+m3).to_f |
図7 視聴率の総計を計算するRubyスクリプト「hours.rb」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#!/usr/bin/env ruby # # cat Rawdata/* > all.csv # ./hours.rb all.csv | sort | uniq -c | \ # sort -n -k 2 | awk '{printf("%d,%d\n", $2,$1)}' > hours.csv number = "(\\d+)" m1 = m2 = m3 = 0 File.open(ARGV[0], "r") {|f| f.each_line {|line| a = line.chomp.split(',') a.map!{|x| /#{number}:#{number}:#{number}/.match(x).to_a[1] } a -= [nil] a.map{|x| print x, "\n" } } } |
香川大学SLPからお届け!(Vol.70掲載)
著者:高嶋真輝
今回は、本物に限りなく近いデータを生成できる「敵対的生成ネットワーク」(Generative Adversarial Networks)という機械学習の技術と、「Fashion-MNIST」という服飾画像のデータセットを用いて、靴の画像生成に挑戦します。
シェルスクリプトマガジン Vol.70は以下のリンク先でご購入できます。
図3 各種ライブラリをインポートするためのコード
1 2 3 4 5 6 7 8 9 10 |
%matplotlib inline import tensorflow as tf import numpy as np import matplotlib.pyplot as plt from tensorflow.keras.datasets import fashion_mnist from tensorflow.keras import Sequential from tensorflow.keras.layers import Dense, Flatten, Reshape, LeakyReLU from tensorflow.keras.optimizers import Adam from google.colab import drive |
図4 モデルの入力次元を設定するためのコード
1 2 3 4 5 |
img_rows = 28 #画像の横軸 img_cols = 28 #画像の縦軸 channels = 1 #画像のチャンネル数(モノクロは「1」、カラーは「3」) img_shape = (img_rows, img_cols, channels) z_dim = 100 #ノイズベクトルの次元数 |
図5 生成器のモデルを構築する関数を定義するためのコード
1 2 3 4 5 6 7 8 9 10 11 12 |
# 生成器モデル構築(画像形状情報:tuple, 入力ノイズベクトル次元数:int) def generatorBuilder(img_shape, z_dim): model = Sequential() # 全結合層(ノード数:int, 入力の形状(第1層のときのみ):z_dim(int)) model.add(Dense(128, input_dim=z_dim)) # LeakyReLUによる活性化(alpha=負の場合の傾き:double) model.add(LeakyReLU(alpha = 0.2)) # 出力層(全結合層)と活性化(ノード数:int, activation = :活性化関数(string)) model.add(Dense(28*28*1, activation="tanh")) # 整形(整形後の形状:img_shape(tuple)) model.add(Reshape(img_shape)) return model |
図9 生成器のモデルを構築する関数を定義するためのコード
1 2 3 4 5 6 7 8 9 10 11 12 |
# 識別器モデル構築(画像形状情報:tuple(rows:int,cols:int,channels:int) def discliminatorBuilder(img_shape): model = Sequential() # データを並べる(第1層なので入力の形状:img_shape(tuple)) model.add(Flatten(input_shape=img_shape)) # 全結合層(ノード数:int) model.add(Dense(128)) # LeakyReLUによる活性化(alpha= : double) model.add(LeakyReLU(alpha = 0.01)) #出力層と活性化(ノード数:int, activation = :活性化関数(string)) model.add(Dense(1, activation="sigmoid")) return model |
図11 GANのシステムモデルを構築する関数を定義するためのコード
1 2 3 4 5 6 |
# GANモデル構築関数 def ganBuilder(generator, discriminator): model = Sequential() model.add(generator) model.add(discriminator) return model |
図12 学習モデルをコンパイルするためのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# 識別器モデル作成(入力形状:img_shape(tuple)) discriminator = discliminatorBuilder(img_shape) # 識別器コンパイル # (loss = :損失関数, optimizer = :最適化手法, metrics = :評価関数) discriminator.compile(loss = "binary_crossentropy", optimizer=Adam(), metrics=['accuracy']) # 生成器モデル作成(出力形状:img_shape(tuple), 入力形状:z_dim(int)) generator = generatorBuilder(img_shape, z_dim) discriminator.trainable = False # 識別器モデルの学習停止 # GANモデル作成(生成器モデル:generator, 識別器モデル:discriminator) gan = ganBuilder(generator, discriminator) # GANコンパイル:(loss = :損失関数, optimizer = :最適化手法) gan.compile(loss = "binary_crossentropy", optimizer = Adam()) |
図13 損失などのデータを格納する変数を定義するためのコード
1 2 3 |
losses = [] accuracies = [] iteration_checkpoints = [] |
図14 学習用データを準備するためのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 訓練用データの抽出 (trainData, trainLabel), (_, _) = fashion_mnist.load_data() trainData = trainData / 127.5 - 1.0 trainData = np.expand_dims(trainData, axis=3) # 真画像ラベル作成 batch_size = 128 real = np.ones((batch_size, 1)) # 偽画像ラベル作成 fake = np.zeros((batch_size, 1)) # 靴のデータの抜き出し # trainLabelは、5がサンダル、7がスニーカ、9がブーツ trainData = trainData.tolist() trainShoes = [] for i in range(len(trainLabel)): if trainLabel[i] in [5,7,9]: trainShoes.append(trainData[i]) |
図15 学習処理用の関数を定義するコード
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 |
def train(iterations, batch_size, sample_interval): for iteration in range(iterations): # 真画像群の作成 idx = np.random.randint(0, len(trainShoes), batch_size) imgs = [trainShoes[i] for i in idx] imgs = np.asarray(imgs) # 生成画像群の作成 # ランダムノイズ作成(正規分布の平均値:float, 標準偏差:float, 形状:tuple) z = np.random.normal(0,1,(batch_size, z_dim)) # 生成画像の取得 gen_imgs = generator.predict(z) # 識別器訓練:真偽のそれぞれで実施 d_loss_real = discriminator.train_on_batch(imgs, real) d_loss_fake = discriminator.train_on_batch(gen_imgs, fake) # 損失と正確さを計算(2損失の合算を半分にする) d_loss, accuracy = 0.5 * np.add(d_loss_real, d_loss_fake) ## 生成画像群の作成(GAN学習用) z = np.random.normal(0,1,(batch_size, z_dim)) g_loss = gan.train_on_batch(z, real) if (iteration + 1) % sample_interval == 0: # 記録と出力 losses.append((d_loss, g_loss)) accuracies.append(100.0 * accuracy) iteration_checkpoints.append(iteration + 1) print("%d [D loss: %f, acc: %.2f%%][G loss: %f]" % (iteration + 1, d_loss, 100.0*accuracy, g_loss)) ## 学習済みgeneratorを渡す(iteration+1は保存に使う) showSample(generator, iteration+1) |
図16 画像の表示と保存をする関数を定義するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# 画像生成と表示 # (学習済みgenerator:TensorFlowSequential, 画像表示用の行, 列:int) def showSample(generator, iteration, img_grid_rows=4, img_grid_cols=4): z = np.random.normal(0, 1, (img_grid_rows*img_grid_cols, z_dim)) gen_imgs = generator.predict(z) # 画像生成 gen_imgs = 0.5 * gen_imgs + 0.5 # 画素値を[0, 1]でスケーリング #画像の表示領域の確保(画像表示の行, 列, 画像サイズ, 表示画像の行と列を共有) fig,axs = plt.subplots(img_grid_rows, img_grid_cols, figsize=(4,4), sharey=True, sharex=True) cnt = 0 #画像の表示 for i in range(img_grid_rows): for j in range(img_grid_cols): axs[i, j].imshow(gen_imgs[cnt, :, :, 0], cmap='gray') axs[i,j].axis('off') cnt += 1 # 現在のfigをGoogleドライブのMy Driveに保存 fig.savefig('./gdrive/My Drive/'+str(iteration)+'FMShoesfig.png') |
シェルスクリプトの書き方入門(Vol.70記載)
著者:大津真
本連載ではシェルスクリプトの書き方をやさしく紹介します。対象とするシェルは、多くのLinuxディストリビューションが標準シェルとして採用する「Bash」です。最終回となる今回は、関数について解説します。
シェルスクリプトマガジン Vol.70は以下のリンク先でご購入できます。
図1 シェルスクリプト「hello1.sh」の内容
1 2 3 4 5 6 7 8 |
#!/bin/bash function hello() { echo "Hello Function" } hello hello hello |
図2 シェルスクリプト「param_test1.sh」の内容
1 2 3 4 5 6 7 8 9 10 11 12 |
#!/bin/bash function param_test() { echo "\$0: $0" echo "\$1: $1" echo "\$2: $2" echo "\$3: $3" echo "\$4: $4" echo "\$#: $#" echo "\$@: $@" } param_test 春 夏 秋 冬 |
図3 シェルスクリプト「scope1.sh」の内容
1 2 3 4 5 6 7 8 9 |
#!/bin/bash function scope_test() { g1="グローバル" local l1="ローカル" } scope_test echo "g1: $g1" echo "l1: $l1" |
図4 シェルスクリプト「file_test1.sh」の内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#!/bin/bash function check_file() { if [[ -f $1 ]]; then echo "ファイルが存在します" return 0 else echo "$1が見つかりません" return 1 fi } if [[ $# -eq 0 ]]; then echo "引数でファイルを指定してください" exit 1 fi check_file $1 echo "終了ステータス: $?" |
図5 ライブラリファイル「my_lib.sh」の内容
1 2 3 4 5 6 7 8 9 |
function check_file() { if [[ -f $1 ]]; then echo "ファイルが存在します" return 0 else echo "$1が見つかりません" return 1 fi } |
図6 シェルスクリプト「file_test2.sh」の内容
1 2 3 4 5 6 7 8 9 10 |
#!/bin/bash source ./my_lib.sh if [[ $# -eq 0 ]]; then echo "引数でファイルを指定してください" exit 1 fi check_file $1 echo "終了ステータス: $?" |
図7 シェルスクリプト「sum_test1.sh」の内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#!/bin/bash function calc_sum() { sum=0 for n in $(seq $1) do sum=$(expr $sum + $n) done echo $sum } # 引数があるかどうかのチェック if [[ $# -eq 0 ]]; then echo "引数を指定してください" exit 1 fi # 引数が整数値であることを調べる expr $1 + 1 &> /dev/null if [[ $? -ge 2 ]] then echo "整数値を指定してください" exit $? fi # calc_sum関数を呼び出す echo "1から$1までの総和: $(calc_sum $1)" |
図8 シェルスクリプト「test1.sh」の内容
1 2 3 4 5 6 7 8 |
#!/bin/bash num=10 sum=0 for n in $(seq $num) do sum=$(expr $sum + $n) done echo "総和: $sum" |
Webアプリケーションの正しい作り方(Vol.70記載)
著者:しょっさん
ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならないでしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。最終回は、本番環境として恥ずかしくないシステムをリリースする方法を解説します。
シェルスクリプトマガジン Vol.70は以下のリンク先でご購入できます。
図3 最終的に実装された各レイヤーのソースコード
■「src/api/expense.ts」の経費精算部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import { Request, Response, NextFunction } from "express"; import Express from "express"; import { ExpenseModel } from "./interfaces/expenseModel"; import { ExpenseController } from "./interfaces/expenseController"; const router = Express.Router(); // DBモデルおよびコントローラのインスタンス化 const expense_model = new ExpenseModel(); const expense_controller = new ExpenseController(expense_model); // コントローラへ、DBモデルのインスタンスを引き継いでいます // POST 経費の入力 router.post("/", async (req: Request, res: Response, next: NextFunction) => { const result = await expense_controller.submitExpense(req.body!); res.send(result); }); (略) export default router; |
■「src/interfaces/expenseController.ts」の経費精算部分
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 { SubmitExpense } from "../usecases/SubmitExpense"; import { IExpenseValue } from "../domains/expenseEntity"; import { ExpenseRepository } from "../adapters/ExpenseRepository"; import { IExpenseModel } from "./IExpenceModel"; export class ExpenseController { private expenseRepository: ExpenseRepository; // DBモデルのインスタンスを基にリポジトリをインスタンス化 constructor(model: IExpenseModel) { this.expenseRepository = new ExpenseRepository(model); } async submitExpense( expense: IExpenseValue ): Promise<IExpenseValue> { try { // リポジトリのインスタンスをユースケースへ引き継いでユースケースを実行 const usecase = new SubmitExpense(this.expenseRepository); const result = await usecase.execute(expense); return result.read(); } catch (error) { throw new Error(error); } } (略) } |
■「src/adapters/ExpenseRepository.ts」の経費精算部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import { ExpenseEntity } from "../domains/expenseEntity"; import { IExpenseRepository } from "./IExpenseRepository"; import { IExpenseModel } from "../interfaces/IExpenceModel"; export class ExpenseRepository implements IExpenseRepository { private expense_model: IExpenseModel; // コントローラから引き継いだDBモデルのインスタンスをここで保持 constructor(model: IExpenseModel) { this.expense_model = model; } store(expense: ExpenseEntity): Promise<ExpenseEntity> { // 特にフォーマットなど変更を今回はしていないので、そのまま保管メソッドをコール return this.expense_model.store(expense); } } |
図5 アクセス認可を制御する方法
■ロールによって実行するモデル操作を変更する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
export class ExpenseModel implements IExpenseModel { private _userModel: IUserModel constructor(user: IUser) { this._userModel = user; } (略) findUnapproval(boss_id: number): Promise<ExpenseEntity[]> { if (this._userModel.role.find(role => role === 'APPROVER') { return Expense.findAll({ where: Sequelize.literal( `approval = ${approval_status.unapproved}` ), (略) } else { return Expense.findAll({ where: Sequelize.literal( `approval = ${approval_status.unapproved} and user_id IN (SELECT id FROM users WHERE boss_id = '${boss_id}')` ), (略) } } |
■経費精算テーブルにアクセス認可用の「role」カラムを追加してアクセス認可させる
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
export class ExpenseModel implements IExpenseModel { private _userModel: IUserModel constructor(user: IUser) { this._userModel = user; } (略) findUnapproval(boss_id: number): Promise<ExpenseEntity[]> { return Expense.findAll({ where: { approval: ${approval_status.unapproved} role: ${this._userModel.role} } (略) } } |
Jetson & Pi 電力測定ボードの販売開始
シェルスクリプトマガジン Vol.69(2020年12月号)の特集1で扱った、Jetson Nano 開発キットとRaspberry Piの両方で利用できる拡張基板「Jetson Nano & Pi 電力測定ボード」の販売を、自社サイトでも開始いたしました。特別価格の4540円(別途送料360円)で購入できます。
Jetson Nano & Pi 電力測定ボードは、デジタル電流・電圧・電力計モジュール「INA260」と単色有機ELディスプレイ「SSD1306」(OLEDモジュール)を搭載しています。INA260で、Jetson Nano 開発キットやRaspberry Piの消費電力をリアルタイムに測定できます。そして、SSD1306にはさまざまな情報を表示可能です。
Jetson Nano & Pi 電力測定ボードを購入して、Jetson Nano 開発キットとRaspberry Piを便利に使いましょう。
購入ページはこちらです。
バーティカルバーの極意(Vol.69掲載)
著者:飯尾 淳
Twitterのトレンドを分析に関する解説の最終回です。これまで、TwitterのトレンドAPIを叩いてトレンドを集め、Twitter Standard search APIでトレンドに関するツイートを収集、そのデータに基づいてトレンドの構造を分析する手法を紹介しました。今回は、その日の主要なトピックは何だったかを可視化する方法を解説します。
シェルスクリプトマガジン Vol.69は以下のリンク先でご購入できます。
図7 JSONデータを得るスクリプト(get_data.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 |
#!/usr/bin/env python import json import urllib.request import sys import re # replace urlbase to the base address of your application # this script should be called as $ ./get_data.py 2019-02-01 # which means the argument should be a date formatted in YYYY-MM-DD urlbase = 'http://iiojun.xyz/twt' jsonapi = '/api/' + sys.argv[1] word_dict = {} words = [] req = urllib.request.Request(urlbase+jsonapi) with urllib.request.urlopen(req) as res: content = json.loads(res.read().decode('utf8')) # loop for labels for item in content: label = item['label'] wid = item['id'] url2 = "{0}/api/trends/{1}".format(urlbase, wid) req2 = urllib.request.Request(url2) dct = {} # loop for words of each label with urllib.request.urlopen(req2) as res2: content2 = json.loads(res2.read().decode('utf8')) for item2 in content2[0]: word = item2['word'] freq = item2['freq'] dct[word] = freq # keep the all words used in the nodes in the array 'words' if not word in words: words.append(word) # keep the ary of {word, freq} in the dictionary 'word_dict' word_dict[label] = dct print("label", end="") for item in words: print("\t{0}".format(item), end="") print() for key in word_dict: print(key, end="") dct = word_dict[key] for item in words: freq = dct[item] if item in dct else 0.0 print("\t%6.3f" % freq, end="") print() |
図9 コサイン類似度を計算するスクリプト(calc_cos_sim.py)
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 |
#!/usr/bin/env python3 import numpy as np import sys def cos_sim(v1, v2): return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2)) lines = sys.stdin.readlines() lines.pop(0) wdic = {} visited = [] for line in lines: words = line.split('\t') label = words.pop(0) wdic[label] = list(map(lambda x: float(x),words)) for key1 in wdic: for key2 in wdic: visited.append(key2+key1) if key1 == key2 or key1+key2 in visited: continue print("{0}\t{1}\t{2:9.6f}" .format(key1,key2,cos_sim(wdic[key1],wdic[key2]))) |
図11 dotスクリプトを作成するスクリプト(mk_net.rb)
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 |
#!/usr/bin/ruby word_ary = [] freq_ary = [] node_set = [] node_color = {} color_tbl = %w(khaki lemonchiffon aliceblue mistyrose coral goldenrod aquamarine lavender palegreen gold lightpink plum yellow lightcyan lavenderblush gainsboro yellowgreen lightsteelblue palegoldenrod lightskyblue greenyellow plum cornflowerblue) th_val = 0.75 mthd = 'fdp' title = 'topicmap' STDIN.each {|line| (kw1,kw2,freq) = line.split(/\t/) word_ary.push(kw1) unless word_ary.include?(kw1) word_ary.push(kw2) unless word_ary.include?(kw2) if freq.to_f > th_val freq_ary.push(line) flag = false node_set.each {|x| if x.include?(kw1) && x.include?(kw2) flag = true break end len0 = x.length x.push(kw2) if x.include?(kw1) && !x.include?(kw2) x.push(kw1) if !x.include?(kw1) && x.include?(kw2) if len0 < x.length flag = true break end } node_set.push([kw1, kw2]) unless flag end } def get_set(ary_of_ary, x) ret_ary = [] ary_of_ary.each {|ary| ret_ary = ret_ary + ary if ary.include?(x) } return ret_ary end def delete_set(ary_of_ary, x) ary_of_ary.each {|ary| ary_of_ary.delete(ary) if ary.include?(x) } end freq_ary.each {|x| (kw1,kw2,freq) = x.split(/\t/) x1 = get_set(node_set, kw1) x2 = get_set(node_set, kw2) next if (x1 == x2) x3 = x1 | x2 delete_set(node_set, kw1) delete_set(node_set, kw2) node_set.push(x3) } word_ary.map {|x| node_color[x] = 'white' } node_set.each_with_index {|value,index| i = index % color_tbl.length value.map{|x| node_color[x] = color_tbl[i] } } print "graph \"#{title}\" {\n" print " graph [\n layout = #{mthd}\n ];\n" word_ary.each {|x| printf " \"%s\" [ fontname = \"ヒラギノ丸ゴ\"; style = \"filled\"; fillcolor = \"%s\"; fontcolor = \"%s\" ];\n", x, node_color[x], 'black' } while (!freq_ary.empty?) do (f,t,prob) = freq_ary.shift.split(/\t/) printf " \"%s\" -- \"%s\" [label = \"%4.2f\"];\n", f, t, prob end print "}\n" |
香川大学SLPからお届け!(Vol.69掲載)
著者:石塚 美伶
今回は、音楽の「耳コピ」を支援する音源可視化ツールをPythonで制作する方法について紹介します。制作するツールは、WAVファイルを読み取って音を判別し、その音をピアノの鍵盤で示すGIFアニメーションを生成します。
シェルスクリプトマガジン Vol.69は以下のリンク先でご購入できます。
図4 「main.py」ファイルに記述する内容(その1)
1 2 3 4 5 6 7 8 |
import scipy.io.wavfile # WAVファイルを読み込むために使用 from scipy import signal # 極大値を求めるために使用 import numpy as np # データの整形や高速フーリエ変換に使用 import pandas as pd # ピアノの音階辞書を作成するために使用 import matplotlib.pyplot as plt # 図の作成に使用 import matplotlib.animation as anm # アニメーション作成に使用 import matplotlib.patches as pat # 長方形を描画するために使用 import time # 時間を表示するために使用 |
図5 「main.py」ファイルに記述する内容(その2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
wav_filename = "./source.wav" # 音声ファイルの読み込み rate, data = scipy.io.wavfile.read(wav_filename) print("サンプリング周波数:", rate) print("データ数:", len(data)) if data.ndim < 2: # 配列の次元数 print("モノラル") else: print("ステレオ") data = np.ravel(data)[::2] # 連結して偶数要素を抽出する #(振幅)の配列を作成 (「-1」~「1」の範囲に正規化) data = data / 32768 # データを0.1秒ごとに分割 split_datas = np.array_split(data, int(len(data) / (rate * 0.1))) |
図6 「main.py」ファイルに記述する内容(その3)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
count = 0 ex_freqency = [] # 抽出したデータを格納するために用意 for short_data in split_datas: ex_freqency.append([]) # データを格納するために空リストを追加 # フーリエ変換により周波数成分と振幅を取得 fft_short_data = np.abs(np.fft.fft(short_data)) freqList = np.fft.fftfreq(short_data.shape[0], d=1.0/rate) maxid = signal.argrelmax(fft_short_data, order=2) # 極大値を求める for i in maxid[0]: if fft_short_data[i] > 10 and 25 < freqList[i] < 4200: ex_freqency[count].append(freqList[i]) # 周波数を格納 count += 1 |
図8 「main.py」ファイルに記述する内容(その4)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
piano_dic = pd.read_csv("./piano_dict.csv", encoding="utf-8") print(piano_dic) black_keys = piano_dic[piano_dic["scaleNameEn"].str.contains("#")].index print(black_keys) count = 0 keys = [] # 含まれる周波数の行 for row in ex_freqency: keys.append([]) # 各フレームの周波数を格納するために空リストを追加 for i in row: # 差が最小の音階 key = piano_dic.loc[abs(piano_dic.frequency - i).idxmin(), "keyNumber"] if (key in keys[count]) == False: keys[count].append(key) # 重複してなければ音階を追加 count += 1 print(keys) |
図9 「main.py」ファイルに記述する内容(その5)
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 |
fig, ax = plt.subplots(figsize = (10, 2)) # 各フレームの描画 def update(i, fig_title, data_list, ax): if i != 0: plt.cla() # 現在描写されているグラフを消去 ax.axis("off") # 軸と目盛りを削除 ax.set_xlim(0, 52.1) ax.set_ylim(-0.5, 2.5) skip = False white_count = 0 plt.title(fig_title + time.strftime("%M:%S", time.gmtime(i * 0.1))) for j in range(0, 88): if skip == True: skip = False # フラグを降ろす continue # 既に描画した白鍵をスキップする if j in black_keys: # 黒鍵の右側の白鍵を描画 color = "white" if j + 1 in data_list[i]: color = "red" # 音が鳴っていれば色を赤にする # 長方形を作成 rec = pat.Rectangle(xy = (white_count, 0), \ width = 1, height = 1.5, \ fc = color, ec = "black") ax.add_patch(rec) # Axesに長方形を追加 skip = True # スキップフラグを立てる # 白鍵の上に黒鍵を描画 color = "gray" x, y = white_count - 0.3, 0.5 w, h = 0.6, 1 else: # 白鍵を描画 color = "white" x, y = white_count, 0 w, h = 1, 1.5 if j in data_list[i]: color = "red" # 音が鳴っていれば色を赤にする # 長方形を作成 rec = pat.Rectangle(xy = (x, y), width = w, \ height = h, fc = color, ec = "black") ax.add_patch(rec) # Axesに長方形を追加 white_count += 1 # 白鍵の数をカウント # アニメーションを生成 ani = anm.FuncAnimation(fig, update, fargs=("Mimicopy ", keys, ax), \ interval=100, frames=len(keys)) # GIFファイルとして保存 ani.save("Sample.gif", writer="pillow") |
中小企業手作りIT化奮戦記(Vol.69掲載)
著者:菅 雄一
2000年にLinuxサーバーを導入して以来、筆者は、主に費用面での手軽さからOSS(オープンソースソフトウエア)を活用したシステム構築に取り組んできた。だが、2020年になってWindowsサーバーに初めて触れることになった。勤務先の会社が、バッファローの「TeraStation」という法人向けのNAS(Network Attached Storage)製品を購入し、そのOSがWindowsサーバーだったからだ。今回は、そのWindowsサーバー搭載のNAS製品を設定した話を書く。
シェルスクリプトマガジン Vol.69は以下のリンク先でご購入できます。
図3 Sambaの設定ファイル(smb.conf)の記述例
1 2 3 4 5 6 7 |
[global] map to guest = bad user guest account = nobody [public] path = /home/samba guest ok = yes |
シェルスクリプトの書き方入門(Vol.69記載)
筆者:大津 真
本連載ではシェルスクリプトの書き方をやさしく紹介します。対象とするシェルは、多くのLinuxディストリビューションが標準シェルとして採用する「Bash」です。第5回は、複数の文字列を柔軟なパターンで指定できる正規表現の基礎について解説します。
シェルスクリプトマガジン Vol.69は以下のリンク先でご購入できます。
図2 シェルスクリプト「pref1.sh」の内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#!/bin/bash if [[ $# -ne 1 ]]; then echo "都道府県名を一つ指定してください" exit 1 fi file=meibo.txt if grep ":${1}$" $file; then count=$(grep -c ":${1}$" $file) echo "-- ${1}は{count}件ありました --" else echo "-- ${1}は見つかりませんでした --" fi |
図3 シェルスクリプト「whileRead1.sh」の内容
1 2 3 4 5 6 |
#!/bin/bash while read line do echo $line done < meibo.txt |
図4 シェルスクリプト「whileRead2.sh」の内容
1 2 3 4 5 6 |
#!/bin/bash cat meibo.txt | while read line do echo $line done |
図5 シェルスクリプト「tokyo.sh」の内容
1 2 3 4 5 6 7 |
#!/bin/bash count=0 grep ":東京$" meibo.txt | while read line do echo "$((++count)):${line}" done |
図6 シェルスクリプト「addpref.sh」の内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#!/bin/bash while read line do if echo $line | grep -q ":東京$"; then echo $line | sed "s/:東京$/:東京都/" elif echo $line | grep -q -e ":大阪$" -e ":京都$"; then echo $line | sed "s/:\(..\)$/:\1府/" elif echo $line | grep -q ":北海道"; then echo $line else echo $line | sed "s/:\(..*\)$/:\1県/" fi done < meibo.txt |
シェルスクリプトマガジンvol.69 Web掲載記事まとめ
004 レポート MacのApple M1
005 レポート セキュリティゲーム「TERMINAL」
006 NEWS FLASH
008 特集1 Jetson & Pi 電力測定ボードの使い方/北崎恵凡 コード掲載
026 特集2 1日で理解するGoプログラミング/KinoCode、ハリー コード掲載
036 特別企画 Redmineで始めるプロジェクト管理/前田剛
048 Raspberry Piを100%活用しよう/米田聡 コード掲載
051 Hello Nogyo!
052 レッドハットのプロダクト/伊藤智博 コード掲載
062 Docker/桑原滝弥、イケヤシロウ
064 バーティカルバーの極意/飯尾淳 コード掲載
070 法林浩之のFIGHTING TALKS/法林浩之
072 香川大学SLPからお届け!/石塚美伶 コード掲載
076 円滑コミュニケーションが世界を救う!/濱口誠一
078 中小企業手作りIT化奮戦記/菅雄一 コード掲載
084 シェルスクリプトの書き方入門/大津真 コード掲載
092 Techパズル/gori.sh
094 コラム「長続きの秘訣」/シェル魔人
特集1 Jetson & Pi 電力測定ボードの使い方(Vol.69記載)
著者:北崎 恵凡
ユーザーコミュニティ「Jetson Japan User Group」のメンバーである筆者が設計した、小型コンピュータボードの「Jetson Nano」と「Raspberry Pi」で共通に使える拡張基板「Jetson & Pi 電力測定ボード」をシェルスクリプトマガジンオリジナルとして作成しました。本特集では、このJetson & Pi電力測定ボードの使い方を紹介します。
シェルスクリプトマガジン Vol.69は以下のリンク先でご購入できます。
Part1 Jetson & Pi 電力測定ボードを動かす
図7 電源電圧を表示するサンプルプログラム「ina260.py」のソースコード
1 2 3 4 5 |
import smbus i2c = smbus.SMBus(1) word = i2c.read_word_data(0x40, 0x02) & 0xFFFF result = ( (word << 8) & 0xFF00 ) + (word >> 8) volt = result * 1.25 / 1000 |
図9 ina260_adafruit.pyのソースコード
1 2 3 4 5 6 7 8 9 10 11 |
import time import board import adafruit_ina260 i2c = board.I2C() ina260 = adafruit_ina260.INA260(i2c) while True: print("Current: %.2f Voltage: %.2f Power: %.2f" %(ina260.current, ina260.voltage, ina260.power)) time.sleep(1) |
図12 ina260_plot.pyのソースコード
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 |
(略) import numpy as np import matplotlib.pyplot as plt import matplotlib.animation as animation (略) NUM_BUF_POINTS = 180 PLOT_INTERVAL = 1000 def get_values(): return([float("{0:.2f}".format(ina260.current / 1000)), float("{0:.2f}".format(ina260.voltage)), float("{0:.2f}".format(ina260.power / 1000))]) (略) def plot(i): global Data zone_names = get_names() zone_temps = get_values() print(zone_temps) Data = np.append(Data, np.array([zone_temps]), axis = 0) if i >= NUM_BUF_POINTS: Data = np.delete(Data, 0, axis = 0) plt.cla() plt.plot(Data, marker = 'x') plt.xlim(0, NUM_BUF_POINTS) plt.ylim(0.0, 10.0) plt.title('Current Monitor', fontsize = 14) plt.xlabel('Time', fontsize = 10) plt.ylabel('Current[A],Voltage[V],Power[W]', fontsize = 10) plt.tick_params(labelsize=10) plt.grid(True) plt.legend(labels = zone_names, loc = 'upper left', fontsize = 10) def main(): global Data zone_names = get_names() print(zone_names) Data = np.empty((0, len(zone_names)), float) fig = plt.figure(figsize=(10, 4)) ani = animation.FuncAnimation(fig, plot, fargs = (), interval = PLOT_INTERVAL) plt.show() (略) |
図14 ssd1306_stats.pyのソースコード
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 |
(略) from PIL import Image, ImageDraw, ImageFont import adafruit_ssd1306 (略) i2c = busio.I2C(SCL, SDA) (略) disp = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c) (略) width = disp.width height = disp.height image = Image.new("1", (width, height)) (略) draw = ImageDraw.Draw(image) (略) cmd = "hostname -I | cut -d' ' -f1" IP = subprocess.check_output(cmd, shell=True).decode("utf-8") cmd = "top -bn1 | grep load | awk '{printf \"CPU Load: %.2f\", $(NF-2)}'" CPU = subprocess.check_output(cmd, shell=True).decode("utf-8") cmd = "free -m | awk 'NR==2{printf \"Mem: %s/%s MB %.2f%%\", $3,$2,$3*100/$2 }'" MemUsage = subprocess.check_output(cmd, shell=True).decode("utf-8") cmd = 'df -h | awk \'$NF=="/"{printf "Disk: %d/%d GB %s", $3,$2,$5}\'' Disk = subprocess.check_output(cmd, shell=True).decode("utf-8") (略) draw.text((x, top + 0), "IP: " + IP, font=font, fill=255) draw.text((x, top + 8), CPU, font=font, fill=255) draw.text((x, top + 16), MemUsage, font=font, fill=255) draw.text((x, top + 25), Disk, font=font, fill=255) (略) disp.image(image) disp.show() |
図19 ina260_oled.pyのソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
(略) import adafruit_ina260 i2c2 = board.I2C() ina260 = adafruit_ina260.INA260(i2c2) (略) c = ina260.current v = ina260.voltage p = ina260.power print("Current: %.2f Voltage: %.2f Power: %.2f" %(c, v, p)) (略) draw.text((x, top + 0), "Current(mA): " + str("{0:.2f}".format(c)) + ' ', font=font, fill=255) draw.text((x, top + 14), "Voltage(V): " + str("{0:.2f}".format(v)) + ' ', font=font, fill=255) draw.text((x, top + 28), "Power(mW): " + str("{0:.2f}".format(p)) + ' ', font=font, fill=255) (略) disp.image(image) disp.show() (略) |
図21 ssd1306_jp_font.pyのソースコード
1 2 3 4 5 6 |
(略) font = ImageFont.truetype('usr/share/fonts/truetype/fonts-japanese-gothic.ttf', 14) (略) draw.text((x, top + 0), "てすと", font=font, fill=255) draw.text((x, top + 14), "日本語", font=font, fill=255) (略) |
図28 imagenet-console_oled.pyのソースコード
1 2 3 4 |
(略) draw.text((0, 0), "分類: " + translator.convert(class_desc), font=font, fill=255) draw.text((0, 14), "確率: " + "{:0.2f}%".format(confidence * 100), font=font, fill=255) (略) |
図32 imagenet-camera_oled.pyのソースコード
1 2 3 4 |
(略) draw.text((0, 0), "分類: " + translator.convert(class_desc), font=font2, fill=255) draw.text((0, 14), "確率: " + "{:0.2f}%".format(confidence * 100), font=font2, fill=255) (略) |
特集2 1日で理解するGoプログラミング(Vol.69記載)
著者:KinoCode、ハリー
「エンジニアが次に学びたい言語」のランキングでたびたび上位にランクインしているGo言語。本特集では、Goの特徴やプログラミングについての概要を、1日で学習できるようにコンパクトに解説します。この機会にぜひGoを学んでみてください。
シェルスクリプトマガジン Vol.69は以下のリンク先でご購入できます。
図13 挨拶文を表示するサンプルプログラム「greeting.go」のコード
1 2 3 4 5 6 7 8 |
package main import ("fmt") func main(){ fmt.Println("Good morning") fmt.Println("Good afternoon") fmt.Println("Good evening") } |
Raspberry Piを100%活用しよう(Vol.69掲載)
著者:米田 聡
小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第2回は、赤外線信号の受信が可能な拡張基板を扱います。
シェルスクリプトマガジン Vol.69は以下のリンク先でご購入できます。
図3 /boot/config.txtファイルの末尾に追加する2行
1 2 |
dtoverlay=gpio-ir,gpio_pin=4 dtoverlay=gpio-ir-tx,gpio_pin=13 |
図4 /etc/lirc/lirc_options.confファイルの変更箇所
1 2 3 4 5 |
(略) driver = default device = /dev/lirc1 (略) |
レッドハットのプロダクト(Vol.69記載)
著者:伊藤 智博
最近は昔と比べ、ビジネス要件のレベルが高くなりました。この要件を実現するためさまざまな技術が新たに誕生していますが、それらの技術を開発者が組み合わせて使用するのは非常に困難です。第5回では、これらの技術を組み合わせて高いレベルの要件を簡単に実現するJavaフレームワークの「Quarkus」を紹介します。
シェルスクリプトマガジン Vol.69は以下のリンク先でご購入できます。
図7 「quarkus-getting-started/src/main/java/sample/GreetingResource.java」ファイルの内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package sample; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @Path("/hello") public class GreetingResource { @GET @Produces(MediaType.TEXT_PLAIN) public String hello() { return "hello"; } } |
Vol.68 補足情報
特集1 Windows 10でWSL 2を使おう
WSL 2ですが、Windows 10 May 2020 Updateを適用したWindows 10 バージョン2004以降だけでなく、バージョン1909や1903にもバックポートされて使えるようになりました。詳しくは、こちらを参照してください。
情報は随時更新致します。
シェルスクリプトマガジンvol.68 Web掲載記事まとめ
004 レポート 暗号通信のTLS1.2以前に脆弱性
005 NEWS FLASH
008 特集1 Windows 10でWSL 2を使おう/三沢友治
020 特集2 高機能CMS Drupal入門/小薗井康志
034 特別企画 量子コンピュータの基礎を知る/沼田祈史、小林有里
046 Raspberry Piを100%活用しよう/米田聡 コード掲載
049 Hello Nogyo!
050 レッドハットのプロダクト/小杉研太 コード掲載
059 中小企業手作りIT化奮戦記/菅雄一
064 法林浩之のFIGHTING TALKS/法林浩之
066 香川大学SLPからお届け!/山下賢治 コード掲載
072 RPA/桑原滝弥、イケヤシロウ
074 Webアプリの正しい作り方/しょっさん コード掲載
084 円滑コミュニケーションが世界を救う!/濱口誠一
086 バーティカルバーの極意/飯尾淳 コード掲載
092 シェルスクリプトの書き方入門/大津真 コード掲載
100 Techパズル/gori.sh
102 コラム「人生の入り口」/シェル魔人
Raspberry Piを100%活用しよう(Vol.68掲載)
著者:米田 聡
小型コンピュータボード「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() |
レッドハットのプロダクト(Vol.68記載)
著者:小杉 研太
前回(第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"); } } |
香川大学SLPからお届け!(Vol.68掲載)
著者:山下 賢治
初めまして。香川大学 工学研究科 修士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; |
Webアプリケーションの正しい作り方(Vol.68記載)
著者:しょっさん
ソフトウエアを正しく作るために、エンジニアたちはどんなことを知らなければならないでしょうか。実際のコードを使って、より良くしていくためのステップを考えてみましょう。第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 ); |
バーティカルバーの極意(Vol.68掲載)
著者:飯尾 淳
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; } } } |
シェルスクリプトの書き方入門(Vol.68記載)
著者:大津 真
本連載ではシェルスクリプトの書き方をやさしく紹介します。対象とするシェルは、多くの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 |
香川大学SLPからお届け!(Vol.67掲載)
著者:山内 真仁
以前、ゲームエンジン「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; // チェックポイントを通過 } } |
シェルスクリプトマガジンvol.67 Web掲載記事まとめ
004 レポート Microsoft社製BASICのソースコード公開
005 NEWS FLASH
008 特集1 まんがで学ぶ自宅ネットワーク入門/麻生二郎
014 特集2 Cisco Webexが実現するテレワーク環境/粕谷一範
022 特集3 PythonとSeleniumを活用 自動操作とデータ分析/川嶋宏彰 コード掲載
035 Hello Nogyo!
036 特別企画 Microsoft Power Platform(後編)/清水優吾
050 ラズパイセンサーボードで学ぶ 電子回路の制御/米田聡 コード掲載
056 レッドハットのプロダクト/杉本拓 コード掲載
064 法林浩之のFIGHTING TALKS/法林浩之
066 バーティカルバーの極意/飯尾淳 コード掲載
072 tele-/桑原滝弥、イケヤシロウ
074 中小企業手作りIT化奮戦記/菅雄一
078 Webアプリの正しい作り方/しょっさん コード掲載
092 円滑コミュニケーションが世界を救う!/濱口誠一
094 香川大学SLPからお届け!/山内真仁 コード掲載
102 シェルスクリプトの書き方入門/大津真 コード掲載
108 Techパズル/gori.sh
110 コラム「ユニケージの本領発揮」/シェル魔人
特集3 PythonとSeleniumを活用 自動操作とデータ分析(Vol.67記載)
著者:川嶋 宏彰
最近、プログラミング言語「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() |
ラズパイセンサーボードで学ぶ電子回路の制御(Vol.67掲載)
著者:米田 聡
シェルスクリプトマガジンでは、小型コンピュータボード「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) |
レッドハットのプロダクト(Vol.67記載)
著者:杉本 拓
「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 |
カテゴリー コード のアーカイブを表示しています。