連載 作りながら学ぶVue.js入門
記事で紹介したサンプルコードの完全版と、その実行方法を記したMarkdown文書をまとめたZIPファイルは、ここからダウンロードできます。
情報は随時更新致します。
test
記事で紹介したサンプルコードの完全版と、その実行方法を記したMarkdown文書をまとめたZIPファイルは、ここからダウンロードできます。
情報は随時更新致します。

004 レポート GPT-5とgpt-ossが登場
005 レポート Python 3.14が間もなくリリース コード掲載
006 製品レビュー スキャナ「ScanSnap iX2500」
007 NEWS FLASH
008 特集1 自宅で本格ネットワーク環境構築/麻生二郎 コード掲載
016 特集2 耐量子コンピュータ暗号とPKI/林正人
022 特別企画 Red Hat Enerprise Linux 10の新機能と無料利用/麻生二郎
031 インタビュー SUSEソフトウエアソリューションズジャパン 渡辺元氏
032 作りながら学ぶVue.js入門/大津真 コード掲載
038 Raspberry Pi Pico W/WHで始める電子工作/米田聡
042 香川大学SLPからお届け!/井上竜輔 コード掲載
048 Pythonあれこれ/飯尾淳 コード掲載
058 Markdownを活用する/藤原由来 コード掲載
066 知っておきたいシェル関連技術/麻生二郎
074 Techパズル/gori.sh
075 コラム「DX人材育成支援事業の取り組み」/シェル魔人
著者:末安 泰三
Pythonソフトウエア財団が2025年10月1日にリリース予定のPython 3.14では、性能向上の足かせだった大域ロック(GIL)を無効化できる。それにより、マルチスレッド処理を大幅に高速化可能だ。マルチスレッド処理のコードを使って、実際にどの程度高速化するのかを検証する。
シェルスクリプトマガジン Vol.98は以下のリンク先でご購入できます。![]()
![]()
図1 検証に使用した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 time, threading N = 100000000 data = list(range(N)) def work(a, b): return sum(x*x for x in data[a:b]) def bench(threads=4): # シングルスレッド t=time.perf_counter() print("single:", work(0,N), "in", time.perf_counter()-t) # マルチスレッド step=N//threads; res=[0]*threads; th=[] t=time.perf_counter() for i in range(threads): a=i*step; b=N if i==threads-1 else a+step th.append( threading.Thread(target=lambda j=i: \ res.__setitem__(j, work(a,b))) ) th[-1].start() [x.join() for x in th] print(f"{threads} threads:", sum(res), "in", time.perf_counter()-t) bench() |
著者:麻生 二郎
スマートフォンやパソコン、スマート家電をインターネットに接続するだけなら市販のルーターを購入するだけでよいでしょう。サーバーソフト、ネットワーク関連のソフトや機器を使ってみたい,試してみたい場合、普段使うネットワークとは分離した環境を用意しておくと便利です。本特集では、そのようなネットワーク環境を構築します。
シェルスクリプトマガジン Vol.98は以下のリンク先でご購入できます。![]()
![]()
図16 /etc/hostapd/hostapd.confファイルの設定例
|
1 2 3 4 5 6 7 8 9 10 11 12 |
interface=wlan0 driver=nl80211 ssid=shmagtoku1 hw_mode=g channel=6 auth_algs=1 wpa=2 wpa_passphrase=shmag202510t1 wpa_key_mgmt=WPA-PSK rsn_pairwise=CCMP country_code=JP ieee80211d=1 |
図17 /etc/dnsmasq.d/dhcp.confファイルの設定例
|
1 2 3 4 |
interface=wlan0 dhcp-range=192.168.2.2,192.168.2.50,12h domain-needed bogus-priv |
図18 /etc/network/interfacesファイルの設定例
|
1 2 3 4 5 6 7 8 9 10 |
auto lo iface lo inet loopback auto eth0 iface eth0 inet dhcp auto wlan0 iface wlan0 inet static address 192.168.2.1 netmask 255.255.255.0 |
図19 /etc/nftables.d/router.nftファイルの設定例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
table inet filter { chain input { type filter hook input priority 0; policy accept; } chain forward { type filter hook forward priority 0; policy accept; } chain output { type filter hook output priority 0; policy accept; } } table ip nat { chain postrouting { type nat hook postrouting priority 100; oifname "eth0" masquerade } } |
図20 /etc/dnsmasq.d/host.confの設定例
|
1 2 3 4 5 6 |
dhcp-option=option:dns-server,192.168.2.1 domain=home.localnet local=/home.localnet/ dhcp-host=00:11:22:33:44:55,web,192.168.2.100 dhcp-host=00:22:33:44:55:66,file,192.168.2.120 |
著者:大津 真
JavaScriptフレームワークの中でも、学びやすさと柔軟さで人気を集めている「Vue.js」。本連載では、Vue.jsの基礎から実践的な使い方までを、分かりやすく丁寧に解説していきます。一緒にフロントエンド開発の楽しさを体験してみましょう。第3回では、HTMLやCSS、JavaScriptのコードをまとめた「SFCファイル」を使用したVueアプリケーションの作成について説明します。
シェルスクリプトマガジン Vol.98は以下のリンク先でご購入できます。![]()
![]()
図6 「index.html」ファイルのbody要素
|
1 2 3 4 |
<body> <div id="app"></div> <script type="module" src="/src/main.js"></script> </body> |
図7 「src/main.js」ファイルの内容
|
1 2 3 4 5 |
import { createApp } from 'vue' import './style.css' import App from './App.vue' createApp(App).mount('#app') |
図8 デフォルトの「src/App.vue」ファイルの内容(抜粋)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<script setup> import HelloWorld from './components/HelloWorld.vue' </script> <template> <div> <a href="https://vite.dev" target="_blank"> <img src="/vite.svg" class="logo" alt="Vite logo" /> </a> <a href="https://vuejs.org/" target="_blank"> <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" /> </a> </div> <HelloWorld msg="Vite + Vue" /> </template> <style scoped> .logo { height: 6em; padding: 1.5em; will-change: filter; transition: filter 300ms; } (略) </style> |
図9 CDN版のVueアプリケーション「counter1.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 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>Vueサンプル</title> <style> h1 { font-size: 3em; background-color: #f0f0f0; color: green; } </style></head> <body> <script type="importmap"> { "imports": { "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js" } } </script> <script type="module"> import { createApp, ref } from 'vue' createApp({ setup() { const counter = ref(0) function increment() { counter.value++ } return { counter, increment } } }).mount('#app') </script> <div id="app"> <h1>カウント: {{ counter }}</h1> <button v-on:click="increment">+</button> </div> </body> </html> |
図11 図9のVueアプリケーションの処理をApp.vueファイルに記述した例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<script setup> import { ref } from 'vue' const counter = ref(0) function increment() { counter.value++ } </script> <template> <h1>カウント: {{ counter }}</h1> <button v-on:click="increment">+</button> </template> <style scoped> h1 { font-size: 3em; background-color: #f0f0f0; color: green; } </style> |
図A 「vite.config.js」ファイルの変更例
|
1 2 3 4 5 6 7 8 |
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' // https://vite.dev/config/ export default defineConfig({ plugins: [vue()], base: '/myapp/' }) |
著者:井上竜輔
最終回では、筆者が作成したプログラミング学習アプリの概要を紹介します。このアプリは、AIなどの外部APIを活用して、オンラインで動的に問題を生成、実行、評価できるものです。安全性を考慮して、ユーザーが提出したプログラムは「Judge0 CE」というオンラインコード実行システムで、実行、評価します。
シェルスクリプトマガジン Vol.98は以下のリンク先でご購入できます。![]()
![]()
図7 「components/CodeEditor.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 |
import React from 'react'; import Editor from '@monaco-editor/react'; import { Select, MenuItem, FormControl, InputLabel, Box } from '@mui/material'; (略) const CodeEditor: React.FC<Props> = ({ language, setLanguage, code, setCode }) => { return ( <Box sx={{ mt: 2 }}> <FormControl sx={{ mb: 1, minWidth: 120 }}> <InputLabel>Language</InputLabel> <Select value={language} onChange={(e) => setLanguage(e.target.value)} label="Language" > <MenuItem value="python">Python</MenuItem> <MenuItem value="cpp">C++</MenuItem> <MenuItem value="javascript">JavaScript</MenuItem> </Select> </FormControl> <Editor height="40vh" language={language} value={code} onChange={(value) => setCode(value || '')} theme="vs-dark" /> </Box> ); }; |
図8 handleSubmitCode関数の定義コード
|
1 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 |
const handleSubmitCode = async () => { if (!problem) return; setIsSubmitting(true); let finalVerdict = "AC (Accepted)"; // サンプルケースごとにコードを実行、検証 for (let i = 0; i < problem.samples.length; i++) { const sample = problem.samples[i]; try { // APIを呼び出してコードを実行 const judgeResult = await submitCode(language, code, sample.input); // 結果を検証 if (judgeResult.status.id === 3) { const userOutput = (judgeResult.stdout || '').trim(); const expectedOutput = sample.output.trim(); if (userOutput !== expectedOutput) { finalVerdict = "WA (Wrong Answer)"; } } else { finalVerdict = judgeResult.status.description; } } catch (error) { } // 最初の失敗で処理を中断 if (finalVerdict !== "AC (Accepted)") { break; } // APIのレート制限を避けるための待ち時間 if (i < problem.samples.length - 1) { await new Promise(resolve => setTimeout(resolve, 1000)); } } setResult(finalVerdict); setIsSubmitting(false); }; |
図9 フロントエンドからのリクエストを受け取るコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import type { NextApiRequest, NextApiResponse } from 'next'; import axios from 'axios'; // Judge0 CEの言語IDをマッピング const LANGUAGE_MAP: { [key: string]: number } = { python: 71, // Python 3.8.1 cpp: 54, // C++ (GCC 9.2.0) javascript: 63, // JavaScript (Node.js 12.14.0) }; export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method !== 'POST') { return res.status(405).json({ error: 'メソッドはポストではありません。' }); } const { language, code, stdin } = req.body; if (!language || !code || !stdin) { return res.status(400).json({ error: '要素が足りません。' }); } const languageId = LANGUAGE_MAP[language]; if (!languageId) { return res.status(400).json({ error: `対応していない言語です。 ${language}` }); } (略) } |
図10 Judge0 CEの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 |
const judge0ApiKey = process.env.NEXT_PUBLIC_JUDGE0_API_KEY; const options = { method: 'POST', url: 'https://judge0-ce.p.rapidapi.com/submissions', params: { base64_encoded: 'false', wait: 'true', // 実行完了まで待機する }, headers: { 'x-rapidapi-host': 'judge0-ce.p.rapidapi.com', 'x-rapidapi-key': judge0ApiKey, // 環境変数から読み込んだAPIキー 'content-type': 'application/json', }, data: { language_id: languageId, source_code: code, stdin: stdin, }, }; try { const submissionResponse = await axios.request(options); // Judge0 CEからの結果をフロントエンドに返す res.status(200).json(submissionResponse.data); } catch (error) {/* エラーハンドリング*/} |
図11 Geminiに送信するプロンプトの例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
あなたは優秀な競技プログラミングの問題作成者です。 以下の制約に従って、新しいプログラミング問題を1つ作成してください。 # 制約 - 難易度: AtCoder ProblemsのDifficulty基準で「B」レベル - トピック: 配列の操作、または文字列の探索 - 入力: 標準入力から受け取ること - 出力: 標準出力へ書き出すこと - 形式: 以下のJSONフォーマットに従うこと # JSONフォーマット { "title": "問題タイトル", "statement": "問題文。背景やストーリーを簡潔に記述。", "constraints": [ "Nは1以上100以下の整数", "Sは英小文字からなる長さNの文字列" ], "input_format": "入力形式の説明", "output_format": "出力形式の説明", "samples": [ { "input": "入力例1", "output": "出力例1" }, { "input": "入力例2", "output": "出力例2" }, { "input": "入力例3", "output": "出力例3" } ], "solution_code": "Pythonによる解答例のコード" } |
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第28回では、インタラクティブなグラフを手軽に作成できる可視化ライブラリ「Plotly」を使って、さまざまなグラフを作成します。
シェルスクリプトマガジン Vol.98は以下のリンク先でご購入できます。![]()
![]()
図2 散布図を描くコード
|
1 2 3 4 |
fig = px.scatter(df, x='sepal_width', y='sepal_length', color='species', size='petal_length') fig.update_layout(width=800, height=600) fig.show() |
図5 花弁の長さのデータを表示できるようにしたコード
|
1 2 3 4 5 |
fig = px.scatter(df, x='sepal_width', y='sepal_length', color='species', size='petal_length', hover_data=['petal_width']) fig.update_layout(width=800, height=600) fig.show() |
図7 がくの幅の分布をヒストグラムで表示するコード
|
1 2 3 |
fig = px.histogram(df, x='sepal_width', color='species') fig.update_layout(width=800, height=600) fig.show() |
図11 カラースキームを変更するコードの例
|
1 2 3 4 |
# 離散的なデータ向けのカラースキーム設定 px.defaults.color_discrete_sequence = px.colors.qualitative.T10 # 連続的なデータ向けのカラースキーム設定 px.defaults.color_continuous_scale = px.colors.sequential.GnBu |
図13 四つのヒストグラムをまとめて表示するコードの例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
from plotly.subplots import make_subplots fig = make_subplots(rows=2, cols=2, shared_yaxes=True) figs = [px.histogram(df, x='sepal_width', color='species', barmode='overlay'), px.histogram(df, x='sepal_length', color='species', barmode='overlay'), px.histogram(df, x='petal_width', color='species', barmode='overlay'), px.histogram(df, x='petal_length', color='species', barmode='overlay')] i = 0 for f in figs: for g in f.data: fig.add_trace(g, row=(i//2)+1, col=(i%2)+1) i += 1 fig.update_layout(height=600, width=800, title_text='4種類のヒストグラム', barmode='overlay', xaxis_title=‘sepal_width', xaxis2_title='sepal_length', xaxis3_title='petal_width', xaxis4_title='petal_length') fig.show() |
図15 図13のコードの改良版
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
from plotly import graph_objects as go from plotly.subplots import make_subplots fig = make_subplots(rows=2, cols=2, shared_yaxes=True) i = 0 for types in ['sepal_width', 'sepal_length', 'petal_width', 'petal_length']: for species, g in df.groupby('species'): fig.add_trace(go.Histogram(x=g[types], name=f'{species} ({types})', opacity=0.7), row=(i//2)+1, col=(i%2)+1) i += 1 fig.update_layout(height=600, width=800, title_text='4種類のヒストグラム', barmode='overlay', xaxis_title=‘sepal_width', xaxis2_title='sepal_length', xaxis3_title='petal_width', xaxis4_title='petal_length') fig.show() |
図17 tipsデータセットを読み込んで散布図を描くコード
|
1 2 3 4 5 6 |
df = px.data.tips() fig = px.scatter(df, x='total_bill', y='tip', color='day', size='size', hover_data=['time', 'sex', 'smoker']) fig.update_layout(width=800, height=600) fig.show() |
図19 曜日ごとのチップの額を箱ひげ図で描くコード
|
1 2 3 4 5 |
fig = px.box(df, x='day', y='tip', color='day', category_orders={'day': ['Thur', 'Fri', 'Sat', 'Sun']}) fig.update_layout(width=800, height=600) fig.show() |
図21 二つの箱ひげ図を並べて描くコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
fig = make_subplots(rows=1, cols=2) figs = [px.box(df, x='day', y='tip', color='day', category_orders={'day': ['Thur', 'Fri', 'Sat', 'Sun']}), px.box(df, x='day', y='total_bill', color='day', category_orders={'day': ['Thur', 'Fri', 'Sat', 'Sun']})] i = 0 for f in figs: for g in f.data: fig.add_trace(g, row=1, col=(i % 2)+1) i += 1 fig.update_layout(width=800, height=600, title_text='tipsとtotal_billの比較', xaxis_title='tips', xaxis2_title='total_bill') |
図23 バイオリン図を描くコード
|
1 2 3 4 5 |
fig = px.violin(df, x='day', y='tip', color='day', category_orders={'day': ['Thur', 'Fri', 'Sat', 'Sun']}) fig.update_layout(width=800, height=600) fig.show() |
図24 ヒートマップを描くコード
|
1 2 3 4 5 6 7 8 |
fig = px.density_heatmap(df, x='day', y='time', z='tip', histfunc='avg', category_orders={'day': ['Thur', 'Fri', 'Sat', 'Sun'], 'time': ['Dinner', 'Lunch']}) fig.update_layout(width=800, height=400, title_text='曜日と時間帯のヒートマップ(tip)') fig.show() |
著者:藤原 由来
本連載では文書の装飾・構造付けを手軽に行える記法であるMarkdownを用いて、さまざまな文書や成果物を作成する方法を紹介します。前回に引き続き、文書変換ツール「Pandoc」の文書作成方法を紹介します。今回はPandocを用いて、EPUB形式電子書籍を作成する方法を紹介します。
シェルスクリプトマガジン Vol.98は以下のリンク先でご購入できます。![]()
![]()
図1 Markdown形式にした「銀河鉄道の夜」の冒頭部分
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
--- title: 銀河鉄道の夜 author: 宮沢賢治 --- # 一、午后《ごご》の授業 「ではみなさんは、そういうふうに川だと云《い》われたり、乳の流れたあとだと云われたりしていたこのぼんやりと白いものがほんとうは何かご承知ですか。」先生は、黒板に吊《つる》した大きな黒い星座の図の、上から下へ白くけぶった銀河帯のようなところを指《さ》しながら、みんなに問《とい》をかけました。 カムパネルラが手をあげました。それから四五人手をあげました。ジョバンニも手をあげようとして、急いでそのままやめました。たしかにあれがみんな星だと、いつか雑誌で読んだのでしたが、このごろはジョバンニはまるで毎日教室でもねむく、本を読むひまも読む本もないので、なんだかどんなこともよくわからないという気持ちがするのでした。 ところが先生は早くもそれを見附《みつ》けたのでした。 「ジョバンニさん。あなたはわかっているのでしょう。」 ジョバンニは勢《いきおい》よく立ちあがりましたが、立って見るともうはっきりとそれを答えることができないのでした。ザネリが前の席からふりかえって、ジョバンニを見てくすっとわらいました。ジョバンニはもうどぎまぎしてまっ赤になってしまいました。先生がまた云いました。 (略) |
図12 本文中の画像を挿入するために追記
|
1 2 3 4 5 6 7 8 9 |
(略) そして教室中はしばらく机《つくえ》の蓋《ふた》をあけたりしめたり本を重ねたりする音がいっぱいでしたがまもなくみんなはきちんと立って礼をすると教室を出ました。 {height=90%} # 二、活版所 ジョバンニが学校の門を出るとき、同じ組の七八人は家へ帰らずカムパネルラをまん中にして校庭の隅《すみ》の桜《さくら》の木のところに集まっていました。それはこんやの星祭に青いあかりをこしらえて川へ流す烏瓜《からすうり》を取りに行く相談らしかったのです。 (略) |
※ 「[」の箇所は「[」です。
図14 オプションをまとめたデフォルトファイル
|
1 2 3 4 5 6 |
input-files: - gingatetsudono_yoru_image.md from: markdown+hard_line_breaks output-file: gingatetsudono_yoru_06.epub css: style.css epub-cover-image: img/Copilot_Generated_Cover.png |
いつもUSP研究所の通信販売をご利用いただき、誠にありがとうございます。
USP研究所では、環境保護および業務効率化の取り組みの一環として、2025年9月1日(月曜日)より、領収書を電子化いたします。
ご注文・お支払い完了後に、領収書をご希望された方へメールにてPDF形式の領収書をお送りいたします。なお、紙の領収書をご希望の場合は、印刷・郵送にかかる事務手数料が必要です。以下のご対応をお願いいたします。
紙の領収書が必要な場合は、商品ご購入時に「URX0001 紙の領収書発行手数料」(税込み220円)を必ずカートに追加してください。こちらのご購入が確認でき次第、紙の領収書を郵送いたします。
電子領収書は迅速にお届けでき、保管も容易です。理解とご協力のほどよろしくお願い申し上げます。
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第27回では、異文化間交流教育プロジェクトで得られたオンライン交流データを分析する、筆者の研究例を紹介します。
シェルスクリプトマガジン Vol.97は以下のリンク先でご購入できます。![]()
![]()
図2 必要なライブラリをインポートするコード
|
1 2 3 4 5 6 7 8 |
import pandas as pd import numpy as np import matplotlib.pyplot as plt import japanize_matplotlib import re, os, math from graphviz import Graph from IPython.display import Image, display, display_png from sklearn.linear_model import LinearRegression |
図4 mk_relations()関数を定義するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
def mk_relations(df): # 関係性を抽出する prev = [] relations = {} f = df.columns for i, row in df.iterrows(): # 発言文字数が「0」ではない発言者を抽出 d = dict(zip(f, list(row))) speakers = list(filter(lambda k: d[k] != 0, d.keys())) # 前行のスピーカーと今の行のスピーカーの関係を抽出 # どちらかもしくは両方が空行のときはスキップ if len(prev)*len(speakers) > 0: for p_speaker in prev: for n_speaker in speakers: r = tuple(sorted([p_speaker, n_speaker])) if r not in relations.keys(): relations[r] = 1 else: relations[r] += 1 prev = speakers return relations |
図5 ファイルからターンテイキングの状況を抽出するコード
|
1 2 3 4 |
filename = 'data/file00.xlsx' df = pd.read_excel(filename).drop(['Unnamed: 0'], axis=1) d = mk_relations(df) d |
図7 conv_dict()関数を定義するコード
|
1 2 3 |
def conv_dict(d): v = d.values() return dict(zip(d.keys(), map(lambda x: x/max(v), v))) |
図9 count_talk()関数を定義するコード
|
1 2 3 4 5 |
def count_talk(df): retvar = {} for (name, col) in df.T.iterrows(): retvar[name] = len(list(filter(lambda x: x > 0, col))) return retvar |
図11 render_graph()関数を定義するコード
|
1 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 |
def render_graph(filename): print(f'[source] {filename}') (basename, extention) = re.match(r'(.*)\.(.*)', filename).groups() pathname = 'data/'+filename outputfile = f'graphs/{basename}' # データフレームの準備 df = pd.read_excel(pathname).drop(['Unnamed: 0'], axis=1) names = df.columns # 発言の関係性と発言回数の抽出 relations = conv_dict(mk_relations(df)) talk_counts = count_talk(df) talks = conv_dict(talk_counts) # グラフ描画に関する定数など ARROW_WIDTH = 3 colors = ['white', 'azure', 'lavender', 'slateblue', 'navy'] textcolors = ['black', 'black', 'black', 'white', 'white'] # dot属性の準備 dot = Graph(format='png') dot.attr('node', shape='ellipse') dot.attr('node', fontname='Arial') # ノードの追加 for n in talks.keys(): idx = int(talks[n]*(len(colors)-1)) dot.node(n, f'{n} ({talks[n]:4.2f})', style='filled', fillcolor=colors[idx], fontcolor=textcolors[idx]) # エッジの追加 for e in relations.keys(): (src, dst) = e dot.edge(src, dst, penwidth=f'{relations[e]*ARROW_WIDTH:5.3f}') # サマリー表の作成 df_summary = pd.DataFrame({'ID': talk_counts.keys(), '発言回数': talk_counts.values()}) # レンダリング display(df_summary) dot.render(outputfile) display_png(Image(f’graphs/{basename}.png')) |
図15 calc_num_cv()関数を定義するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
def calc_num_cv(filename): print(f'[source] {filename}') (basename, extention) = re.match(r'(.*)\.(.*)', filename).groups() pathname = 'data/'+filename # データフレームの準備 df = pd.read_excel(pathname).drop(['Unnamed: 0'], axis=1) names = df.columns num = len(names) # 発言回数の抽出 talk_counts = count_talk(df) # サマリー表の作成 df_summary = pd.DataFrame({'ID': talk_counts.keys(), '発話回数': talk_counts.values()}) # 平均・標準偏差・変動係数の計算 mu = sum(df_summary['発話回数'])/num # 平均値 sd = math.sqrt(sum((df_summary['発話回数'] - mu) * (df_summary['発話回数'] - mu))/(num-1)) # 不偏標準偏差 print(f'参加者数 = {num}') print(f'平均 = {mu:4.2f}') print(f'不偏標準偏差 = {sd:4.2f}') print(f'変動係数 = {(sd/mu):6.4f}') return num, sd/mu |
図16 すべてのデータから参加者数と変動係数を計算するコード
|
1 2 3 4 5 6 7 |
files = sorted(os.listdir('data')) table = [] Xlabel = '参加者数' Ylabel = '変動係数' for filename in files: num, cv = calc_num_cv(filename) table.append({ 'Name': filename, Xlabel: num, Ylabel: cv }) |

004 レポート WSL2版Red Hat Enterprise Linux公開
005 レポート KubeCon + CloudNativeCon Japan開催
006 製品レビュー PC「ThinkPad X1 Carbon Gen 13」
007 NEWS FLASH
008 特集1 Red Hat OpenShift Virtualization/宇都宮卓也、田中司恩
019 インタビュー デジサート・ジャパン/二宮要氏
020 特集2 OCIクラウドネイティブアプリ開発/曽川宥輝
034 作りながら学ぶVue.js入門/大津真 コード掲載
041 Markdownを活用する/藤原由来 コード掲載
050 Raspberry Pi Pico W/WHで始める電子工作/米田聡 コード掲載
056 Pythonあれこれ/飯尾淳 コード掲載
064 中小企業手作りIT化奮戦記/菅雄一
070 知っておきたいシェル関連技術/麻生二郎
074 Techパズル/gori.sh
075 コラム「意識教育」/シェル魔人
著者:大津 真
JavaScriptフレームワークの中でも、学びやすさと柔軟さで人気を集めている「Vue.js」。本連載では、Vue.jsの基礎から実践的な使い方までを、分かりやすく丁寧に解説していきます。一緒にフロントエンド開発の楽しさを体験してみましょう。第2回では、テンプレート構文において、各要素に特別な動作を与えるための機能であるディレクティブの基礎について説明します。
シェルスクリプトマガジン Vol.97は以下のリンク先でご購入できます。![]()
![]()
図1 オブジェクトリテラルの例
|
1 2 3 4 5 6 7 8 |
{ name: name, age: age, scores: { math: [40, 50], eng: [70, 60] }, sayHello: function() { console.log('こんにちは'); } } |
図2 シンプルなVueアプリ「sample1.html」の主要部分のコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<script type="module"> import { createApp, ref } from 'vue' createApp({ setup() { const msg = ref('ハローVue') const person = ref({name:'大津真', age: 35}) return { msg, person } } }).mount('#app') </script> <div id="app"> <h1>{{ msg }}</h1> <p>{{ person }}</p> </div> |
図3 ボタンクリックで値を増減させるVueアプリ「counter1.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 |
<script type="module"> import { createApp, ref } from 'vue' createApp({ setup() { // counterをリアクティブな変数として定義 const counter = ref(0) // counterを「1」増やす function increment() { counter.value++ } // counterを「1」減らす function decrement() { counter.value-- } return { counter, increment, decrement } } }).mount('#app') </script> <div id="app"> <h1>カウント: {{ counter }}</h1> <button v-on:click="increment">+</button> <button v-on:click="decrement">-</button> </div> |
図4 図3のアプリをインラインイベントハンドラを用いて書き換えた「counter2.html」のコード(抜粋)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<script type="module"> import { createApp, ref } from 'vue' createApp({ setup() { // counterをリアクティブな変数として定義 const counter = ref(0) return { counter, } } }).mount('#app') </script> <div id="app"> <h1>カウント: {{ counter }}</h1> <button @click="counter += 1">+</button> <button @click="counter -= 1">-</button> </div> |
図5 図4のアプリに「3倍にする」ボタンを追加した「counter3.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 |
<script type="module"> import { createApp, ref } from 'vue' createApp({ setup() { // counterをリアクティブな変数として定義 const counter = ref(0) const multiplier = ref(3) // counterをn倍にする function multiply(n) { counter.value *= n } return { counter, multiply, multiplier } } }).mount('#app') </script> <div id="app"> <h1>カウント: {{ counter }}</h1> <button @click="counter += 1">+</button> <button @click="counter -= 1">-</button> <button @click="multiply(multiplier)"> {{ multiplier }}倍にする</button> </div> |
図6 マスタッシュ構文とv-htmlの違いを示す「v-html1.html」のコード(抜粋)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<script type="module"> import { createApp } from 'vue' createApp({ setup() { const msg1 = 'msg1:\ <span style="font-size:3em;color: red;">\ ハローVue</span>' return { msg1 } } }).mount('#app') </script> <div id="app"> <p>{{ msg1 }}</p> <p v-html="msg1"></p> </div> |
図9 おみくじアプリ「omikuji1.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 |
<script type="module"> import { createApp, ref } from 'vue' createApp({ setup() { // おみくじの画像ファイル名を配列で定義 const kujis = ["daikichi.png", "chukichi.png", "syokichi.png", "kyo.png"] // 初期画像を設定 const kuji = ref('default.png') // おみくじを引く関数 function changeKuji() { const randomIndex = Math.floor(Math.random() * kujis.length) kuji.value = kujis[randomIndex] } return { kuji, changeKuji } } }).mount('#app') </script> <div id="app"> <h1>おみくじ</h1> <div> <button @click="changeKuji">おみくじを引く</button> </div> <img :src="'figs/' + kuji" alt="おみくじ" @click="changeKuji"> </div> |
図11 「style1.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 |
<script type="module"> import { createApp, ref } from 'vue' createApp({ setup() { const fcolor = 'red' const size = '4em' const align = ref('left') function setAlign(value) { align.value = value } return { fcolor, align, size, setAlign } } }).mount('#app') </script> <div id="app"> <div> <button @click="setAlign('left')">左寄せ</button> <button @click="setAlign('center')">中央寄せ</button> <button @click="setAlign('right')">右寄せ</button> <p :style="{color:fcolor, textAlign:align, fontSize:size}">Vue.js</p> </div> </div> |
図12 「style2.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 |
<script type="module"> import { createApp, ref } from 'vue' createApp({ setup() { const styleAll = ref({ color: 'red' fontSize: '4em' textAlign: 'left' }) function setAlign(value) { styleALL.value.textAlign = value } return { styleAll, setAlign } } }).mount('#app') </script> <div id="app"> <div> <button @click="setAlign('left')">左寄せ</button> <button @click="setAlign('center')">中央寄せ</button> <button @click="setAlign('right')">右寄せ</button> <p :style="styleAll">Vue.js</p> ←③ </div> </div> |
図13 「style3.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 |
<script type="module"> import { createApp, ref } from 'vue' createApp({ setup() { const styleAll = ref({ color: 'red' fontSize: '4em' textAlign: 'left' }) const styleAdd = ref({ color: 'blue', backgroundColor: 'yellow', }) (略) return { styleAll, styleAdd, setAlign } } }).mount('#app') </script> <div id="app"> <div> (略) <p :style="[styleAll, styleAdd]">Vue.js</p> </div> </div> |
図17 「class1.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 |
<style> .class1 { text-align: center; color: blue; } .class2 { font-size: 4em; color: red; } </style> (略) <script type="module"> import { createApp, ref } from 'vue' createApp({ setup() { const enableClass2 = ref(false) return { enableClass2 } } }).mount('#app') </script> <div id="app"> <button @click="enableClass2=!enableClass2">class2を有効/無効</button> <p :class="{class1:true, class2:enableClass2}">Vue.jsの世界</p> </div> |
筆者:藤原 由来
本連載では文書の装飾・構造付けを手軽に行える記法であるMarkdownを用いて、さまざまな文書や成果物を作成する方法を紹介します。前回に引き続き、文書変換ツール「Pandoc」を扱います。今回は、Pandocの概要と基本的な使い方について説明します。
シェルスクリプトマガジン Vol.97は以下のリンク先でご購入できます。![]()
![]()
図3 Markdownテキストファイル(sample.md)の内容
|
1 2 3 4 5 6 7 8 9 10 11 |
# カレーの作り方 おいしいカレーを作りましょう。 ## 材料 - 牛肉 - 玉ねぎ - にんじん - カレールー - 水 |
図9 数式を用いたMarkdown記法
|
1 2 3 4 5 6 7 |
インライン数式 $f(x) = ax^2 + bx + c$ です。 ## ブロック数式 $$ e^{i \theta} = \cos \theta + i \sin \theta $$ |
筆者:米田 聡
本連載では、人気のマイコンボード「Raspberry Pi Pico/Pico 2」シリーズを活用していきます。同ボードは、無線LANやBluetoothの通信機能を搭載するモデルもあり、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。今回は、前回Raspberry Pi Pico 2に取り付けた小型LCDに画像を表示するプログラムを紹介します。
シェルスクリプトマガジン Vol.97は以下のリンク先でご購入できます。![]()
![]()
図1 RGB565の非圧縮ビットマップ画像に変換するPythonプログラム(rgb565.py)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
#!/usr/bin/python3 import sys import argparse from PIL import Image def rgb565_convert(image_path, output_path ,order='little'): """ 画像をRGB565形式に変換し、バイナリファイルに保存します。 Args: image_path: 入力画像のパス output_path: 出力バイナリファイルのパス """ try: img = Image.open(image_path).convert("RGB") # RGBに変換 except FileNotFoundError: print(f"Error: 画像ファイル '{image_path}' が見つかりません。") return except Exception as e: print(f"Error: 画像を開く際にエラーが発生しました: {e}") return width, height = img.size with open(output_path, "wb") as f: for y in range(height): for x in range(width): r, g, b = img.getpixel((x, y)) # RGB565に変換 r5 = r >> 3 # 上位5ビット g6 = g >> 2 # 上位6ビット b5 = b >> 3 # 上位5ビット rgb565 = (r5 << 11) | (g6 << 5) | b5 # バイナリ書き込み (リトルエンディアン) f.write(rgb565.to_bytes(2, byteorder=order)) print(f"画像 '{image_path}' をRGB565形式に変換し、'{output_path}' に保存しました。") if __name__=="__main__": parser = argparse.ArgumentParser() parser.add_argument("files",type=str,nargs="*") parser.add_argument("--order", type=str, choices=['little', 'big'], default="little") args = parser.parse_args() files = args.files order = args.order if len(files) != 2: print("Usage: rgb565.py infile outfile [--order little | big ]") else: rgb565_convert(files[0], files[1], order) |
図2 sample.binをLCDに表示するC++プログラム(main.cpp)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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 |
#include <Arduino.h> #include <Adafruit_GFX.h> #include <Adafruit_ST7735.h> #include <Adafruit_ST7789.h> #include <SPI.h> // 以下がPico SDKのヘッダーファイル #include "hardware/dma.h" #include "hardware/spi.h" #include "pico/stdlib.h" #define TFT_CS 17 #define TFT_RST 21 #define TFT_DC 20 // サンプル画像を埋め込む __asm(\ ".section \".rodata\" \n"\ ".balign 4\n"\ ".global _sample_picture\n"\ ".global _picture_size\n"\ "_sample_picture:\n"\ ".incbin \"include/sample.bin\"\n"\ ".set _picture_size, . - _sample_picture\n"\ ".section \".text\"\n"\ ); extern const uint16_t _sample_picture[]; extern uint32_t _picture_size[]; // Adafruit ST77xxを初期化生成 Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); void setup() { Serial1.begin(115200); // USBシリアルを使用する場合はSerialに変更、以下同じ // SPIクロック100MHz tft.setSPISpeed(100*000*000); // 解像度240×240、SPIモード3 tft.init(240, 240, SPI_MODE3); // 黒で消去 tft.fillScreen(ST77XX_BLACK); // 空いているDMAチャンネルを得る int dma_ch = dma_claim_unused_channel(true); // DMA設定を取得 dma_channel_config c = dma_channel_get_default_config(dma_ch); // データ転送単位:DMA_SIZE_8=8iビット channel_config_set_transfer_data_size(&c, DMA_SIZE_8); // 読み出し側アドレスのインクリメントを有効 channel_config_set_read_increment(&c, true); // 書き込み側アドレスのインクリメントは行わない channel_config_set_write_increment(&c, false); // spi0送信FIFOのデータ要求を使用する channel_config_set_dreq(&c, DREQ_SPI0_TX); // DMAにパラメータを設定する dma_channel_configure( dma_ch, // DMAチャンネル &c, // 設定 &spi_get_hw(spi0)->dr, // 書き込み先:SPI FIFOデータレジスタ(SSPDR) _sample_picture, // 読み出し元アドレス (uint)_picture_size, // 転送するデータカウント数 false // 今すぐ開始するか(しない) ); // LCDディスプレイメモリーの書き込み矩形を設定 tft.setAddrWindow(0, 0, tft.width(), tft.height()); ulong starttime = micros(); tft.startWrite(); // 書き込み開始 dma_channel_start(dma_ch); // DMA開始 dma_channel_wait_for_finish_blocking(dma_ch); // DMA終了待ち tft.endWrite(); // 書き込み終了 ulong elapsed = micros() - starttime; // 描画に費やした時間を表示 Serial1.printf("elapsed time = %lu\n", elapsed); } void loop() { } |
図4 埋め込んだアセンブリ言語のリスト
|
1 2 3 4 5 6 7 8 9 10 11 |
.section ".rodata" .balign 4 .global _sample_picture .global _picture_size _sample_picture: .incbin "include/sample.bin" .set _picture_size, . - _sample_picture .section ".text |
著者:大津 真
JavaScriptフレームワークの中でも、学びやすさと柔軟さで人気を集めている「Vue.js」。本連載では、Vue.jsの基礎から実践的な使い方までを、分かりやすく丁寧に解説していきます。一緒にフロントエンド開発の楽しさを体験してみましょう。第1回では、Vue.jsの概要と、簡単なアプリの作成方法を紹介します。
シェルスクリプトマガジン Vol.96は以下のリンク先でご購入できます。![]()
![]()
図3 Options APIを利用したコードの例(抜粋)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<script type="module"> import { createApp } from 'vue' createApp({ data() { return { count: 0 } }, methods: { increment() { this.count++ } } }).mount('#app') </script> <div id="app"> <button @click="increment">Count is: {{ count }}</button> </div> |
図4 Composition APIを利用したコードの例(抜粋)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<script type="module"> import { createApp, ref } from 'vue' createApp({ setup() { const count = ref(0) function increment() { count.value++ } return { count, increment } } }).mount('#app') </script> <div id="app"> <button @click="increment">Count is: {{ count }}</button> </div> |
図5 SFCファイルの記述例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<script setup> import { ref } from 'vue' const count = ref(0) </script> <template> <button @click="count++">Count is: {{ count }}</button> </template> <style scoped> button { font-weight: bold; } </style> |
図6 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 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>Vueサンプル</title> </head> <body> <script type="importmap"> "imports": { "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js" } } </script> <script type="module"> import { createApp, ref } from 'vue' createApp({ setup() { // 処理はここに記述 } }).mount('#app') </script> <div id="app"> </div> </body> </html> |
図8 「sample1.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 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>Vueサンプル</title> </head> <body> <script type="importmap"> { "imports": { "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js" } } </script> <script type="module"> import { createApp, ref } from 'vue' createApp({ setup() { const msg = ref('ハローVue') const person = ref({name:'大津真', age: 35}) return{ msg, person } } }).mount('#app') </script> <div id="app"> <h1>{{ msg }}</h1> <p>{{ person }}</p> </div> </body> </html> |
図10 setup()関数の定義コードをこのように変更
|
1 2 3 4 5 6 7 8 9 10 11 |
setup() { const msg = ref('ハローVue') const person = ref({name:'大津真', age: 35}) // 3秒後にリアクティブな変数msgを変更する setTimeout(() => { msg.value = "ようこそVueの世界へ" }, 3000) return{ msg, person } } |
図12 div要素の記述をこのように変更
|
1 2 3 4 |
<div id="app"> <h1>{{ "反転→ " + msg.split("").reverse().join("")}}</h1> <p>{{ `名前: ${person.name}, 年齢: ${person.age}才` }}</p> </div> |

004 レポート 米OpenAI社が「Codex CLI」を公開
005 レポート VSCodeの拡張機能「Docker DX」登場
006 製品レビュー USB電源「Anker Charger(140W,4Ports)」
007 NEWS FLASH
008 特集1 ゼロトラストセキュリティ入門/石井英男、小寺勝仁、荻野隼
019 インタビュー BrainPad AAA 辻陽行氏
020 特集2 開発ツールのプリザンター/内田太志
032 作りながら学ぶVue.js入門/大津真 コード掲載
038 Markdownを活用する/藤原由来
044 Raspberry Pi Pico W/WHで始める電子工作/米田聡
048 Pythonあれこれ/飯尾淳 コード掲載
054 香川大学SLPからお届け!/織田悠暉 コード掲載
060 中小企業手作りIT化奮戦記/菅雄一
066 知っておきたいシェル関連技術/麻生二郎
069 IT関連の新刊
072 Techパズル/gori.sh
073 コラム「毎週開催の金曜勉強会」/シェル魔人
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第26回では、データを視覚化(ビジュアライゼーション)してデータの傾向を探るためのツール「PyGWalker」の利用に挑戦します。
シェルスクリプトマガジン Vol.96は以下のリンク先でご購入できます。![]()
![]()
図5 コードセルを追加して実行するコード
|
1 2 3 4 |
df['Weekend'] = df['Day of the week'].isin([0, 6]).astype(int) df['Holiday'] = (df['holiday'] == 'yes').astype(int) df['Holiday or Weekend'] = (df['Weekend'] | df['Holiday']).astype(str) pyg.walk(df) |
筆者:織田 悠暉
今回は、ゲームエンジン「Unity」で2Dキャラクタを動かす方法を紹介します。具体的には、プレーヤのキャラクタと、地形の作成、左右の移動と、ジャンプ動作の実装、各動作に合わせてキャラクタの画像を変更する方法について解説します。とても手軽に実装できるので、皆さんもぜひチャレンジしてください。
図9 「Move」ファイル(「Move.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 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.InputSystem; public class Move : MonoBehaviour { Rigidbody2D rigid2D; void Start() { rigid2D = GetComponent<Rigidbody2D>(); } void Update() { Vector2 right = new Vector2 (30f, 0); // 右向きの移動速度 Vector2 left = new Vector2 (-30f, 0); // 左向きの移動速度 // 右向きの移動 if (Keyboard.current.dKey.isPressed && (rigid2D.linearVelocity.magnitude < 7f)) { rigid2D.AddForce(right); } // 左向きの移動 if (Keyboard.current.aKey.isPressed && (rigid2D.linearVelocity.magnitude < 7f)) { rigid2D.AddForce(left); } } } |
図10 「Move」ファイル(「Move.cs」ファイル)に挿入するコード
|
1 2 3 4 5 6 7 8 |
void OnTriggerStay2D(Collider2D collision) { if (Keyboard.current.dKey.wasReleasedThisFrame || Keyboard.current.aKey.wasReleasedThisFrame) { rigid2D.linearVelocity = Vector2.zero; } } |
図11 「Jump」ファイル(「Jump.cs」ファイル)に記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.InputSystem; public class Jump : MonoBehaviour { Rigidbody2D rigid2D; // ジャンプの速度 float speed = 350.0f; void Start() { rigid2D = GetComponent<Rigidbody2D>(); } void OnTriggerStay2D(Collider2D collision) { if(Keyboard.current.spaceKey.isPressed) { rigid2D.AddForce(Vector2.up * speed); } } } |
図13 「Anime」ファイル(「Anime.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 36 37 38 39 40 41 42 43 44 45 46 47 48 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.InputSystem; public class Anime : MonoBehaviour { public Sprite[] character_anime; SpriteRenderer spriteRenderer; Rigidbody2D rigid2D; bool direction_right = true; void Start() { spriteRenderer = GetComponent<SpriteRenderer>(); rigid2D = GetComponent<Rigidbody2D>(); } private void Update() { if (rigid2D.linearVelocity.x > 0) direction_right = true; if (rigid2D.linearVelocity.x < 0) direction_right = false; } void OnTriggerStay2D(Collider2D collision) { if (direction_right) { if (rigid2D.linearVelocity.x == 0) spriteRenderer.sprite = character_anime[0]; else spriteRenderer.sprite = character_anime[2]; } else { if (rigid2D.linearVelocity.x == 0) spriteRenderer.sprite = character_anime[3]; else spriteRenderer.sprite = character_anime[5]; } } void OnTriggerExit2D(Collider2D collision) { if (direction_right) spriteRenderer.sprite = character_anime[1]; else spriteRenderer.sprite = character_anime[4]; } } |

004 レポート 米Adobe社の商用利用可能な動画生成AI
005 レポート MongoDB代替のFerretDB新版 コード掲載
006 製品レビュー パソコン「Dell Pro 13 Premium」
007 NEWS FLASH
008 特集1 はじめてのシェルスクリプト/麻生二郎 コード掲載
013 インタビュー Linux Foundation Japan 福安徳晃氏
014 特集2 ベクトル検索エンジンのVald/湯川輝一朗 コード掲載
022 特集3 Hinemos入門 ジョブ編/倉田晃次、加藤達也
030 Markdownを活用する/藤原由来
039 Raspberry Pi Pico W/WHで始める電子工作/米田聡
046 Pythonあれこれ/飯尾淳 コード掲載
052 香川大学SLPからお届け!/遠藤幸太郎 コード掲載
058 中小企業手作りIT化奮戦記/菅雄一
064 SSHによるリモート管理入門/大津真
074 Techパズル/gori.sh
075 コラム「小売業のビジネスリーダーを育成する講義」/シェル魔人
著者:末安 泰三
米FerretDB社は2025年3月5日、MongoDB互換のドキュメント指向データベース管理システム「FerretDB」のバージョン2.0をリリースした。米Microsoft社のNoSQLデータベース実装「DocumentDB」を採用したことで、従来版比で20倍以上の性能を出せるケースがあるという。
シェルスクリプトマガジン Vol.95は以下のリンク先でご購入できます。![]()
![]()
図1 「docker-compose.yml」ファイルに記述する内容
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
services: postgres: image: ghcr.io/ferretdb/postgres-documentdb:17-0.102.0-ferretdb-2.0.0 platform: linux/amd64 restart: on-failure environment: - POSTGRES_USER=username - POSTGRES_PASSWORD=password - POSTGRES_DB=postgres volumes: - ./data:/var/lib/postgresql/data ferretdb: image: ghcr.io/ferretdb/ferretdb:2.0.0 restart: on-failure ports: - 27017:27017 environment: - FERRETDB_POSTGRESQL_URL=postgres://username:password@postgres:5432/postgres networks: default: name: ferretdb |
著者:麻生 二郎
シェルスクリプトは、LinuxやUNIX系OSのコマンドのみで記述できるプログラムです。さまざまな処理をテキストファイルに書き込み、LinuxやUNIX系OSの環境ですぐに試せるので便利です。本特集では、はじめてシェルスクリプトを書く人向けにその記述テクニックを分かりやすく紹介します。
シェルスクリプトマガジン Vol.95は以下のリンク先でご購入できます。![]()
![]()
図5 引数の受け取りを試すシェルスクリプト(test1.sh)
|
1 2 3 4 5 |
#!/bin/bash -e echo $1 echo $2 echo $3 |
図6 readコマンドを試すシェルスクリプト(test2.sh)
|
1 2 3 4 |
#!/bin/bash -e read -p 'Please input: ' INPUT01 echo $INPUT01 | tee input.txt |
図7 ifを試すシェルスクリプト(test3.sh)
|
1 2 3 4 5 6 7 8 9 |
#!/bin/bash -e if [ $1 == '100' ]; then echo 'good' elif [ $1 == '0' ]; then echo 'bad' else echo 'unknown' fi |
図8 caseを試すシェルスクリプト(test4.sh)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#!/bin/bash -e case $1 in 100) echo 'very good' ;; 80) echo 'pretty good' ;; 60) echo 'good' ;; 0) echo 'bad' ;; esac |
図9 Whileを試すシェルスクリプト(test5.sh)
|
1 2 3 4 5 6 7 8 |
#!/bin/bash -e i=0 while [ $i -le 10 ] do echo $i i=$((i + 1)) done |
図10 forを試すシェルスクリプト(test6.sh)
|
1 2 3 4 5 6 |
#!/bin/bash -e for i in 0 1 2 3 4 5 do echo $i done |
図11 関数を試すシェルスクリプト(test7.sh)
|
1 2 3 4 5 6 7 8 9 |
#!/bin/bash -e function output() { echo $1 } output run1 output run2 |
著者:湯川 輝一朗
ベクトル検索エンジン「Vald」は、純国産のオープンソースソフトウエア(OSS)です。画像や音声などの非構造データを数値ベクトル化にして検索に利用します。クラウド上でも動作し、大規模な高速検索に向いています。ライセンスはApache License 2.0であり、無料利用が可能です。本特集では、Valdについて解説します。
シェルスクリプトマガジン Vol.95は以下のリンク先でご購入できます。![]()
![]()
図7 values.yamlファイル
|
1 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 |
defaults: ## Log level logging: level: debug ## 利用するDockerイメージのtag(componentごとに個別設定も可能) image: tag: v1.7.16 server_config: ## ヘルスチェックの設定 healths: liveness: livenessProbe: initialDelaySeconds: 60 readiness: readinessProbe: initialDelaySeconds: 60 ## O11yのための設定 servers: grpc: server: grpc: interceptors: - RecoverInterceptor - TraceInterceptor - MetricInterceptor ## 以下もO11yのための設定 grpc: client: dial_option: interceptors: - TraceInterceptor observability: enabled: true otlp: collector_endpoint: "opentelemetry-collector-collector.default.svc.cluster.local:4317" trace: enabled: true networkPolicy: enabled: true custom: ingress: - from: - podSelector: matchLabels: app.kubernetes.io/name: pyroscope egress: - to: - podSelector: matchLabels: app.kubernetes.io/name: opentelemetry-collector-collector ## gatewayの設定 gateway: lb: minReplicas: 3 maxReplicas: 3 resources: requests: cpu: 150m memory: 150Mi hpa: enabled: false ingress: # Kubernetesのingressを利用する enabled: true # ingressのホスト host: localhost service: # k3dでingressを利用するための設定 annotations: traefik.ingress.kubernetes.io/service.serversscheme: h2c ## vald-agentの設定 agent: minReplicas: 6 maxReplicas: 6 ## Production環境では推奨しないが、今回はParallelで行う podManagementPolicy: Parallel resources: requests: cpu: 150m memory: 150Mi ngt: # 次元数 dimension: 784 # 距離関数 distance_type: l2 # ベクトルのオブジェクトタイプ object_type: float # 自動Indexを行うためのチェック周期(Index Mangerを利用するためマイナスにして無効化) auto_index_check_duration_limit: "-1m" # 自動Indexの周期(Index Mangerを利用するためマイナスにして無効化) auto_index_duration_limit: "-20s" # 自動インデックス作成操作のバッチプロセスプールサイズ auto_create_index_pool_size: 100 # インデックス構築時のデフォルトバッチサイズ default_pool_size: 100 ## vald-discovererの設定 discoverer: resources: requests: cpu: 150m memory: 50Mi ## vald-managerの設定 manager: index: resources: requests: cpu: 150m memory: 30Mi indexer: # Index Managerを有効化 enable: true # 自動インデックス作成の制限期間 # これにより、各Vald Agentで強制インデックス作成がトリガーされる auto_index_duration_limit: 1m # 自動インデックス作成のチェック期間 # これにより、コミットされていないインデックスが制限を超えた場合、各Vald Agnetでのインデックス作成がトリガーされる auto_index_check_duration: 40s |
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第25回では、人間がどのように文章の意味を理解するかという認知能力に関わる、少し不思議で面白い現象について取り上げます。
シェルスクリプトマガジン Vol.95は以下のリンク先でご購入できます。![]()
![]()
図4 タイポグリセミア度を算出する関数tl()の定義コード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import re # タイポグリセミアに関係する部分を取り出して文字のリストを返す def s2cl(s): sp_chars = '[,.;:.,。、??!!]' s2 = re.sub(sp_chars, '', s.strip()).split() return list("".join( [x[1:-1] for x in filter(lambda x: len(x) > 3, s2)] )) # タイポグリセミア度を計算する関数 def tl(s1, s2): s1_chars = s2cl(s1) s2_chars = s2cl(s2) # 入れ換えた文字を数える ctr = 0 for i in range(len(s1_chars)): if s1_chars[i] != s2_chars[i]: ctr += 1 # タイポグリセミア度を返す return ctr / len(s1_chars) |
図5 関数make_candidate_index()の定義コード
|
1 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 |
def make_candidate_index(orig, debug=False): tmp = ' ' candidate = ' ' stopwords = ' ,.!?!?.。,、「」\"\#\*\&\n' indexes = [] # ストップワード文字と、それに隣接する文字を空白に変換 for i in range(1, len(orig)-1): tmp = tmp[:i] + \ (' ' if orig[i-1] in stopwords \ or orig[i+1] in stopwords \ or orig[i] in stopwords \ else orig[i]) # 直前、直後がストップワード文字の文字を空白に変換 for i in range(1, len(tmp)-1): candidate = candidate[:i] + \ (' ' if (tmp[i-1] in stopwords \ and tmp[i+1] in stopwords) \ or (tmp[i] in stopwords) \ else tmp[i]) # debug=True フラグで動作確認できるようにする if debug: print('ORG:' + orig) print('TMP:' + tmp) print('CDD:' + candidate) # 候補の文字位置のリストを要素にするリストを作成 flag:bool = False for i in range(1, len(candidate)-1): if candidate[i] not in stopwords: if not flag: flag = True; subindexes = [i] else: subindexes.append(i) else: if flag: indexes.append(subindexes) flag = False return indexes |
図7 タイポグリセミア文を作成する関数typoglycemia()の定義コード
|
1 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 |
from functools import reduce import random def typoglycemia(orig, tlval, debug=False): if tlval < 0.0 or tlval > 1.0: print('tlvalは0.0以上1.0以下でなくてはいけません') return None # 最初にインデックスのリストを作成する indexes = make_candidate_index(orig) # indexesに含まれる全要素数を計算する denominator = len(reduce(lambda x, y: x+y, indexes)) # 文字を交換すべき処理対象のリスト procs = [] # 交換する文字数 swap_nums = 0 while swap_nums < tlval * denominator: # 対象を決める target_idx = random.randint(0, len(indexes) - 1) target_list = indexes[target_idx] if debug: print('---\n処理の対象: ' + str(target_list)) # 単語が十分に長い場合は、処理対象の2文字を特定する if len(target_list) >= 4: char_idx = 0 swap_idx = 0 while char_idx == swap_idx: char_idx = random.randint(0, len(target_list) - 1) swap_idx = random.randint(0, len(target_list) - 1) c1 = target_list[char_idx] c2 = target_list[swap_idx] target_list.remove(c1) target_list.remove(c2) procs.append([c1, c2]) swap_nums += 2 # 処理対象の単語が2文字または3文字の場合 elif len(target_list) == 2 or len(target_list) == 3: procs.append(target_list) indexes.remove(target_list) swap_nums += len(target_list) target_list = [] else: print('エラー') return None if debug: print('処理リスト: ' + str(procs)) print('処理後対象: ' + str(target_list)) if debug: print('---\n残り: ' + str(indexes) + '\n') # procsに入れられた情報に基づき文字の入れ替え処理を行う chars:list[str] = list(orig) for l in procs: if len(l) == 2: tmp = chars[l[0]] chars[l[0]] = orig[l[1]] chars[l[1]] = tmp else: tmp = chars[l[0]] if random.randint(0, 1) == 0: # 時計回りの3文字入れ替え chars[l[0]] = orig[l[1]] chars[l[1]] = orig[l[2]] chars[l[2]] = tmp else: # 反時計回りの3文字入れ替え chars[l[0]] = orig[l[2]] chars[l[2]] = orig[l[1]] chars[l[1]] = tmp return ''.join(chars) |
図8 タイポグリセミア度「0.7」の文を作成した例
|
1 2 3 |
org = 'The superpowered quick brown fox jumps over the beautiful lazy dog.' typo = typoglycemia(org, 0.7, debug=True) typo |
著者:遠藤幸太郎
AIと自然言語処理技術の発展により、「ChatGPT」に代表される、人間と自然な形で対話できる会話型AIが注目を集めています。今回は、PythonでAIチャットボットアプリを開発していきます。米Google社の「Gemini API」を利用し、対話だけでなく、音声や映像ファイルをアップロードして分析できるようにします。
シェルスクリプトマガジン Vol.95は以下のリンク先でご購入できます。![]()
![]()
図3 「win.py」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
from PySide6.QtWidgets import ( QApplication, QMainWindow, QLabel ) from PySide6.QtCore import Qt import sys class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("ウィンドウタイトル") self.setGeometry(100, 100, 400, 300) label = QLabel("テキスト", self) label.setGeometry(0, 0, 400, 300) label.setAlignment(Qt.AlignCenter) if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec()) |
図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 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 |
from PySide6.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QTextEdit, QLineEdit, ① ) from PySide6.QtCore import Qt, QThread, Signal import os, sys, re ② import google.generativeai as genai from dotenv import load_dotenv from markdown2 import markdown MAX_HISTORY_LENGTH = 10 class ChatWindow(QMainWindow): def __init__(self): super().__init__() self.history = [] self.setWindowTitle("ChatBot") self.setGeometry(100, 100, 600, 400) self.central_widget = QWidget() self.setCentralWidget(self.central_widget) self.main_layout = QVBoxLayout() self.chat_layout = QVBoxLayout() self.input_layout = QHBoxLayout() self.file_layout = QHBoxLayout() self.chat_display = QTextEdit() self.chat_display.setReadOnly(True) self.main_layout.addWidget(self.chat_display) self.input_box = QLineEdit() self.input_box.setPlaceholderText("ここにメッセージを入力") self.input_layout.addWidget(self.input_box) self.send_button = QPushButton("送信") self.send_button.clicked.connect(self.handle_send) self.input_layout.addWidget(self.send_button) self.main_layout.addLayout(self.input_layout) ③ self.central_widget.setLayout(self.main_layout) def handle_send(self): user_message = self.input_box.text().strip() if not user_message: return self.send_button.setEnabled(False) self.chat_display.append(f"<b>あなた:</b> {user_message}") ④ self.input_box.clear() self.response_thread = BotResponseThread( user_message, self.history ⑤ ) self.response_thread.response_ready.connect( self.display_bot_response ) self.response_thread.start() def safe_markdown(self, text): lines = text.splitlines() formatted_lines = [] for i, line in enumerate(lines): if re.match(r"^\s*(\d+\.\s+|[-*+]\s+)", line) and ( i + 1 < len(lines) and lines[i + 1].strip() ): formatted_lines.append(line) formatted_lines.append("") else: formatted_lines.append(line) formatted_text = "\n".join(formatted_lines) return markdown(formatted_text) def display_bot_response(self, response_text): html_content = self.safe_markdown(response_text) self.chat_display.append(f"<b>Bot:</b> {html_content}") self.send_button.setEnabled(True) ⑥ class BotResponseThread(QThread): response_ready = Signal(str) def __init__(self, user_message, history): ⑦ super().__init__() self.user_message = user_message ⑧ self.history = history def run(self): response_text = self.get_bot_response(self.user_message) ⑨ self.response_ready.emit(response_text) def get_bot_response(self, message): ⑩ input_text = message self.history.append({"role": "user", "parts": [input_text]}) ⑪ response = model.generate_content(self.history) self.history.append(response.candidates[0].content) if len(self.history) > MAX_HISTORY_LENGTH: self.history.pop(0) return response.text def initialize_genai(): load_dotenv() api_key = os.getenv("GOOGLE_API_KEY") genai.configure(api_key=api_key) return genai.GenerativeModel("gemini-1.5-flash") if __name__ == "__main__": model = initialize_genai() app = QApplication(sys.argv) window = ChatWindow() window.show() sys.exit(app.exec()) |
図9 図5の③で示す箇所に挿入するコード
|
1 2 3 4 5 6 7 8 |
self.file_label = QLabel("ファイルが選択されていません") self.file_label.setAlignment(Qt.AlignLeft) self.file_layout.addWidget(self.file_label) self.upload_button = QPushButton("ファイルを選択") self.upload_button.clicked.connect(self.handle_file_selection) self.file_layout.addWidget(self.upload_button) self.main_layout.addLayout(self.file_layout) self.selected_file_path = None |
図10 図5の④で示す箇所に挿入するコード
|
1 2 3 4 5 6 7 8 9 |
if self.selected_file_path: self.chat_display.append( f"<b>添付ファイル:</b> {self.selected_file_path}" ) file_path = self.selected_file_path self.selected_file_path = None self.file_label.setText("ファイルが選択されていません") else: file_path = None |
図11 図5の⑥で示す箇所に挿入するコード
|
1 2 3 4 5 6 7 8 9 10 |
def handle_file_selection(self): file_dialog = QFileDialog() file_path, _ = file_dialog.getOpenFileName( self, "ファイルを選択" ) if file_path: self.selected_file_path = file_path self.file_label.setText( f"選択したファイル: {file_path.split('/')[-1]}" ) |
図12 図5の⑪で示す箇所に挿入するコード
|
1 2 3 4 5 6 7 8 9 10 |
if file_path: attached_file = genai.upload_file(path=file_path) while attached_file.state.name == "PROCESSING": time.sleep(10) attached_file = genai.get_file(attached_file.name) self.history.append( {"role": "user", "parts": [attached_file]} ) else: pass |
Hinemosの運用管理機能を提供する「Hinemosマネージャ」などのソフトウエアを、「Red Hat Enterprise Linux 9」が稼働するホストにインストールする手順を、こちらで紹介しています。
静的サイトジェネレータ「Hugo」およびバージョン管理システム「Git」のインストール、Gitのセットアップ、サイトの構築手順は、こちらから参照できます。
記事で紹介したコードを拡張して、アプリのボタンなどの見栄えを調整しました。コードを記したファイルはここからダウンロードできます(ファイルはZIPで圧縮しています)。
情報は随時更新致します。
2025年4月号特集3を読むための準備として、Linuxディストリビューション「Red Hat Enterprise Linux 9」(RHEL9)環境にHinemosをインストールしてエージェントを起動する方法を紹介します。このコンテンツは、2024年10月号のシェルスクリプトマガジンVol.92特集2を基にしています。以下では、インストール先のホスト名が「rhel-manager」、IPアドレスが「172.16.63.245」の場合を例に、作業手順を解説します。他のホスト名やIPアドレスを利用している場合には、適宜読み替えてください。
Hinemosのシステムは、「マネージャサーバ」「クライアント」「管理対象ノード」で構成されます。(図1)。

マネージャサーバは、Himemosの運用管理機能を提供するHinemosマネージャ(以下、マネージャ)が稼働するマシンです。管理対象の情報を保持したリポジトリと、監視やジョブといった各機能で扱うデータを保管するデータベースを保持します。
クライアントは、オペレータが利用するGUIベースの操作端末です。OSに専用ソフトウエアをインストールして使う「リッチクライアント」と、Webブラウザで操作できる「Webクライアント」の2種類があります。今回は、マネージャサーバにWebクライアントをインストールして、それをWebブラウザで使うことにします。
管理対象ノードは、監視やジョブ実行などを行うHinemosの管理対象となるマシンです。利用する機能によっては、管理対象ノードに「Hinemosエージェント」(以下、エージェント)やSNMP*サーバーなどのソフトウエアをインストールする必要があります。今回は、マネージャサーバ自身にエージェントをインストールして、管理対象ノードにします。
【SNMP】 リモート機器を監視、制御するためのネットワークプロトコル。Simple Network Management Protocolの略。
マネージャをインストールする前に、マネージャの動作に必要なパッケージをインストールします。rhel-managerに管理者(rootユーザー)でログインしてから、次のコマンドを実行してください。
|
1 2 |
# dnf install -y java-1.8.0-openjdk tar unzip vim ↵ # dnf install -y google-noto-sans-cjk-ttc-fonts java-1.8.0-openjdk-devel lsof net-snmp net-snmp-utils sysstat tcpdump wsmancli zip ↵ |
なお、2回目のdnfコマンドでインストールしているパッケージは必須ではありませんが、インストールが推奨されているものです。特別な理由がなければインストールすることをお勧めします。以下では、これらの推奨パッケージもインストールしたものとします。
続いて、次のコマンドを実行してロケール(言語設定)を日本語にします。
|
1 2 |
# dnf install -y glibc-langpack-ja ↵ # localectl set-locale LANG=ja_JP.UTF-8 ↵ |
さらにSELinux*を無効にします。SELinuxが有効になっていると、マネージャをインストールできないからです。SELinuxを無効にするには、「/etc/selinux/config」ファイルの「SELINUX」行を次のように書き換えてから、RHEL9を再起動します。
|
1 |
SELINUX=disabled |
【SELinux】 強制アクセス制御と呼ばれるセキュリティ機能を提供するセキュリティモジュール。Security-Enhanced Linuxの略。
なお、同行が「SELINUX=enforcing」の場合には、SELinuxが有効になります。「SELINUX=permissive」の場合には、SELinuxによるアクセス制御は無効になりますが、監査ログの記録は有効になります。
マネージャサーバがクライアントや管理対象ノードからの接続を受け付けられるように、ファイアウォールの設定もします。RHEL9では、デフォルトでファイアウォールが有効になっています。そのため、ファイアウォールを無効にするか、接続を待ち受けるポートを開放する必要があります。
ファイアウォールを無効にするには、次のコマンドを実行します。
|
1 2 |
# systemctl stop firewalld ↵ # systemctl disable firewalld ↵ |
ファイアウォールを有効にしておきたい場合には、ポートを開放する設定をします。例えば、TCPの22番ポートと80番ポートを開放するには、次のようにfirewalldコマンドを実行します。
|
1 |
# firewall-cmd --permanent --add-port={22/tcp,80/tcp} ↵ |
同様の手順で、表1に示すポートをすべて開放する設定をしてください*1。設定後、次のコマンドを実行することでファイアウォールに反映されます。
|
1 |
# firewall-cmd --reload ↵ |
*1 開放するポートの詳細については、「Hinemos ver.7.1 基本機能マニュアル」(https://github.com/hinemos/hinemos/releases/download/v7.1.0/ja_Base_Linux_7.1_rev1.pdf)の「2.2.9 ネットワークの要件」を参照してください。
表1 マネージャサーバで開放すべきポート
| プロトコル | リッチクライアントや管理対象ノードからの接続待ち受けポート | Webクライアントの接続待ち受けポート |
|---|---|---|
| TCP | 22,8080,8081,8082,8083,8443,8444,8445,24001 | 80,443 |
| UDP | 161,162,514 | – |
最後に、マネージャサーバのホスト名の名前解決ができることを確認します。名前解決ができない場合、マネージャがうまく動作しないことがあります。
次のようにpingコマンドを実行して、自分自身から応答があることを確認してください。
|
1 |
# ping rhel-manager ↵ |
「Name or service not known」と表示される場合には、名前解決ができていません。そのときには、次のコマンドを実行して名前解決用の情報を登録します。他のホスト名やIPアドレスを利用している場合には、適宜読み替えてください。
|
1 |
# echo 172.16.63.245 rhel-manager >> /etc/hosts ↵ |
以上で事前準備は完了です。
マネージャやWebクライアント用コンポーネント、管理対象ノードにインストールするエージェントのパッケージは、GitHubリポジトリのリリースページ(https://github.com/hinemos/hinemos/releases)から入手できます。ここから、最新版をインストールしてください。
今回は、2024年9月上旬時点の最新版である「Hinemos ver.7.1.0」のパッケージをインストールします。マネージャのパッケージは「hinemos-7.1-manager-7.1.0-1.el9.x86_64.rpm」、Webクライアント用コンポーネントのパッケージは「hinemos-7.1-web-7.1.0-1.el9.x86_64.rpm」です。これらを「/tmp」ディレクトリなどにダウンロードし、ダウンロードしたディレクトリで次のコマンドを実行するとインストールできます。
|
1 2 |
# rpm -ivh hinemos-7.1-manager-7.1.0-1.el9.x86_64.rpm ↵ # rpm -ivh hinemos-7.1-web-7.1.0-1.el9.x86_64.rpm ↵ |
GitHubリポジトリからも直接インストールできます。それには、次のようにrpmコマンドを実行します。
|
1 2 |
# rpm -ivh https://github.com/hinemos/hinemos/releases/download/v7.1.0/hinemos-7.1-manager-7.1.0-1.el9.x86_64.rpm ↵ # rpm -ivh https://github.com/hinemos/hinemos/releases/download/v7.1.0/hinemos-7.1-web-7.1.0-1.el9.x86_64.rpm ↵ |
パッケージのインストール後、次のコマンドを実行するとマネージャとWebクライアントが起動します。
|
1 2 |
# systemctl start hinemos_manager ↵ # systemctl start hinemos_web ↵ |
続いて、マネージャサーバにエージェントを導入して、自分自身を管理対象ノードとして取り扱えるようにします*2。それにはまず、次のコマンドを実行して、エージェントの実行に必要なパッケージをインストールします。
|
1 |
# dnf install -y openssh-clients ↵ |
*2 マネージャサーバ以外のコンピュータにエージェントをインストールする場合の作業手順については、「Hinemos ver.7.1 基本機能マニュアル」の「2.2.5Hinemosエージェントの要件」などを参照してください。
ファイアウォールも設定します。ファイアウォールを無効にするか、表2に示すポートを開放してください。
表2 管理対象ノードで開放すべきポート
| プロトコル | 開放すべきポート |
|---|---|
| TCP | 22 |
| UDP | 161,24005 |
さらに、次のコマンドを実行してsnmpdサービス(SNMPサーバー)を起動します。これにより、マネージャサーバのCPUやメモリーなどのリソース情報をHinemosが取得できるようになります。
|
1 |
# systemctl start snmpd ↵ |
ここまでの作業で、エージェントをインストールする準備が整いました。エージェントのパッケージは「hinemos-7.1-agent-7.1.0-1.el.noarch.rpm」です。これを「/tmp」ディレクトリなどにダウンロードし、ダウンロードしたディレクトリで次のコマンドを実行するとインストールできます。
|
1 |
# HINEMOS_MANAGER=172.16.63.245 rpm -ivh hinemos-7.1-agent-7.1.0-1.el.noarch.rpm ↵ |
シェル変数HINEMOS_MANAGERには、マネージャサーバのIPアドレスを設定します。
先ほどと同様に、GitHubリポジトリからも直接インストールできます。それには、次のようにrpmコマンドを実行します。
|
1 |
# HINEMOS_MANAGER=172.16.63.245 rpm -ivh https://github.com/hinemos/hinemos/releases/download/v7.1.0/hinemos-7.1-agent-7.1.0-1.el.noarch.rpm ↵ |
最後に、次のコマンドを実行すればエージェントが起動します。
|
1 |
# systemctl start hinemos_agent ↵ |
連載「多種多様な文書作成が可能 Markdownを活用する」の第6回(シェルスクリプトマガジン Vol.94で掲載)で作成したWebサイトを第7回でも使用します。第7回で紹介する作業が進めやすいように、HugoとGitのインストール、Gitの初期設定、Webサイトの構築手順を以下にまとめてました。
なお、手順自体は、第6回の「Hugoのインストール」「HugoによるWebサイトの構築」で紹介した内容と同じです。
Hugoを使用するには、次のソフトウエアをインストールします。
・Hugo本体
・Git
まず、Hugo本体をインストールします。以降は、導入先のパソコンのOSとしてWindows 11を前提に進めます。導入後は、macOSやLinuxでも同じようにHugoを利用できます。また、Markdownを記述するためのテキストエディタとしてVisual Studio Code(VSCode)を利用します。VSCodeは、あらかじめ
インストールされていることを前提とします。
[Windows]キーを押しながら[X]キーを押すと、図1のようなメニューが表示されます。このメニューの「ターミナル( 管理者)」を選択します*1。選択後に、「ユーザー アカウント制御」ダイアログが表示されるので、「はい」ボタンをクリックします(図2)。管理者権限付きのPowerShellが起動します*2。
図1「ターミナル(管理者)」を選択 図2「ユーザーアカウント制御」ダイアログ


PowerShellで、次のコマンドを実行してHugo本体をインストールします。
|
1 |
> winget install -e --id Hugo.Hugo.Extended ↵ |
図3のようにインストールが実行されます。最後に「インストールが完了しました」のメッセージが表示されれば、インストール完了です。
図3 Hugoのインストール

右上の「×」ボタンをクリックして、管理者権限付きのPowerShellを閉じます。再び、[Windows]キーを押しながら[X]キーを押し、表示されるメニューから「ターミナル」(もしくは「Windows PowerShell」)を選択します。一般権限でPowerShellが起動します。
PowerShellで、次のコマンドを実行します。
|
1 |
> hugo version ↵ |
次のようにバージョン番号の文字列が表示されれば、Hugoが利用できます。
|
1 |
hugo v0.139.4-3afe91d4b1b069abbedd6a96ed755b1e12581dfe+extended windows/amd64 BuildDate=2024-12-09T17:45:23Z VendorInfo=gohugoio |
右上の「×」ボタンをクリックして、PowerShellを閉じます。
続いて、Gitをインストールします。その前に、Gitがすでにインストールされているかどうかを確認します。[Windows]キーを押しながら[X]キーを押し、表示されるメニューから「ターミナル(管理者)」(もしくは「Windows PowerShell( 管理者)」)を選択します。選択後、「ユーザー アカウント制御」ダイアログが表示されたら、「はい」ボタンをクリックします。管理者権限付きのPowerShellが起動します。
このPowerShellで、次のコマンドを実行します。
|
1 |
> git --version ↵ |
次のような形式でバージョン番号が表示されれば、Gitがインストールされています*4。このような状態ならGitのインストールをスキップしてください。
|
1 |
git version 2.47.0.windows.2 |
一方、次のように表示された場合は、Gitがインストールされていません。
|
1 |
git : 用語 'git' は、コマンドレット、関数、スクリプトファイル、または操作可能なプログラムの名前として認識されません。 |
次のコマンドでGitをインストールします。
|
1 |
> winget install --id Git.Git -e --source winget ↵ |
途中でダイアログが表示されますが、そのまま待ってください。最後に「インストールが完了しました」のメッセージが表示されればインストール完了です。
右上の「×」ボタンをクリックして、管理者権限付きのPowerShellを閉じます。[Windows]キーを押しながら[X]キーを押し、表示されるメニューから「ターミナル」(もしくは「Windows PowerShell」)を選択します。一般権限でPowerShellが起動します。
再度、
|
1 |
> git --version ↵ |
を実行し、バージョン番号が表示されればインストール完了です。
Gitを初めて使用する場合、次のコマンドで自身の氏名とメールアドレスを設定します。氏名は、全角の平仮名や片仮名、漢字ではなく、半角のアルファベットやスペース、数字、記号で入力します。
|
1 2 |
git config --global user.name "氏名" git config --global user.email "メールアドレス" |
ここで入力した氏名とメールアドレスは、次回の連載で使用する、ソースコードのホスティングサービス「GitHub」で公開される情報となります。したがって、自分の本名を隠したい場合、氏名はハンドルネーム(インターネット上のニックネーム)で構いません。
|
1 |
> git config --global user.name "free writer" ↵ |
また、記事内で紹介した方法でGitHub専用のメールアドレスを発行します。まだ、GitHubのアカウントを持っていない場合は、次のように仮のメールアドレスを設定しておいてください。すでにGitHubアカウントを持っている場合は、そのアカウントに登録しているメールアドレスを設定します。
|
1 2 |
> git config --global user.email "dummy@example.com" ↵ |
Power Shellで次のコマンドを実行します。
|
1 2 3 4 5 6 7 |
> cd ~ ↵ > hugo new site first-hugo --format yaml ↵ > cd first-hugo ↵ > git init ↵ > git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke.git themes/ananke ↵ > hugo new content content/posts/first-post.md ↵ > hugo new content content/posts/public-post.md ↵ |
以上のコマンドを実行した後に、次のコマンドでVSCode(Visual Studio Code)を起動します*3。
|
1 |
> code . ↵ |
「このフォルダー内のファイルの作成者を信頼しますか?」というダイアログが表示されたら「はい、作成者を信頼します」をクリックします。
VSCodeが起動したら、VSCode上で次の三つのファイルを書き換えます。
「hugo.yaml」ファイルでは、「languageCode」「title」「theme」の行を書き換えます。
|
1 2 3 4 |
baseURL: https://example.org/ languageCode: ja-jp title: "はじめてのHugoサイト" theme: ananke |
「first-post.md」ファイルでは、「title」の行を書き換えて、「# はじめての記事」から始まる本文を追加します。
|
1 2 3 4 5 6 7 8 9 |
--- date: '2024-11-30T13:02:12+09:00' draft: true title: 'はじめての記事' --- # はじめての記事 こんにちは! |
「public-post.md」ファイルでは、「draft」と「title」の行を書き換えて、「# 公開テスト」から始まる本文を追加します。
|
1 2 3 4 5 6 7 8 9 |
--- date: '2024-11-30T13:50:00+09:00' draft: false title: '公開テスト' --- # 公開テスト この記事は公開されます。 |
first-post.mdファイルは下書き状態(draft: true)であり、public-post.mdファイルは公開状態(draft: false)にしています。
次のコマンドを実行してWebサーバーを起動します。
|
1 |
> hugo server ↵ |
コマンドを実行すると、次のような表示がされます。
|
1 2 3 4 5 6 |
Built in 252 ms Environment: "development" Serving pages from disk Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender Web Server is available at http://localhost:1313/ (bind address 127.0.0.1) Press Ctrl+C to stop |
ここで表示されているURLの「http://localhost:1313/」(「1313」は環境によって異なる)をコピーし、Webブラウザを開いてアドレスバーに貼り付けてアクセスします。「公開テスト」のコンテンツのみが表示され、「はじめての記事」のコンテンツは表示されません。
PowerShellに戻って[Ctrl]キーを押しながら[C]キーを押し、Webサーバーを停止します。
最後に、次のコマンドを実行して、Webサイトをビルドします。
|
1 |
> hugo ↵ |
*1 Windows 10では「Windows PowerShell( 管理者)」を選択します。
*2 「WSL」(Windows Subsystem for Linux)でLinuxディストリビューション「Ubuntu」などを導入している場合は、PowerShellではなくWSLが優先されます。その場合は、ターミナル上部タブの右にある下向き矢印のアイコンをクリックしてメニューから「Windows PowerShell」を選んでください。以降、PowerShellのターミナルを開くときはこの手順を実施してください。
*3 VSCodeインストール時の「追加タスクの選択」「- その他」で、「PATHへの追加(再起動後に使用可能)」にチェックが付けられていないとcodeコマンドを実行できません(デフォルトでチェックあり)。なお、チェックを付けてVSCodeを再インストールすれば、使えます。

004 レポート AIチャット「Grok」がXで無料利用
005 レポート 完全オープンなLLM「OpenCoder」
006 製品レビュー ウエアラブル「ViXion01」
007 NEWS FLASH
008 特集1 パソコン仮想化ソフトのVMware Workstation Pro/麻生二郎
019 インタビュー Infinidat Japan 山田秀樹氏
020 特集2 さくらのレンタルサーバで始める情報発信ガイド/前佛雅人 コード掲載
028 特集3 Red Hat Enterprise Linux AI/村木暢哉
034 Raspberry Pi Pico W/WHで始める電子工作/米田聡 コード掲載
038 Markdownを活用する/藤原由来
046 Pythonあれこれ/飯尾淳 コード掲載
052 香川大学SLPからお届け!/川本真子 コード掲載
058 中小企業手作りIT化奮戦記/菅雄一
064 SSHによるリモート管理入門/大津真
072 Techパズル/gori.sh
073 コラム「ユニケージ流のデータ設計法」/シェル魔人
著者:前佛雅人
さくらインターネットが提供する「さくらのレンタルサーバ」を利用すれば、「WordPress」を使ったブログサイトなどの制約が少ない、自分だけの情報発信拠点をインターネット上に構築できます。ファイル共有やWikiなどのアプリケーションも思い通りにカスタマイズして利用できます。本特集では、さくらのレンタルサーバを活用した情報発信の方法を紹介します。
シェルスクリプトマガジン Vol.94は以下のリンク先でご購入できます。![]()
![]()
図8 サンプルHTMLファイル「index.html」の内容
|
1 2 3 4 5 6 7 8 9 |
<!DOCTYPE html> <html> <head><title>サンプルWebページ</title></head> <body> <h1>これはサンプルWebページです</h1> <p>次に示すのはカワセミの写真です。</p> <p><img src="image.jpg" width="320"></p> </body> </html> |
著者:米田 聡
本連載では、人気のマイコンボード「Raspberry Pi Pico W/WH」を活用していきます。同ボードは、無
線LANやBluetoothの通信機能を搭載し、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第8回は前回に引き続き、「Raspberry Pi Pico 2」を扱います。
シェルスクリプトマガジン Vol.94は以下のリンク先でご購入できます。![]()
![]()
図10 LED点滅のプログラム(blink.c)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <stdio.h> #include "pico/stdlib.h" int main() { stdio_init_all(); int flag = 0; const unit LED_PIN = PICO_DEFAULT_LED_PIN; gpio_init(LED_PIN); gpio_set_dir(LED_PIN, GPIO_OUT); while(true) { gpio_put(LED_PIN, flag ^= 1); sleep_ms(500); } return 0; } |
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第24回では、高校で学ぶレベルの数学を用いて、問題を最適にする解は何かを探索します。題材は、線形計画法と組合せ最適問題の二つです。
シェルスクリプトマガジン Vol.94は以下のリンク先でご購入できます。![]()
![]()
図3 図1の問題を解くPythonコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
from ortools.linear_solver import pywraplp # ソルバーの定義 solver = pywraplp.Solver.CreateSolver('GLOP') # 変数の準備 x = solver.NumVar(0, solver.infinity(), 'x') y = solver.NumVar(0, solver.infinity(), 'y') # 目的関数と制約条件の定義 solver.Maximize(400 * x + 300 * y) solver.Add(60 * x + 40 * y <= 3200) solver.Add(20 * x + 30 * y <= 2000) solver.Add(20 * x + 10 * y <= 1000) # 問題を解く status = solver.Solve() # 結果を出力 if status == pywraplp.Solver.OPTIMAL: print(f'解:') print(f'x = {x.solution_value()}') print(f'y = {y.solution_value()}') else: print('最適解は存在しません') |
図4 CBCソルバーを使うように書き換えた図3のPythonコード
|
1 2 3 4 5 6 7 |
(略) # ソルバーの定義 solver = pywraplp.Solver.CreateSolver('CBC') # 変数の準備 x = solver.IntVar(0, solver.infinity(), 'x') y = solver.IntVar(0, solver.infinity(), 'y') (略) |
図6 データを準備するためのPythonコード
|
1 2 3 |
teachers = ['田中', '山本', '竹村', '飯島', '佐藤'] days = ['月', '火', '水', '木', '金'] periods = ['1限', '2限', '3限', '4限'] |
図7 lessons連想配列を作成するPythonコード
|
1 2 3 |
lessons = { (t, d, p): \ model.NewBoolVar('lesson_%s_%s_%s' % (t, d, p)) \ for t in teachers for d in days for p in periods } |
図8 lessons連想配列の内容
|
1 2 3 4 5 6 7 |
{('田中', '月', '1限'): lesson_田中_月_1限(0..1), ('田中', '月', '2限'): lesson_田中_月_2限(0..1), ('田中', '月', '3限'): lesson_田中_月_3限(0..1), ('田中', '月', '4限'): lesson_田中_月_4限(0..1), ('田中', '火', '1限'): lesson_田中_火_1限(0..1), ('田中', '火', '2限'): lesson_田中_火_2限(0..1), (略) |
図9 「同時に2人以上の先生が授業することはない」制約条件を与えるPythonコード
|
1 2 3 |
for d in days: for p in periods: model.Add(sum(lessons[(t, d, p)] for t in teachers) == 1) |
図10 「先生は1日に1コマだけ授業を担当する」制約条件を与えるPythonコード
|
1 2 3 |
for t in teachers: for d in days: model.Add(sum(lessons[(t, d, p)] for p in periods) <= 1) |
図11 「担当時間数に偏りが出ないように配置する」制約条件を与えるPythonコード
|
1 2 3 |
for t in teachers: model.Add(sum(lessons[(t, d, p)] \ for d in days for p in periods) == 4) |
図12 requests連想配列を定義するコードの例
|
1 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 |
requests = { '田中': { '月': {'1限': 0, '2限': 0, '3限': 1, '4限': 0}, '火': {'1限': 0, '2限': 0, '3限': 0, '4限': 0}, '水': {'1限': 0, '2限': 1, '3限': 0, '4限': 0}, '木': {'1限': 0, '2限': 0, '3限': 0, '4限': 1}, '金': {'1限': 0, '2限': 1, '3限': 0, '4限': 0} }, '山本': { '月': {'1限': 1, '2限': 0, '3限': 0, '4限': 0}, '火': {'1限': 0, '2限': 1, '3限': 0, '4限': 0}, '水': {'1限': 0, '2限': 0, '3限': 1, '4限': 0}, '木': {'1限': 1, '2限': 0, '3限': 0, '4限': 0}, '金': {'1限': 0, '2限': 0, '3限': 0, '4限': 0} }, '竹村': { '月': {'1限': 0, '2限': 1, '3限': 0, '4限': 0}, '火': {'1限': 0, '2限': 1, '3限': 0, '4限': 0}, '水': {'1限': 1, '2限': 0, '3限': 0, '4限': 0}, '木': {'1限': 1, '2限': 0, '3限': 0, '4限': 0}, '金': {'1限': 0, '2限': 0, '3限': 0, '4限': 0} }, '飯島': { '月': {'1限': 0, '2限': 0, '3限': 0, '4限': 1}, '火': {'1限': 0, '2限': 0, '3限': 0, '4限': 0}, '水': {'1限': 0, '2限': 1, '3限': 0, '4限': 0}, '木': {'1限': 0, '2限': 1, '3限': 0, '4限': 0}, '金': {'1限': 1, '2限': 0, '3限': 0, '4限': 0} }, '佐藤': { '月': {'1限': 0, '2限': 0, '3限': 0, '4限': 0}, '火': {'1限': 0, '2限': 0, '3限': 1, '4限': 0}, '水': {'1限': 0, '2限': 0, '3限': 1, '4限': 0}, '木': {'1限': 0, '2限': 1, '3限': 0, '4限': 0}, '金': {'1限': 0, '2限': 0, '3限': 0, '4限': 1} } } |
図13 目的関数を定義するPythonコード
|
1 2 3 |
model.Maximize( \ sum(requests[t][d][p] * lessons[(t, d, p)] \ for t in teachers for d in days for p in periods)) |
図14 結果を表示するPythonコード
|
1 2 3 4 5 6 7 8 |
for d in days: print(d + "曜日:") for p in periods: for t in teachers: if solver.Value(lessons[(t, d, p)]) == 1: print(t+'先生が'+p+'の授業を担当する。', end="") print() if requests[t][d][p] == 1 else print('※') print() |
図16 「先生の希望は少なくとも3コマはかなえる」制約条件を与えるPythonコード
|
1 2 3 |
for t in teachers: model.Add(sum(lessons[(t, d, p)]*requests[t][d][p] \ for d in days for p in periods) >= 3) |
著者:川本真子
私は最近、To Doリストを管理するGU(I Graphical User Interface)アプリをPythonで作成しました。作成には、Pythonの標準GUIライブラリの「Tkinter」(https://docs.python.org/ja/3/library/tkinter.html)を用いています。今回は、そのGUIアプリの実装について紹介します。
シェルスクリプトマガジン Vol.94は以下のリンク先でご購入できます。![]()
![]()
図7 フレーム配置用のコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# フレームの作成 fr_title = tk.Frame(root, width=650, height=40, bd=5, relief='solid') fr_genre = tk.Frame(root, width=650, height=45, bd=5, relief='solid') fr_add = tk.Frame(root, width=650, height=70, bd=5, relief='solid') fr_op = tk.Frame(fr_add, width=100, height=65, bd=5, relief='solid') fr_must = tk.Frame(root, width=325, height=260, bd=5, relief='solid') fr_mlist = tk.Frame(fr_must, width=315, height=190, bd=5, relief='solid') fr_want = tk.Frame(root, width=325, height=260, bd=5, relief='solid') fr_wlist = tk.Frame(fr_want, width=315, height=190, bd=5, relief='solid') # フレームのサイズを固定 fr_title.grid_propagate(False) fr_add.grid_propagate(False) fr_op.grid_propagate(False) fr_genre.grid_propagate(False) fr_must.grid_propagate(False) fr_mlist.grid_propagate(False) fr_want.grid_propagate(False) fr_wlist.grid_propagate(False) # フレームを配置 fr_title.grid(row=0, column=0, columnspan=2, sticky=tk.EW) fr_genre.grid(row=1, column=0, columnspan=2, sticky=tk.EW) fr_add.grid(row=2, column=0, columnspan=2, sticky=tk.EW) fr_must.grid(row=3, column=0) fr_want.grid(row=3, column=1) |
図9 グローバル変数を定義するコード
|
1 2 3 4 |
btnge = 'sc' color = '#A8EEFF' la_text = '学校のTo Do' textlist = [] |
図10 ウィジェット配置用のコード
|
1 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 |
# fr_genreのウィジェット buttonsc = tk.Button(fr_genre, text="学校", state=tk.DISABLED, bg='#A8EEFF', font=font1, command=lambda:genre(buttonli, buttonsc, 'sc', la_title, label_ge, label_m, label_w)) buttonli = tk.Button(fr_genre, text="生活", state=tk.NORMAL, bg='#FFF9A8', font=font1, command=lambda:genre(buttonsc, buttonli, 'li', la_title, label_ge, label_m, label_w)) # fr_titleのウィジェット la_title = tk.Label(fr_title, text="今日のTo Do", bg=color, font=font1) # fr_addのウィジェット label_ge = tk.Label(fr_add, text=la_text, bg=color, font=font1) entry = tk.Entry(fr_add, width=30, font=font1) rad = tk.StringVar() rad.set('Must') radio_m = tk.Radiobutton(fr_op, text='Must', font=font1, value='Must', variable=rad) radio_w = tk.Radiobutton(fr_op, text='Want', font=font1, value='Want', variable=rad) button_add = tk.Button(fr_add, text="追加", font=font1, command = lambda:add(rad)) # fr_mustのウィジェット label_m = tk.Label(fr_must, text="Must", bg=color, font=font1) de_must = tk.Button(fr_must, text="削除", font=font1, command=lambda:deletion('Must')) # fr_wantのウィジェット label_w = tk.Label(fr_want, text="Want", bg=color, font=font1) de_want = tk.Button(fr_want, text="削除", font=font1, command=lambda:deletion('Want')) # ウィジェットと残りのフレームの配置 la_title.grid() buttonsc.grid(row=0, column=0) buttonli.grid(row=0, column=1) label_ge.grid(row=0, column=0) entry.grid(row=0, column=1) fr_op.grid(row=0, column=2) button_add.grid(row=0, column=3) radio_m.grid(row=0, column=0) radio_w.grid(row=1, column=0) label_m.grid() fr_mlist.grid() de_must.grid() label_w.grid() fr_wlist.grid() de_want.grid() |
図12 クラスと関数を定義するコード
|
1 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 |
class Text: def __init__(self, box, de, fr, te, ch, pr, ge): self.box = box # チェックボックスの変数 self.de = de # 完了は「fin」、未完了は「unfin」 self.fr = fr # チェックボックスの表示フレーム self.te = te # To Doの内容 self.ch = ch # チェックの有無 self.pr = pr # MustかWantか self.ge = ge # 学校か生活か def add(rad): text = tk.StringVar() text.set((entry.get())) entry.delete(0, tk.END) check = tk.StringVar() check.set('0') if rad.get() == 'Must': frame = fr_mlist priority = 'Must' else: frame = fr_wlist priority = 'Want' box = tk.Checkbutton(frame, textvariable=text, variable=check, font=font1) tex=Text(box, 'unfin' ,frame, text, check, priority, btnge) textlist.append(tex) todo() def todo(): i = 0 for t in textlist: if t.de == 'unfin': if t.ge == btnge: t.box.grid(row=i, sticky = tk.W) i += 1 def deletion(pri): for t in textlist: if t.pr == pri: if t.ge == btnge: if t.ch.get() == '1': t.de = 'fin' t.box.destroy() todo() def genre(button, buclick, gen, la_title, label_ge, label_m, label_w): button['state'] = tk.NORMAL buclick['state'] = tk.DISABLED global btnge global color global la_text btnge = gen if (gen == 'sc'): color = '#A8EEFF' la_text = '学校のTo Do' else: color = '#FFF9A8' la_text = '生活のTo Do' la_title['bg'] = color label_ge['bg'] = color label_ge['text'] = la_text label_m['bg'] = color label_w['bg'] = color # チェックボックスを破棄して再作成 for t in textlist: t.box.destroy() t.box = tk.Checkbutton(t.fr, textvariable=t.te, variable = t.ch, font=font1) todo() |

004 レポート Python用の国産GUIライブラリ公開 コード掲載
005 レポート Open Source Summit + AI_dev開催
製品レビュー 組み込み機器「ラズパイ AI HAT+」
007 NEWS FLASH
008 特集1 Streamlitで作るデータ分析Webアプリ/邑川真也 コード掲載
018 特集2 Oracleデータベース入門/宮本拓弥、百木和美、武井菜々子、北村海人
034 イベントレポート「第35回 プロコン」/塩田哲也
036 Raspberry Pi Pico W/WHで始める電子工作/米田聡
040 Pythonあれこれ/飯尾淳 コード掲載
046 Markdownを活用する/藤原由来 コード掲載
056 中小企業手作りIT化奮戦記/菅雄一
062 香川大学SLPからお届け!/原口莉奈 コード掲載
068 これだけは覚えておきたいLinuxコマンド/大津真
076 Techパズル/gori.sh
077 コラム「仕組みを作るSE」/シェル魔人
著者:末安 泰三
クジラ飛行机氏は2024年11月7日、手軽にGUIを作成可能なPythonライブラリ「TkEasyGUI」のバージョン1.0系列をリリースした。バージョン1.0系列では、複数項目を入力できるフォームダイアログが追加されるなどの機能拡充やバグ修正が施されている。
シェルスクリプトマガジン Vol.93は以下のリンク先でご購入できます。![]()
![]()
図1 個人情報を入力させるウィンドウを作成するコードの例
|
1 2 3 4 5 6 7 8 9 10 11 12 |
import TkEasyGUI as eg layout = [ [eg.Text('氏名', size=(15, 1)), eg.InputText()], [eg.Text('住所', size=(15, 1)), eg.InputText()], [eg.Text('電話番号', size=(15, 1)), eg.InputText()], [eg.Text()], [eg.Button('OK'), eg.Button('Cancel')] ] with eg.Window('個人情報を入力', layout) as window: for event in window.event_iter(): pass |
図3 フォームダイアログを表示するコードの例
|
1 2 3 4 |
iimport TkEasyGUI as eg form = eg.popup_get_form(["氏名", "住所", "電話番号"], title="個人情報の入力") |
著者:邑川 真也
「Streamlit」は、OSS(オープンソースソフトウエア)のPython向けフレームワーク(ライブラリ)です。データの可視化機能や分析機能、ダッシュボードなどを備えるWebアプリケーションを、Pythonを使って迅速かつ容易に作成できます。本特集では、さまざまなWebアプリケーションを実際に作成しながら、基本的なデータの可視化方法から、ノーコードでデータ分析を実現する方法まで、Streamlitのさまざまな活用方法を紹介します。
シェルスクリプトマガジン Vol.93は以下のリンク先でご購入できます。![]()
![]()
図2 HTMLとCSS、JavaScriptを用いて棒グラフを描くコードの例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Bar Chart Example</title> <style> .chart-container { width: 70%; margin: 0 auto; } </style> </head> <body> <div class="chart-container"> <canvas id="myBarChart"></canvas> </div> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script> var ctx = document.getElementById('myBarChart').getContext('2d'); var myBarChart = new Chart(ctx, { type: 'bar', data: { labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], datasets: [{ label: 'Sample Data', data: [65, 59, 80, 81, 56, 55, 40], backgroundColor: 'rgba(54, 162, 235, 0.2)', borderColor: 'rgba(54, 162, 235, 1)', borderWidth: 1 }] }, options: { scales: { y: { beginAtZero: true } } } }); </script> </body> </html> |
図3 Streamlitを用いて棒グラフを描くコードの例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import streamlit as st import pandas as pd import altair as alt data = pd.DataFrame({ 'Month': ['January', 'February', 'March', 'April', 'May', 'June', 'July'], 'Value': [65, 59, 80, 81, 56, 55, 40] }) chart = alt.Chart(data).mark_bar().encode( x='Month', y='Value', color=alt.value('steelblue') ) st.title('Bar Chart Example') st.altair_chart(chart, use_container_width=True) |
図6 シンプルなWebアプリケーションのコード
|
1 2 3 4 |
import streamlit as st # タイトルを表示 st.title("シェルスクリプトマガジン") |
図9 架空の株価データを生成して表示するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import streamlit as st import pandas as pd import numpy as np from datetime import datetime, timedelta # 株価データを生成(仮のデータ) dates = [datetime.today() - timedelta(days=i) for i in range(14)] dates.reverse() data = { 'Date': dates, 'Google': np.random.uniform(2700, 3000, 14), 'Apple': np.random.uniform(140, 160, 14), 'Meta': np.random.uniform(320, 350, 14), 'Amazon': np.random.uniform(3300, 3500, 14) } # データフレームに変換 df = pd.DataFrame(data) df.set_index('Date', inplace=True) # タイトルを表示 st.title("株価データの可視化") # データフレームの表示 st.dataframe(df) |
図14 「data.csv」ファイルに記述するデータ(抜粋)
|
1 2 3 4 5 6 7 |
Case ID,Company,Inquiry Summary,Status,Start Date,Severity 1,Acme Corp,大規模データセットに対するクエリの最適化について,Open,2024-05-19,Sev4 2,Globex Inc,DWHのベストプラクティスについて,Open,2024-05-26,Sev3 3,Globex Inc,データロード時のエラー,Open,2024-05-21,Sev3 4,Soylent Corp,新機能の有効化について,Open,2024-05-22,Sev2 5,Soylent Corp,レプリケートに失敗しています,Open,2024-05-22,Sev3 (略) |
図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 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 |
import streamlit as st import pandas as pd import numpy as np import matplotlib.pyplot as plt # ページ設定 st.set_page_config( page_title="サポートデスクダッシュボード", initial_sidebar_state="expanded", layout="wide" ) st.title("サポートデスクダッシュボード") # チケット一覧を表示 st.subheader("チケット一覧") # データ読み込み df = pd.read_csv("data.csv") with st.container(border=True, height=100): filter1, filter2 = st.columns(2) with filter1: # ステータスによるフィルタ status_list = df['Status'].unique() selected_status = st.multiselect("ステータス", options=status_list, default=status_list) with filter2: # 重要度によるフィルタ severity_list = df['Severity'].unique() selected_severity = st.multiselect("重要度", options=severity_list, default=severity_list) # フィルタを適用 if selected_status or selected_severity: df = df[(df['Status'].isin(selected_status) & df['Severity'].isin(selected_severity))] # データを表示 st.dataframe(df, use_container_width=True) # グラフを横並びに表示するレイアウトを作成 st.markdown("") col1, col2 = st.columns(2) # 縦棒グラフでステータスの数を表示 with col1: st.subheader("チケットごとのステータス集計") fig, ax = plt.subplots() status_counts = df["Status"].value_counts() ax.bar(status_counts.index, status_counts.values, color="skyblue") ax.set_xlabel("Status") ax.set_ylabel("Count") ax.set_title("Tickets by Status") with st.container(border=True): st.pyplot(fig) # 縦棒グラフで重要度の数を表示 with col2: st.subheader("チケットごとの重要度集計") severity_counts = df["Severity"].value_counts() fig, ax = plt.subplots() ax.bar(severity_counts.index, severity_counts.values, color="coral") ax.set_xlabel("Severity") ax.set_ylabel("Count") ax.set_title("Tickets by Severity") with st.container(border=True): st.pyplot(fig) # CSVダウンロード csv = df.to_csv(index=False) st.download_button( label="Download CSV", data=csv, file_name='sample_data.csv', mime='text/csv' ) |
図17 「config.toml」ファイルに記述するテーマ設定の例
|
1 2 3 4 5 6 |
[theme] primaryColor = "#F14143" # プライマリカラー backgroundColor = "#101216" # 背景色 secondaryBackgroundColor = "#191B20" # セカンダリ背景色 textColor = "#F9F9F9" # テキストカラー font = "sans serif" # フォント |
図18 「pygw_test.py」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
from pygwalker.api.streamlit import StreamlitRenderer import pandas as pd import streamlit as st # Streamlitの画面幅を調整 st.set_page_config( page_title="Use Pygwalker In Streamlit", layout="wide" ) # データの取得 df = pd.read_csv("https://kanaries-app.s3.ap-northeast-1.amazonaws.com/public-datasets/bike_sharing_dc.csv") # PYGWalkerによる可視化 pyg_app = StreamlitRenderer(df) pyg_app.explorer() |
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第23回では、機械学習ライブラリ「scikit-learn」で何ができるのかを紹介した上で、データを二つのクラスに分類するPythonプログラムを作成します。
シェルスクリプトマガジン Vol.93は以下のリンク先でご購入できます。![]()
![]()
図9 テストデータを作成するPythonコード
|
1 2 3 4 5 6 7 8 9 10 11 12 |
import pandas as pd from sklearn.datasets import make_classification (data, label) = make_classification(n_samples=1000, n_features=2, n_classes=2, n_informative=2, n_redundant=0, random_state=133) df = pd.DataFrame({'x1':[d[0] for d in data], 'x2':[d[1] for d in data], 'label':label}) df.plot.scatter(x='x1', y='x2', c=df['label'].map({0:'blue', 1:'red'}), s=5) |
図10 LinearSVCを用いて学習と分類をするPythonコード
|
1 2 3 4 5 |
from sklearn.svm import LinearSVC from sklearn.metrics import accuracy_score clf = LinearSVC(random_state=0, dual='auto', tol=1e-5) clf.fit(data, label) accuracy_score(clf.predict(data), label) |
図11 分離面を描画するPythonコード
|
1 2 |
from mlxtend.plotting import plot_decision_regions plot_decision_regions(data, label, clf=clf) |
図13 NuSVCを用いて学習と分類をするPythonコード
|
1 2 3 4 |
from sklearn.svm import NuSVC clf = NuSVC(nu=0.1, random_state=0) clf.fit(data, label) accuracy_score(clf.predict(data), label) |
図15 SVCを用いて学習と分類をするPythonコード(その1)
|
1 2 3 4 |
from sklearn.svm import SVC clf = SVC(gamma=0.1, random_state=0) clf.fit(data, label) accuracy_score(clf.predict(data), label) |
図17 SVCを用いて学習と分類をするPythonコード(その2)
|
1 2 3 4 |
from sklearn.svm import SVC clf = SVC(gamma=0.1, random_state=0) clf.fit(data, label) accuracy_score(clf.predict(data), label) |
著者:藤原 由来
本連載では文書の装飾・構造付けを手軽に行える記法であるMarkdownを用いて、さまざまな文書や成果物を作成する方法を紹介します。今回は、プレゼンテーション用のスライドを作成するためのプレゼンテーションアプリ「Marp」について解説します。
シェルスクリプトマガジン Vol.93は以下のリンク先でご購入できます。![]()
![]()
図1 Marp用にMarkdownで記述したテキストの例
|
1 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 |
--- marp: true --- # 美味しいカレーの作り方 シェル花子 --- # 材料 - 玉ねぎ - にんじん - じゃがいも - 肉 - カレールー - 水 --- # 作り方 1. 材料を切る 2. 肉を炒める 3. 野菜を加える 4. 水を加えて煮る 5. カレールーを入れる |
図8 見出しレベル1をスライドの区切りにしたMarkdown形式のテキスト例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
--- marp: true headingDivider: 1 --- # 美味しいカレーの作り方 ## シェル花子 # 材料 ## ここに ### 材料を書く # 作り方 ## 適当に作る |
図11 スライドの背景や文字色を変更したMarkdown形式のテキスト例
|
1 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 |
--- marp: true --- # 通常のスライド --- <!-- _backgroundColor: black _color: white --> # 背景が黒く文字が白いスライド --- <!-- _backgroundImage: "linear-gradient(to bottom, #67b8e3, #0288d1)" _color: white --> # 背景が青いグラデーションのスライド --- <!-- _backgroundImage: url('cat1.png') --> # 背景が画像のスライド |
図13 背景画像表示とぼかしフィルタ適用のMarkdown形式のテキスト例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
--- marp: true --- # 背景画像を表示する  --- # 背景画像にぼかしを入れる  |
図15 背景画像の横並びと縦並びのMarkdown形式のテキスト例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
--- marp: true --- # 背景に横並びに配置    --- # 背景に縦並びに配置    |
図20 ChatGPTが生成したMarkdown形式のテキスト
|
1 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 |
--- marp: true theme: default paginate: true backgroundColor: #f0f0f0 --- # 知られざるインドカレー ### シェル花子 --- # 目次 1. 北インド: パニール・バターマサラ 2. 南インド: チェッティナード・チキンカレー 3. 西インド: ゴア・フィッシュカレー 4. 東インド: コサ・マンシャ 5. 中央インド: ダールバーフライ 6. 北東インド: アクホニカレー --- # 1. 北インド: パニール・バターマサラ - クリーミーで濃厚なグレイビー - トマト、バター、クリームを使用 - パニール(カッテージチーズ)入り - パンジャーブ地方で有名 - ナンやバスマティライスと食べる --- (略) |
著者:原口 莉奈
私は最近、RPG(Role-Playing Game)の戦闘シーンをC++言語で作成しました。作成には、無償で利用できるゲーム開発用ライブラリ「DXライブラリ」(https://dxlib.xsrv.jp/)を用いています。今回は、その一部であるコマンド選択画面の実装について紹介します。
シェルスクリプトマガジン Vol.93は以下のリンク先でご購入できます。![]()
![]()
図2 ビルド用のバッチファイル「build.bat」に記述する内容
|
1 2 3 4 5 6 |
g++ -c Main.cpp -DDX_GCC_COMPILE -I"DxLib" g++ -o game.exe Main.o -L"DxLib" -lgcc -lDxLib -lDxUseCLib ^ -lDxDrawFunc -ljpeg -lpng -lzlib -ltiff -ltheora_static ^ -lvorbis_static -lvorbisfile_static -logg_static ^ -lbulletdynamics -lbulletcollision -lbulletmath -lopusfile ^ -lopus -lsilk_common -lcelt |
図3 「Main.cpp」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#define WIN_X 256 #define WIN_Y 224 #include <DxLib.h> int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR IpCmdLine, _In_ int nShowCmd) { ChangeWindowMode(TRUE); DxLib_Init(); // ウィンドウ初期化 SetWindowText("game"); // タイトル SetGraphMode(WIN_X, WIN_Y, 32); // 解像度と色深度 SetBackgroundColor(255, 255, 255); // 背景色 WaitKey(); DxLib_End(); return 0; } |
図5 「Main.cpp」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#define WIN_X 256 #define WIN_Y 224 #include <DxLib.h> #include "Status.h" #include "Battle.h" int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR IpCmdLine, _In_ int nShowCmd) { ChangeWindowMode(TRUE); DxLib_Init(); SetWindowText("game"); SetGraphMode(WIN_X, WIN_Y, 32); SetBackgroundColor(255, 255, 255); SetDrawScreen(DX_SCREEN_BACK); // 裏画面描画 SetWindowSize(1024, 960); // ウィンドウサイズ Battle.materialLoad(); while (ProcessMessage() == 0) { SetBackgroundColor(0, 0, 0); Battle.BattleDraw(); } DxLib_End(); return 0; } |
図6 「Status.h」ファイルに記述するコード
|
1 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 |
class BATTLE { public: struct Status { char name[50]; int HP, ATK, DEF; //体力、攻撃力、防御力 }; struct Status player = { "player",30, 10, 5 }; struct Status villain = { "villain",30, 7, 5 }; struct Status* Player = &player; const char* str[4] = { "たたかう", "まもる", "どうぐ", "にげる" }; const char* item[1] = { "回復" }; int firstsym = 80; int sym = firstsym; int sym_limit = firstsym + (4 - 1) * 17; int symbol = -1, symnext = 0; int pattern = 1; int firstarrow = 80; int arrow = firstarrow; int limit; // firstarrow + (num - 1) * 17 int a_posi = -1; int re = 0; int black, white, ms; void materialLoad(); void BattleDraw(); void one(); void two(); void Choices(int x, const char* str[], int n); private: }; |
図7 「Battle.h」ファイルに記述するコード
|
1 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 |
BATTLE Battle; void BATTLE::materialLoad() { black = GetColor(0, 0, 0); white = GetColor(255, 255, 255); ms = CreateFontToHandle("MS ゴシック", 10, 6, DX_FONTTYPE_ANTIALIASING_4X4); } void BATTLE::Choices(int x, const char* str[], int n) { int w = 0; for (int i = 0; i < n; i++) { DrawBox(x, firstarrow + w, x + 80, firstarrow + 16 + w, white, TRUE); DrawFormatStringToHandle(x + 5, firstarrow + 5 + w, black, ms, str[i]); w += 17; } } // 初期表示 void BATTLE::one() { sym = firstsym; pattern = 2; } // 矢印の移動 void BATTLE::two() { if (symnext == 0) { switch (WaitKey()) { case KEY_INPUT_DOWN: if (sym != sym_limit) sym += 17; break; case KEY_INPUT_UP: if (sym != firstsym) sym -= 17; break; case KEY_INPUT_ESCAPE: DxLib_End(); break; } if (symbol != -1) symnext++; } } void BATTLE::BattleDraw() { SetDrawScreen(DX_SCREEN_BACK); ClearDrawScreen(); switch(pattern): if (pattern == 1) one(); else if (pattern == 2) two(); Choices(160, str, 4); DrawFormatStringToHandle(144, sym, white, ms, "→"); ScreenFlip(); } |
図9 「Status.h」ファイルにHpDraw()関数の記述を追加
|
1 2 3 4 |
(略) void HpDraw(); void BattleDraw(); (略) |
図10 「Battle.h」ファイルのBattleDraw()関数定義部分をこのように書き換える
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
(略)void BATTLE::HpDraw() { DrawBox(180, 160, 250, 184, white, FALSE); DrawBox(180, 186, 250, 210, white, FALSE); DrawFormatStringToHandle(182, 162, white, ms, "villain\nHP: %d", villain.HP); DrawFormatStringToHandle(182, 188, white, ms, "player\nHP:%d", player.HP); } void BATTLE::BattleDraw() { SetDrawScreen(DX_SCREEN_BACK); ClearDrawScreen(); switch(pattern): if (pattern == 1) one(); else if (pattern == 2) two(); Choices(160, str, 4); DrawFormatStringToHandle(144, sym, white, ms, "→"); HpDraw(); ScreenFlip(); } |

004 レポート 仮想化ソフト「VirtualBox」の新版リリース
005 レポート マイコンボード「Raspberry Pi Pico 2」登場
006 製品レビュー 組み込み機器「CH32V003ボード」
007 NEWS FLASH
008 特集1 古いRaspberry Piを復活させる/麻生二郎
016 特集2 統合運用管理ソフトウエア Hinemos入門/倉田晃次、加藤達也
026 特別企画 イノシシ撃退機を作る ソフト作成編/米田聡 コード掲載
036 特別企画 Rocky Linux 9のインストール/麻生二郎
042 Pythonあれこれ/飯尾淳 コード掲載
048 Markdownを活用する/藤原由来 コード掲載
058 中小企業手作りIT化奮戦記/菅雄一
062 香川大学SLPからお届け!/池内稜來斗 コード掲載
068 これだけは覚えておきたいLinuxコマンド/大津真
078 Techパズル/gori.sh
079 コラム「SEの働き方の新しい仕組みを作る」/シェル魔人
著者:米田 聡
「Interface」「CQ ham radio」「トランジスタ技術」などの雑誌を出版するCQ出版社の電子部品セット「イノシシ撃退機部品セットVer.1」を組み立て、ソフトウエアに一工夫を加えてイノシシ撃退機を作ります。今回は完成したハードウエア向けにソフトウエアを作成します。
シェルスクリプトマガジン Vol.92は以下のリンク先でご購入できます。![]()
![]()
図18 PWM機能の初期化
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
(略) #define PWM_PIN 20 (略) #define PWM_SLICE_NUM pwm_gpio_to_slice_num(PWM_PIN) (略) void pwm_gpio_init() { (略) gpio_set_function(PWM_PIN, GPIO_FUNC_PWM); pwm_set_wrap(PWM_SLICE_NUM, 4095); pwm_set_clkdiv(PWM_SLICE_NUM, 2); pwm_set_irq_enabled(PWM_SLICE_NUM, true); irq_set_exclusive_handler(PWM_IRQ_WRAP, on_pwm_wrap); irq_set_enabled(PWM_IRQ_WRAP, true); (略) } (略) |
図20 割り込みハンドラ
|
1 2 3 4 5 6 7 8 9 10 11 |
void on_pwm_wrap() { pwm_clear_irq(PWM_SLICE_NUM); (略) uint16_t duty = convert_flash_data_to_duty(spi_rx_buf[flash_data_index], spi_rx_buf[flash_data_index + 1]); pwm_set_chan_level(PWM_SLICE_NUM, PWM_CHAN_A, duty); flash_data_index += 2; if (flash_data_index >= 256) { flash_data_index = 0; } (略) } |
図21 ダブルバッファを使用する修正
|
1 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 |
■34~37行目の修正コード #ifdef FLASH_DATA_TO_DUTY // ダブルバッファ化 uint8_t spi_rx_buf[2][256]; volatile uint32_t flash_data_index = 0; #endif ■45~54行目の修正コード // バッファ選択 volatile uint32_t buf_flip = 0; void on_pwm_wrap() { pwm_clear_irq(PWM_SLICE_NUM); #ifdef FLASH_DATA_TO_DUTY uint16_t duty = convert_flash_data_to_duty(spi_rx_buf[buf_flip][flash_data_index], spi_rx_buf[buf_flip][flash_data_index + 1]); pwm_set_chan_level(PWM_SLICE_NUM, PWM_CHAN_A, duty); flash_data_index += 2; if (flash_data_index >= 256) { // バッファ切り替え buf_flip ^= 1; flash_data_index = 0; } } ■87~105行目の修正コード void play_sound() { // バッファ切り替え(ローカル) int flip = 0; flash_data_index = 0; buf_flip = 0; pwm_set_enabled(PWM_SLICE_NUM, true); //mu0918 for(uint32_t address = 0; address < FLASH_SIZE; address += 256) { gpio_put(PICO_DEFAULT_SPI_SSEL_PIN, 0); // Set the CS pin to active LOW // Send the read command uint8_t spi_tx_buf[] = {0x03, (address >> 16) & 0xFF, (address >> 8) & 0xFF, address & 0xFF}; spi_write_blocking(spi, spi_tx_buf, 4); // Read the data spi_read_blocking(spi, 0, spi_rx_buf[flip], 256); // バッファ切り替え flip ^= 1; gpio_put(PICO_DEFAULT_SPI_SSEL_PIN, 1); // Set the CS pin to non-active HIGH } pwm_set_enabled(PWM_SLICE_NUM, false); //mu0918 } |
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第22回では、機械学習ライブラリ「PyTorch」を使って、画像内の、歩行者が写っている領域を自動認識するPythonプログラムを作成します。
シェルスクリプトマガジン Vol.92は以下のリンク先でご購入できます。![]()
![]()
図3 47番のデータを確認するPythonコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import matplotlib.pyplot as plt from torchvision.io import read_image image =read_image( "data/PennFudanPed/PNGImages/FudanPed00047.png") mask = read_image( "data/PennFudanPed/PedMasks/FudanPed00047_mask.png") plt.figure(figsize=(16, 8)) plt.subplot(121) plt.title("Image") plt.imshow(image.permute(1, 2, 0)) plt.subplot(122) plt.title("Mask") plt.imshow(mask.permute(1, 2, 0)) |
図5 PennFudanDataSetクラスを定義する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 |
import os import torch from torchvision.io import read_image from torchvision.ops.boxes import masks_to_boxes from torchvision import tv_tensors from torchvision.transforms.v2 import functional as F class PennFudanDataset(torch.utils.data.Dataset): def __init__(self, root, transforms): self.root = root self.transforms = transforms # イメージデータとマスクデータをロードし、ソートして並べておく self.imgs = \ list(sorted(os.listdir(os.path.join(root, "PNGImages")))) self.masks = \ list(sorted(os.listdir(os.path.join(root, "PedMasks")))) def __getitem__(self, idx): # イメージとマスクのロード img_path = \ os.path.join(self.root, "PNGImages", self.imgs[idx]) mask_path = \ os.path.join(self.root, "PedMasks", self.masks[idx]) img = read_image(img_path) mask = read_image(mask_path) # インスタンスは色別にエンコードされていて…… obj_ids = torch.unique(mask) # 最初のIDは背景色なので削除 obj_ids = obj_ids[1:] num_objs = len(obj_ids) # 色別にエンコードされているマスクを2値マスクに分ける masks = \ (mask == obj_ids[:, None, None]).to(dtype=torch.uint8) # マスクデータに対してバウンディングボックスを求める boxes = masks_to_boxes(masks) labels = torch.ones((num_objs,), dtype=torch.int64) image_id = idx area = (boxes[:, 3] - boxes[:, 1]) * (boxes[:, 2] - boxes[:, 0]) # 群衆でないと仮定 iscrowd = torch.zeros((num_objs,), dtype=torch.int64) # tv_tensorsイメージに変換する img = tv_tensors.Image(img) target = {} target["boxes"] = \ tv_tensors.BoundingBoxes(boxes, format="XYXY", canvas_size=F.get_size(img)) target["masks"] = tv_tensors.Mask(masks) target["labels"] = labels target["image_id"] = image_id target["area"] = area target["iscrowd"] = iscrowd if self.transforms is not None: img, target = self.transforms(img, target) return img, target def __len__(self): return len(self.imgs) |
図6 get_model_instance_segmentation()関数を定義する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 torchvision from torchvision.models.detection.faster_rcnn \ import FastRCNNPredictor from torchvision.models.detection.mask_rcnn \ import MaskRCNNPredictor def get_model_instance_segmentation(num_classes): # COCOで事前学習済みのモデルデータをロードする model = torchvision.models.detection.maskrcnn_resnet50_fpn( weights="DEFAULT") # 入力特徴量 in_features = \ model.roi_heads.box_predictor.cls_score.in_features # num_classesで指定するクラスの分類器をセット(今回は2クラス) model.roi_heads.box_predictor = \ FastRCNNPredictor(in_features, num_classes) # マスク判別器も同様に設定 in_features_mask = \ model.roi_heads.mask_predictor.conv5_mask.in_channels hidden_layer = 256 model.roi_heads.mask_predictor = MaskRCNNPredictor( in_features_mask, hidden_layer, num_classes ) return model |
図7 get_transform()関数を定義するPythonコード
|
1 2 3 4 5 6 7 8 9 |
from torchvision.transforms import v2 as T def get_transform(train): transforms = [] if train: transforms.append(T.RandomHorizontalFlip(0.5)) transforms.append(T.ToDtype(torch.float, scale=True)) transforms.append(T.ToPureTensor()) return T.Compose(transforms) |
図8 追加学習の準備をするための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 |
import utils from engine import train_one_epoch, evaluate device = torch.device('cuda') \ if torch.cuda.is_available() else torch.device('cpu') # 背景と人物の2クラス判別器を作成する num_classes = 2 # データセットはすでにロード済みのものを用いる dataset = \ PennFudanDataset('data/PennFudanPed', get_transform(train=True)) dataset_test = \ PennFudanDataset('data/PennFudanPed', get_transform(train=False)) # データセットを学習用とテスト用に分ける indices = torch.randperm(len(dataset)).tolist() dataset = torch.utils.data.Subset(dataset, indices[:-50]) dataset_test = torch.utils.data.Subset( dataset_test, indices[-50:]) # 学習と評価用のデータローダを定義 data_loader = torch.utils.data.DataLoader( dataset, batch_size=2, shuffle=True, num_workers=4, collate_fn=utils.collate_fn ) data_loader_test = torch.utils.data.DataLoader( dataset_test, batch_size=1, shuffle=False, num_workers=4, collate_fn=utils.collate_fn ) # 先ほど定義したヘルパーファンクションを用いてモデルを用意 model = get_model_instance_segmentation(num_classes) # モデルをデバイスに結び付ける model.to(device) # 最適化器を作成 params = [p for p in model.parameters() if p.requires_grad] optimizer = torch.optim.SGD( params, lr=0.005, momentum=0.9, weight_decay=0.0005 ) # 学習レートのスケジューラを設定 lr_scheduler = torch.optim.lr_scheduler.StepLR( optimizer, step_size=3, gamma=0.1 ) |
図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 |
import matplotlib.pyplot as plt from torchvision.utils \ import draw_bounding_boxes, draw_segmentation_masks image = \ read_image("data/PennFudanPed/PNGImages/FudanPed00047.png") eval_transform = get_transform(train=False) model.eval() with torch.no_grad(): x = eval_transform(image) # RGBA -> RGBにコンバートしてデバイスにひも付ける x = x[:3, ...].to(device) predictions = model([x, ]) pred = predictions[0] image = (255.0 * (image - image.min()) / (image.max() - image.min())).to(torch.uint8) image = image[:3, ...] pred_labels = [f"pedestrian: {score:.3f}" for label, \ score in zip(pred["labels"], pred["scores"])] pred_boxes = pred["boxes"].long() output_image = draw_bounding_boxes(image, pred_boxes, pred_labels, colors="red") masks = (pred["masks"] > 0.7).squeeze(1) output_image = draw_segmentation_masks(output_image, masks, alpha=0.5, colors="blue") plt.figure(figsize=(12, 12)) plt.imshow(output_image.permute(1, 2, 0)) |
図11 Penn-Fudanデータセットを用いて追加学習するPythonコード
|
1 2 3 4 5 6 7 8 9 10 |
# 2エポックだけ学習させてみる num_epochs = 2 for epoch in range(num_epochs): # 10回ごとに表示させながら1エポックの学習を実行 train_one_epoch(model, optimizer, data_loader, device, epoch, print_freq=10) # 学習レートをアップデート lr_scheduler.step() # テストデータで評価 evaluate(model, data_loader_test, device=device) |
著者:藤原 由来
本連載では文書の装飾・構造付けを手軽に行える記法であるMarkdownを用いて、さまざまな文書や成果物を作成する方法を紹介します。今回は前回に引き続き、Markdownを用いて書籍を制作できる組版アプリ「Vivliostyle」について解説します。特に、技術同人誌の制作方法と、印刷所へ入稿する際の注意
点について説明します。
シェルスクリプトマガジン Vol.92は以下のリンク先でご購入できます。![]()
![]()
図7 書き換えたVivliostyleの設定ファイル
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
module.exports = { title: 'sample-book-kagakunofushigi', author: 'アンリイ・ファブル(大杉栄、伊藤野枝訳)', language: 'ja', size: 'JIS-B5', theme: '@vivliostyle/theme-techbook@^1.0.1', entryContext: './manuscripts', entry: [ 'index.md', '01.md', '02.md', '03.md', '04.md', '05.md', '06.md', '07.md', '08.md', '09.md', 'colophon.md', ], workspaceDir: '.vivliostyle', } |
図8 扉のindex.mdファイル
|
1 2 3 4 |
# 科学の不思議 アンリイ・ファブル(大杉栄、伊藤野枝訳) © 某サークル, 20xx |
図9 奥付のcolophon.mdファイル
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
## 科学の不思議 20xx年x月x日 初版発行 ---------------- - 発行 某サークル - 著者 アンリイ・ファブル - 翻訳 大杉栄、伊藤野枝 - 編集 シェル花子 - 連絡先 foo@example.com - 印刷 サンプル印刷 ---------------- 底本は青空文庫から転載・改変しました。 - ファーブル ジャン・アンリ『科学の不思議』(大杉 栄、伊藤 野枝 訳) © 某サークル, 20xx |
図11 my-theme.cssファイル内のCSSを反映する設定
|
1 2 3 4 |
theme: [ '@vivliostyle/theme-techbook@^1.0.1', 'themes/my-theme.css', ], |
図12 扉と奥付用のCSS
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/* 扉 */ section#title-page { display: flex; justify-content: center; align-items: center; text-align: center; height: 50vh; margin: 0; } /* 奥付 */ section#colophon { position: relative; float-reference: page; float: bottom; margin-bottom: 0; } |
図13 扉のindex.mdファイルの変更
|
1 2 3 4 5 6 7 8 9 |
<section id="title-page"> # 科学の不思議 アンリイ・ファブル(大杉栄、伊藤野枝訳) © 某サークル, 20xx </section> |
図14 奥付のcolophon.mdファイルの変更
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<section id="colophon" role="doc-colophon"> ## 科学の不思議 20xx年x月x日 初版発行 ---------------- - 発行 某サークル - 著者 アンリイ・ファブル - 翻訳 大杉栄、伊藤野枝 - 編集 シェル花子 - 連絡先 foo@example.com - 印刷 サンプル印刷 ---------------- 底本は青空文庫から転載・改変しました。 - ファーブル ジャン・アンリ『科学の不思議』(大杉 栄、伊藤 野枝 訳) © 某サークル, 20xx </section> |
図17 目次を自動設定する設定
|
1 2 3 4 5 6 7 8 9 10 11 |
(略) entryContext: './manuscripts', toc: { htmlPath: 'toc.html', title: '目次', }, entry: [ 'index.md', { rel: 'contents' }, '01.md', (略) |
著者:池内稜來斗
各都道府県の都道府県庁所在地を尋ねるクイズを出題するWebサイトをPHPで作成しました。今回は、そのWebサイトの概要や作成手順などを紹介します。PHPの実行環境や、各種サーバーソフトウエアは、「XAMPP」(https://www.apachefriends.org/)を使ってインストールします。
シェルスクリプトマガジン Vol.92は以下のリンク先でご購入できます。![]()
![]()
図2 「db.sql」ファイルに記述するSQL文
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
use pref_capitals_quiz; insert into pref_capitals (id, name_p, name_c, image_p) values (1, '北海道', '札幌市', 'img/hokkaidou.png'), (2, '青森県', '青森市', 'img/aomori.png'), (3, '岩手県', '盛岡市', 'img/iwate.png'), (4, '秋田県', '秋田市', 'img/akita.png'), (5, '宮城県', '仙台市', 'img/miyagi.png'), (6, '山形県', '山形市', 'img/yamagata.png'), (7, '福島県', '福島市', 'img/fukushima.png'), (8, '茨城県', '水戸市', 'img/ibaraki.png'), (9, '栃木県', '宇都宮市', 'img/tochigi.png'), (10, '群馬県', '前橋市', 'img/gunma.png'), (11, '埼玉県', 'さいたま市', 'img/saitama.png'), (12, '千葉県', '千葉市', 'img/chiba.png'), (13, '東京都', '東京', 'img/tokyo.png'), (略) (47, '沖縄県', '那覇市', 'img/okinawa.png'); commit; |
図3 「index.html」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<html lang=ja> <head> <meta charset="utf-8"> <title>都道府県庁所在地クイズ!!</title> </head> <body> <h1>都道府県庁所在地クイズ!!</h1> <p>表示された都道府県の都道府県庁所在地を回答してください。<br> 東京都の場合は、区名と地域名のどちらで回答しても構いません。<br> すべての都道府県について回答したら終了です。 </p> <button type="button" onclick="location.href='question.php'"> クイズを始める </button> </body> </html> |
図4 「question.php」ファイルに記述するコード
|
1 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 |
<?php session_start(); if (!isset($_SESSION['times'])) { $_SESSION['times'] = 0; $_SESSION['right'] = 0; } if ($_SESSION['times'] == 0) { $r = new \Random\Randomizer(); $order = $r->shuffleArray(range(1, 47)); $_SESSION['order'] = $order; } $order = $_SESSION['order']; $times = $_SESSION['times']; $pref = $order[$times]; ?> <html lang=ja> <head> <meta charset="UTF-8"> <title>都道府県庁所在地クイズ!!</title> </head> <body> <?php $link = mysqli_connect('localhost','root','', 'pref_capitals_quiz'); mysqli_set_charset($link,'utf8'); $sql = "SELECT id, name_p, name_c, image_p " . "FROM pref_capitals WHERE id = $pref"; $result = mysqli_query($link, $sql); $row = mysqli_fetch_assoc($result); $path = $row['image_p']; echo "<br>"; echo "問題", $times + 1; echo '<img src="' . $path . '" alt="' . $row['name_p'] . 'の地図">'; print(' '.$row['name_p']); ?> <form method="POST" class="form" action="answer.php"> <input type="text" name="Answer" class="input"> <p> <input type="submit" value="回答" class="submit"> </p> </form> </body> </html> |
図5 「answer.php」ファイルに記述するコード
|
1 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 |
<?php session_start(); $times = $_SESSION['times']; $order = $_SESSION['order']; $right = $_SESSION['right']; $pref = $order[$times]; ?> <html lang=ja> <head> <meta charset="UTF-8"> <title>都道府県庁所在地クイズ!!</title> </head> <body> <?php $received_Answer = $_POST['Answer']; $link = mysqli_connect('localhost','root','', 'pref_capitals_quiz'); mysqli_set_charset($link,'utf8'); $sql = "SELECT id, name_p, name_c, image_p " . "FROM pref_capitals WHERE id = $pref"; $result = mysqli_query($link, $sql); $row = mysqli_fetch_assoc($result); // 東京都かどうか if ($row['id'] == 13) { if (($received_Answer == $row['name_c']) || ($received_Answer == '新宿区')) { echo "正解\n"; $right++; } } else if (($received_Answer == $row['name_c']) || (($received_Answer . "市") == $row['name_c'])) { echo "正解\n"; $right++; } else { echo "不正解\n"; } $_SESSION['times'] = ++$times; $_SESSION['right'] = $right; echo "<br>"; echo "問題{$times} {$row['name_p']} の答え"; echo "<br>"; echo $row['name_c']; if ($times != 47) { echo ' <form method="POST" class="form" action="question.php"> <button type="submit">次の問題</button> </form>'; } ?> <form method="POST" class="form" action="result.php"> <button type="submit">回答終了</button> </form> </body> </html> |
図6 「result.php」ファイルに記述するコード
|
1 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 |
<?php session_start(); if (isset($_POST['reset'])) { $_SESSION['times'] = 0; $_SESSION['right'] = 0; header("Location: index.html"); } ?> <html lang=ja> <head> <meta charset="UTF-8"> <title>都道府県庁所在地クイズ!!</title> </head> <body> <?php echo "今回の正答数は<br>"; echo $_SESSION['times'], "問中", $_SESSION['right'], "問"; echo "<br>"; echo "お疲れさまでした<br>"; ?> <form method="POST" action="result.php"> <input type="submit" value="トップページに戻る" name="reset"> </form> </body> </html> |
記事中で作成したクイズWebサイト用のコードや、データベースへのデータ入力用のファイルをまとめたアーカイブファイルがここからダウンロードできます。
情報は随時更新致します。

004 レポート 統合開発環境「Theia IDE」正式版リリース
005 レポート サイバーエージェントがVLM、新LLM公開
006 製品レビュー パソコン「ASUS Vivobook S 15 S5507QA」
007 NEWS FLASH
008 特集1 Oracle Cloud Infrastructure入門/福島耕平、中村峻大
020 特集2 ChatGPTで手軽に実現/佐藤秀輔 コード掲載
028 特別企画 イノシシ撃退機を作る ハード製作編/米田聡
036 Pythonあれこれ/飯尾淳 コード掲載
042 Markdownを活用する/藤原由来 コード掲載
052 香川大学SLPからお届け!/佐藤楓真 コード掲載
058 中小企業手作りIT化奮戦記/菅雄一
064 これだけは覚えておきたいLinuxコマンド/大津真 コード掲載
073 ユニケージレポート/田中湧也
076 Techパズル/gori.sh
077 コラム「要求工学を取り入れる」/シェル魔人
著者:佐藤 秀輔
対話型AIサービス「ChatGPT」が注目を集めています。特に、2023年3月に登場した「GPT-4」という大規模言語モデル(LLM)を利用可能になってからは自然言語の理解力が飛躍的に向上し、指示に基づいて、さまざまな処理を自動化できるようになっています。本特集では、ChatGPTを個人で活用する例として、家計簿レビューをさせる方法と、API(Application Programming Interface)を利用してWebスクレイピングをさせる方法を紹介します。
シェルスクリプトマガジン Vol.91は以下のリンク先でご購入できます。![]()
![]()
図1 ランダムな家計簿データを生成するPythonスクリプト
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import datetime import random itemlist = ['食料品', '外食費', '日用品', '家財道具', '交通費', '電気代', '水道代', 'ガス代', 'ガソリン代', 'その他'] for i in range(366): basedate = datetime.datetime(2024, 1, 1) date = basedate + datetime.timedelta(days=i) item = itemlist[random.randrange(10)] amount = random.randrange(100) * 100 print(f"{date.date()},{date.time()},{item},{amount}") |
図10 Webスクレイピング対象となるWebページの例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>サンプル商品リスト</title> </head> <body> <h1>商品リスト</h1> <div class="product" id="product1"> <h2>商品名:スニーカー</h2> <p>価格:\5000</p> <p>評価:4.5 星</p> </div> <div class="product" id="product2"> <h2>商品名:バックパック</h2> <p>価格:\8000</p> <p>評価:4.0 星</p> </div> <div class="product" id="product3"> <h2>商品名:水筒</h2> <p>価格:\1500</p> <p>評価:4.8 星</p> </div> </body> </html> |
図11 Webスクレイピング用の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 json from bs4 import BeautifulSoup # HTMLファイルを開く with open('sample.html', 'r', encoding='utf-8') as file: html_content = file.read() # BeautifulSoupオブジェクトを作成 soup = BeautifulSoup(html_content, 'lxml') # 商品情報を抽出 products = soup.find_all('div', class_='product') product_list = [] for product in products: product_name = product.find('h2').text.replace('商品名:', '').strip() price = product.find_all('p')[0].text.replace('価格:', '').strip() rating = product.find_all('p')[1].text.replace('評価:', '').strip() # 各商品情報を辞書として追加 product_list.append({ '商品名': product_name, '価格': price, '評価': rating }) # JSON形式で結果を出力 json_output = json.dumps(product_list, ensure_ascii=False, indent=4) print(json_output) |
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第21回では、「ハノイの塔」の解法「GDHP(Gniibe Distributed Hanoi Protocol)」の手順をアニメーションで表示するPythonアプリを作成します。
シェルスクリプトマガジン Vol.91は以下のリンク先でご購入できます。![]()
![]()
図2 GDHPの手順をアニメーションで表示するJavaScriptコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
const COLORS = [ 'crimson', 'forestgreen', 'yellow', 'royalblue', 'saddlebrown', 'hotpink', 'darkorange', 'darkmagenta' ]; const NUM_OF_DISKS = COLORS.length; const BASE_LENGTH = 200; const C_WIDTH = 3.732 * BASE_LENGTH; const C_HEIGHT = 3.500 * BASE_LENGTH; const DISK_R = 0.9 * BASE_LENGTH; const POLE_R = 15; const POSITIONS = { 'Source' : [0.268, 0.714], 'Auxiliary' : [0.500, 0.286], 'Destination' : [0.732, 0.714] }; const FLASHING_COUNTER = 20; const STEPS = 30; class Vector { constructor(x,y) { this.x = x; this.y = y; } } class Position extends Vector { constructor(x,y) { super(x,y); } move(vec) { this.x += vec.x; this.y += vec.y; } } class Disk { constructor(level) { this.level = level; this.color = COLORS[level]; this.r = (DISK_R-POLE_R)*(NUM_OF_DISKS-level)/NUM_OF_DISKS + POLE_R; } } class MovingDisk extends Disk { constructor(level,from,to) { super(level); [sx,sy] = [from.pos.x,from.pos.y]; [dx,dy] = [to.pos.x, to.pos.y]; this.pos = new Position(sx,sy); this.mvec = new Vector((dx-sx)/STEPS,(dy-sy)/STEPS); this.move_ctr = 0; this.from = from; this.to = to; } step_forward() { this.pos.move(this.mvec); this.move_ctr++; } finish_p() { var ret_flag = false; if (ret_flag = (this.move_ctr == STEPS)) { this.to.disks.push(new Disk(this.level)); } return ret_flag; } } class Tower { constructor(name, disks, direction=null) { this.name = name; this.disks = []; for (var i = 0; i < disks; i++) { this.disks.push(new Disk(i)); } this.direction = direction; this.moving = false; this.flash_ctr = 0; } get toplevel() { var l = this.disks.length; // '-1' means there is no disk. return (l > 0) ? this.disks[l-1].level : -1; } } var src = new Tower('Source', NUM_OF_DISKS); var aux = new Tower('Auxiliary', 0, src); var dst = new Tower('Destination', 0, src); // In the case of NUM_OF_DISKS is odd, // the src must face the src. // Otherwise, the src faces the aux. src.direction = (COLORS.length % 2 == 1) ? dst : aux; // the reference to moving disk is stored to this variable. var moving_disk = null; function setup() { createCanvas(C_WIDTH, C_HEIGHT); frameRate(30); [src,aux,dst].forEach(function(t) { [rx,ry] = POSITIONS[t.name]; t.pos = new Position(rx * C_WIDTH, ry * C_HEIGHT); }) } function base_drawing() { background('beige'); [src,aux,dst].forEach(function(t) { // draw disks t.disks.forEach(function(d) { stroke('black'); fill(d.color); ellipse(t.pos.x,t.pos.y,2*d.r); }) // draw a pole stroke('brown'); fill(t.moving & (t.flash_ctr < FLASHING_COUNTER/2) ? 'gold' : 'white'); ellipse(t.pos.x,t.pos.y,2*POLE_R); // draw a direction stroke('navy'); [sx, sy] = [t.pos.x, t.pos.y]; [dx, dy] = [t.direction.pos.x, t.direction.pos.y]; r = POLE_R / Math.sqrt((dx-sx)*(dx-sx)+(dy-sy)*(dy-sy)); [dx, dy] = [(dx-sx)*r+sx, (dy-sy)*r+sy]; line(sx,sy,dx,dy); }) } function flash_poles() { [src,aux,dst].forEach(function(t) { t.moving = (t.direction.direction === t); t.flash_ctr += 1; t.flash_ctr %= FLASHING_COUNTER; }) } function pop_disk(src,aux,dst) { var towers = [src,aux,dst].filter(t => t.moving); var idx,from,to; idx = (towers[0].toplevel > towers[1].toplevel) ? 0 : 1; [from, to] = [towers[idx], towers[1-idx]]; return new MovingDisk(from.disks.pop().level,from,to); } function draw_moving_disk() { var d = moving_disk; d.step_forward(); stroke('black'); fill(d.color); ellipse(d.pos.x,d.pos.y,2*d.r); return d.finish_p(); } function turn() { [moving_disk.from,moving_disk.to].forEach(function(t) { t.direction = ([src,aux,dst] .filter(x => (x !== t) && (x !== t.direction)))[0]; t.moving = false; }) } function draw() { // base drawing base_drawing(); // find two exchange-towers out of three flash_poles(); // start moving if (moving_disk == null) { moving_disk = pop_disk(src,aux,dst); } else { if (draw_moving_disk()) { turn(); moving_disk = null; } } } |
図3 Pygameを用いて描画ウィンドウを表示するPythonコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import pygame pygame.init() screen = pygame.display.set_mode((640,480)) pygame.display.set_caption('Pygame Window') while True: for event in pygame.event.get(): if event.type == pygame.TEXTINPUT and event.text == 'q': pygame.quit() exit() screen.fill('lavender') pygame.display.flip() pygame.time.delay(30) |
図5 GDHPの手順をアニメーションで表示する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 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
import pygame import math COLORS = [ 'crimson', 'forestgreen', 'yellow', 'royalblue', 'saddlebrown', 'hotpink', 'darkorange', 'darkmagenta' ] NUM_OF_DISKS = len(COLORS) BASE_LENGTH = 200 C_WIDTH = 3.732 * BASE_LENGTH C_HEIGHT = 3.500 * BASE_LENGTH DISK_R = 0.9 * BASE_LENGTH POLE_R = 15 POSITIONS = { 'Source' : [0.268, 0.714], 'Auxiliary' : [0.500, 0.286], 'Destination' : [0.732, 0.714] } FLASHING_COUNTER = 20 STEPS = 30 class Vector: def __init__(self, x, y): self.x = x self.y = y class Position(Vector): def __init__(self, x, y): super().__init__(x, y) def move(self, vec): self.x += vec.x self.y += vec.y class Disk: def __init__(self, level): self.level = level self.color = COLORS[level] self.r = (DISK_R-POLE_R)*(NUM_OF_DISKS-level)/NUM_OF_DISKS + POLE_R class MovingDisk(Disk): def __init__(self, level, frm, to): super().__init__(level) [sx, sy] = [frm.pos.x, frm.pos.y] [dx, dy] = [to.pos.x, to.pos.y] self.pos = Position(sx,sy) self.mvec = Vector((dx-sx)/STEPS,(dy-sy)/STEPS) self.move_ctr = 0 self.frm = frm self.to = to def step_forward(self): self.pos.move(self.mvec) self.move_ctr += 1 def finish_p(self): ret_flag = (self.move_ctr == STEPS) if ret_flag: self.to.disks.append(Disk(self.level)) return ret_flag class Tower: def __init__(self, name, disks, direction=None): self.name = name self.disks = [] for i in range(disks): self.disks.append(Disk(i)) self.direction = direction self.moving = False self.flash_ctr = 0 def toplevel(self): l = len(self.disks) # '-1' means there is no disk. return self.disks[l-1].level if l > 0 else -1 def setup(): pygame.init() screen = pygame.display.set_mode((C_WIDTH, C_HEIGHT)) pygame.display.set_caption('GDHP') for t in [src,aux,dst]: [rx, ry] = POSITIONS[t.name] t.pos = Position(rx * C_WIDTH, ry * C_HEIGHT) return screen def base_drawing(): screen.fill('beige') for t in [src,aux,dst]: # draw disks for d in t.disks: pygame.draw.circle(screen, d.color, (t.pos.x,t.pos.y), d.r) pygame.draw.circle(screen, 'black', (t.pos.x,t.pos.y), d.r, 1) # draw a pole fillcolor = 'gold' \ if t.moving and t.flash_ctr < FLASHING_COUNTER/2 else 'white' pygame.draw.circle(screen, fillcolor, (t.pos.x,t.pos.y), POLE_R) pygame.draw.circle(screen, 'brown', (t.pos.x,t.pos.y), POLE_R, 1) # draw a direction [sx, sy] = [t.pos.x, t.pos.y] [dx, dy] = [t.direction.pos.x, t.direction.pos.y] r = POLE_R / math.sqrt((dx-sx)*(dx-sx)+(dy-sy)*(dy-sy)) [dx, dy] = [(dx-sx)*r+sx, (dy-sy)*r+sy] pygame.draw.line(screen, (0,0,128), (sx,sy), (dx,dy), 3) def flash_poles(): for t in [src,aux,dst]: t.moving = (t.direction.direction == t) t.flash_ctr += 1 t.flash_ctr %= FLASHING_COUNTER def pop_disk(src,aux,dst): towers = list(filter(lambda x: x.moving, [src,aux,dst])) idx = 0 if towers[0].toplevel() > towers[1].toplevel() else 1 [frm, to] = [towers[idx], towers[1-idx]] return MovingDisk(frm.disks.pop().level,frm,to) \ if len(frm.disks) > 0 else None def draw_moving_disk(): d = moving_disk d.step_forward() pygame.draw.circle(screen, d.color, (d.pos.x,d.pos.y), d.r) pygame.draw.circle(screen, 'black', (d.pos.x,d.pos.y), d.r, 1) return d.finish_p() def turn(): for t in [moving_disk.frm,moving_disk.to]: t.direction = list(filter( lambda x: (x != t) and (x != t.direction), [src,aux,dst]))[0] t.moving = False def draw(): # base drawing base_drawing() # find two exchange-towers out of three flash_poles() # start moving finish_p = False mdisk = moving_disk if mdisk == None: mdisk = pop_disk(src,aux,dst) if mdisk == None: finish_p = True else: if draw_moving_disk(): turn() mdisk = None return mdisk, finish_p # main routine if __name__ == '__main__': src = Tower('Source', NUM_OF_DISKS) aux = Tower('Auxiliary', 0, src) dst = Tower('Destination', 0, src) # In the case of NUM_OF_DISKS is odd, # the src must face the src. # Otherwise, the src faces the aux. src.direction = dst if len(COLORS) % 2 == 1 else aux # the reference to moving disk is stored to this variable. moving_disk = None screen = setup() while True: for event in pygame.event.get(): if event.type == pygame.TEXTINPUT and event.text == 'q': pygame.quit() exit() moving_disk, finish_p = draw() if finish_p: pygame.quit() exit() pygame.display.flip() pygame.time.delay(30) |
著者:藤原 由来
本連載では文書の装飾・構造付けを手軽に行える記法であるMarkdownを用いて、さまざまな文書や成果物を作成する方法を紹介します。今回は、Markdownを用いて書籍を制作できる組版アプリ「Vivliostyle」の導入と、書籍作成の基本について説明します。
シェルスクリプトマガジン Vol.91は以下のリンク先でご購入できます。![]()
![]()
図11 chap1.mdファイルの内容
|
1 2 3 4 5 6 7 8 |
# 後注と脚注の例 これは後注[^1]の例です。 インラインで後注^[これも章の後ろに表示される]もできます。 [^1]: 各章の後部にまとめて出力されるタイプの注釈 こちらは脚注<span class="footnote">ページ直下に表示される</span>です。 |
図12 chap2.mdファイルの内容
|
1 2 3 |
# {吾輩|わがはい}は猫である。 {吾輩|わがはい}は猫である。名前はまだ無い。 |
著者:佐藤 楓真
今回は、「焼きなまし法」と呼ばれる手法で、すべての都市を1回訪問して出発地点に帰るまでの最短経路を求める「巡回セールスマン問題」を近似的に解いてみます。焼きなまし法のベースとなる「山登り法」についても解説します。使用するプログラミング言語は、C++です。
シェルスクリプトマガジン Vol.91は以下のリンク先でご購入できます。![]()
![]()
図3 図1の巡回セールスマン問題を山登り法で解く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 |
#include <iostream> #include <algorithm> using namespace std; #define TL 3 //制限時間 #define N 4 int d[N][N] = {{0, 2, 10, 5}, {2, 0, 5, 3}, {10, 5, 0, 4}, {5, 3, 4, 0}}; int ans[N+1] = {0, 1, 3, 2, 0}; int rand_num(void) { return rand() % N + 1; } int calc_dist(void) { int sum = 0; for (int i = 0; i < N; i++) { sum += d[ans[i]][ans[i+1]]; } return sum; } int main(void) { srand((unsigned)time(NULL)); int st_time = clock(); while ((clock() - st_time) / CLOCKS_PER_SEC < TL) { int L = rand_num(), R = rand_num(); if (L > R) swap(L, R); int bf_dist = calc_dist(); reverse(ans + L, ans + R); int af_dist = calc_dist(); if (bf_dist < af_dist) reverse(ans + L, ans + R); } for (int i = 0; i < N+1; i++) { cout << ans[i] + 1 << " "; } cout << endl << calc_dist() << endl; return 0; } |
図4 都市数「5000」のテスト用データを作成する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 |
#include <iostream> #include <vector> #include <fstream> #include <string> using namespace std; #define N 5000 int main(void) { srand((unsigned)time(NULL)); for (int num = 1; num <= 100; num++) { vector<vector<int>> d(N, vector<int>(N)); for (int i = 0; i < N; i++) { for (int j = i; j < N; j++) { if (i == j) { d[i][j] = 0; } else { d[i][j] = rand() % 5000 + 1; } } } for (int i = 0; i < N; i++) { for (int j = 0; j < i; j++) { d[i][j] = d[j][i]; } } string name = "case" + to_string(num) + ".txt"; ofstream out(name); out << N << endl; for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { out << d[i][j] << " "; } out << endl; } } return 0; } |
図5 テスト用データに基づく巡回セールスマン問題を山登り法で解く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 |
#include <iostream> #include <algorithm> #include <vector> #include <string> #include <fstream> using namespace std; #define TL 3 //制限時間 int calc_dist(vector<vector<int>>& dist, vector<int>& ans, int N) { int sum = 0; for (int i = 0; i < N; i++) { sum += dist[ans[i]][ans[i+1]]; } return sum; } int rand_num(int N){ return rand() % N + 1; } int main(void) { long long ave = 0; for (int num = 1; num <= 100; num++) { srand((unsigned)time(NULL)); string name = "case" + to_string(num) + ".txt"; ifstream infile(name); int N; infile >> N; vector<vector<int>> d(N, vector<int>(N)); for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { infile >> d[i][j]; } } vector<int> ans(N + 1); for (int i = 0; i < N; i++) ans[i] = i; ans[N] = 0; int st_time = clock(); while ((clock() - st_time) / CLOCKS_PER_SEC < TL) { int bf_dist = calc_dist(d, ans, N); int L = rand_num(N), R = rand_num(N); if (L > R) swap(L, R); reverse(ans.begin() + L, ans.begin() + R); int af_dist = calc_dist(d, ans, N); if (af_dist > bf_dist) reverse(ans.begin() + L, ans.begin() + R); } ave += calc_dist(d, ans, N); } cout << ave / 100.0 << endl; return 0; } |
図7 テスト用データに基づく巡回セールスマン問題を焼きなまし法で解く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 |
#include <iostream> #include <algorithm> #include <vector> #include <string> #include <fstream> #include <cmath> using namespace std; #define MAX_TMP 100.0 //初期温度 #define TL 3 //制限時間 int calc_dist(vector<vector<int>>& dist, vector<int>& ans, int N) { int sum = 0; for (int i = 0; i < N; i++) { sum += dist[ans[i]][ans[i+1]]; } return sum; } int rand_num(int N) { return rand() % N + 1; } double rand_p(void) { return (double)rand() / RAND_MAX; } int main(void) { srand((unsigned)time(NULL)); long long ave = 0; for (int num = 1; num <= 100; num++) { string name = "case" + to_string(num) + ".txt"; ifstream infile(name); int N; infile >> N; vector<vector<int>> d(N, vector<int>(N)); for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { infile >> d[i][j]; } } vector<int> ans(N + 1); for (int i = 0; i < N; i++) ans[i] = i; ans[N] = 0; int st_time = clock(); //焼きなまし開始時間 while ((clock() - st_time) / CLOCKS_PER_SEC < TL ) { int bf_dist = calc_dist(d, ans, N); int L = rand_num(N), R = rand_num(N); if (L > R) swap(L, R); reverse(ans.begin() + L, ans.begin() + R); int af_dist = calc_dist(d, ans, N); double t = (double)(clock() - st_time) / CLOCKS_PER_SEC; //経過時間 double tmp = MAX_TMP - MAX_TMP * (t / TL); //現在の温度 double p = exp((bf_dist - af_dist) / tmp); //採用確率 if (rand_p() > p) reverse(ans.begin() + L, ans.begin() + R); } ave += calc_dist(d, ans, N); } cout << ave / 100.0 <<endl; return 0; } |
著者:大津 真
本連載では、Linuxの初心者に覚えておいてほしいコマンドを紹介していきます。ここで紹介するコマンドさえ押さえておけば、基本的な操作に困ることはなくなるでしょう。第2回では、主にテキストファイルを処理するための基本コマンドについて解説します。
シェルスクリプトマガジン Vol.91は以下のリンク先でご購入できます。![]()
![]()
図6 「members.csv」ファイルの内容
|
1 2 3 4 5 6 7 8 9 |
<code>001,大津一郎,男,43,東京 002,江南直子,女,8,埼玉 003,唐木田信ー,男,43,福岡 004,森岡一郎,男,41,東京 005,秋山敬一郎,男,44,秋田 006,山田謙一,男,9,福岡 007,山田一郎,男,45,東京 009,福岡花子,女,35,東京 010,白戸二郎,男,39,北海道</code> |
クラウド型家計簿アプリ「Dr.Wallet」のサンプルデータがここからダウンロードできます。家計簿を付けていない人は、こちらを利用してください。なお、本サンプルデータ中のショップ名、電話番号、住所の情報はすべて架空のものです。実在の企業や事業者とは関係ありません。
情報は随時更新致します。
著者:藤原 由来
本連載では文書の装飾や構造付けを手軽に行える記法である「Markdown」を用いて、さまざまな文書や成果物を作成する方法を紹介します。今回も前回に引き続き、Markdownベースの高機能なノートアプリ「Obsidian」を扱います。前回の内容を踏まえて、Obsidianの発展的な使い方を紹介します。
図11 デイリーノートのテンプレート(テンプレート_カレー日記)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
テンプレート_カレー日記 作成日時:{{date}} {{time}} ## 今日のカレー - メニュー: - 感想: - 評価:★★★☆☆ ## お店の情報 - 店名: - 住所: - 最寄り駅: - URL: |
著者:小河原 直道
Raspberry Pi 5(以下、ラズパイ5)が日本でも発売され、私も購入しました。センサーとの接続やサーバーの構築をしてみたいと思ったため、湿温度/気圧/CO₂濃度を測定できるセンサーを接続し、その値をWebブラウザから確認できるシステムを作成しました。このシステムの作成ポイントについて解説します。
シェルスクリプトマガジン Vol.90は以下のリンク先でご購入できます。![]()
![]()
図8 BME280のチップIDを読み出すPythonコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import spidev # 読み取りは8ビット目を「1」に, 書き込みは「0」にする def get_reg_addr(rw: str, addr: int) -> int: rw_flag = 0b1000_0000 if rw == "w": rw_flag = 0b0000_0000 return rw_flag | (addr & 0b0111_1111) spi = spidev.SpiDev() spi.open(0, 0) # bus0, cs0 spi.mode = 0 # mode0, CPOL=CPHA=0 spi.max_speed_hz = 10 * 10**6 # 10MHz bme_id = spi.xfer2([get_reg_addr("r", 0xD0), 0]) print(f"id: {hex(bme_id[1])}") spi.close() |
図9 BME280の気温データを読み出す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 spidev # 読み取りは8ビット目を「1」に, 書き込みは「0」にする def get_reg_addr(rw: str, addr: int) -> int: rw_flag = 0b1000_0000 if rw == "w": rw_flag = 0b0000_0000 return rw_flag | (addr & 0b0111_1111) spi = spidev.SpiDev() spi.open(0, 0) # bus0, cs0 spi.mode = 0 # mode0, CPOL=CPHA=0 spi.max_speed_hz = 10 * 10**6 # 10MHz # 湿度の測定を有効化。オーバーサンプリングx1 r = spi.xfer2([get_reg_addr("r", 0xF2), 0])[1] ctrl_hum = r & 0b1111_1000 | 0b001 spi.xfer2([get_reg_addr("w", 0xF2), ctrl_hum]) # 気圧、気温の測定を有効化。オーバーサンプリングx1、ノーマルモードに ctrl_meas = (0b001 << 5) + (0b001 << 3) + 0b11 spi.xfer2([get_reg_addr("w", 0xF4), ctrl_meas]) # 気温データの読み出し temp_raw_bins = spi.xfer2([get_reg_addr("r", 0xFA), 0, 0, 0]) temp_raw = (temp_raw_bins[1] << 8+4) | (temp_raw_bins[2] << 4) |\ ((temp_raw_bins[3]>>4) & 0b00001111) print(f"気温: {temp_raw}") spi.close() |
図10 MH-Z19CのCO2濃度データを読み出す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 |
import serial import sys import time serial = serial.Serial(port="/dev/ttyAMA0", timeout=5) # 念のためバッファをリセットする serial.reset_input_buffer() serial.reset_output_buffer() time.sleep(2) # 待ち時間は適宜増減して調整 s = bytearray.fromhex("FF 01 86 00 00 00 00 00 79") serial.write(s) r = serial.read(size=9) serial.close() if r[0] != 0xFF: print("invalid data\n") sys.exit(-1) for_check=0 for i, b in enumerate(r): if i==0 or i==8: continue # チェックサムは1バイトのみ、あふれた分は捨てる for_check = for_check + b & 0b1111_1111 for_check = 0xFF - for_check + 1 if r[8] != for_check: print("checksum error\n") sys.exit(-1) print(f"{r[2]*256 + r[3]} ppm") |
図12 測定データをデータベースに保存するPythonコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import mariadb import sys try: conn = mariadb.connect( user = ユーザー名, password = パスワード, host = "127.0.0.1", port = 3306, database = "sensor1", autocommit = True ) except mariadb.Error as e: print(f"db connect error: {e}") sys.exit(-1) cur = conn.cursor() sql = 'INSERT INTO sensor (datetime, temp, humid, press, co2) VALUES(NOW(), ?, ?, ?, ?)' データ(temp_data、humid_data、press_data、co2_data)取得用コードをここに記述 try: cur.execute(sql, (temp_data, humid_data, press_data, co2_data)) except Exception as e: print(f"insert error: {e}") finally: cur.close() conn.close() |
図13 「db/db_pool.js」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 |
const mariadb = require("mariadb"); const pool = mariadb.createPool({ host: "localhost", user: ユーザー名, password: パスワード, database: "sensor1", connectionLimit: 5, }); module.exports = pool; |
図14 「route/api_v1.js」ファイルに記述するコード(抜粋)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
const router = require("express").Router(); const pool = require("../db/db_pool"); function getColumnName(sensor){ switch(sensor){ case "all":return "*"; case "temperature": return "datetime, temp" case "humidity": return "datetime, humid" case "pressure": return "datetime, press" case "co2": return "datetime, co2" default: throw new Error("sensor name error"); } } router.get("/:sensor/latest", async(req, res) => { let sqlCol; try{ sqlCol = getColumnName(req.params.sensor); } catch (e) { return res.status(404).send(`bad sensor name`); } try{ let dbRes = await pool.query( `select ${sqlCol} from sensor order by datetime desc limit 1` ); // クエリー結果の最初のレコードをJSON形式で送る return res.status(200).json(dbRes[0]); } catch(err) { return res.status(500).json({mes:err.toString()}); } }); module.exports = router; |
図15 「route/index.js」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 |
const express = require('express'); const router = express.Router(); const path = require('path'); router.get("/", (req, res) => { res.sendFile(path.join(__dirname, "../view/index.html")); }); router.use("/api/v1", require("./api_v1")); module.exports = router; |
図16 「app.js」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 |
const express = require("express"); const app = express(); const path = require('path'); app.use("/", require("./route/index")); app.use(express.static(path.join(__dirname, "public"))); app.all("*", (req, res) => { return res.sendStatus(404); }); app.listen(3000, () => { console.log("Listening at port 3000"); }); |
筆者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第20回では、データの可視化などにも活用できるWebアプリケーションフレームワーク「Streamlit」の、より進んだ使い方を紹介します。
シェルスクリプトマガジン Vol.90は以下のリンク先でご購入できます。![]()
![]()
図4 プルダウンメニューでサブページに移動できるようにしたコード
|
1 2 3 4 5 6 7 8 9 |
import streamlit as st selected = st.selectbox('なに食べる?', ('-', 'ramen', 'curry', 'katsudon')) pages = {'ramen': 'pages/1_🍜_ramen.py', 'curry': 'pages/2_🍛_curry.py', 'katsudon': 'pages/3_🍚_katsudon.py' } if selected in pages.keys(): st.switch_page(pages[selected]) |
図8 「1_edit.py」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 |
import streamlit as st import pandas as pd df = pd.read_excel('TODO.xlsx') df = st.data_editor(df) clicked = st.button('保存', type='primary', on_click=lambda:\ df.to_excel('TODO.xlsx', index=False)) if (clicked): st.switch_page('index.py') |
図11 「2_new.py」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import streamlit as st import pandas as pd df = pd.read_excel('TODO.xlsx') title = st.text_input(label='タイトル') memo = st.text_area(label='内容') date = st.date_input(label='締切') done = False def append(): global df df_new = pd.DataFrame({'タイトル': [title], '内容': [memo], '締切': [date], '実施': [done]}) df = pd.concat([df, df_new]) df.to_excel('TODO.xlsx', index=False) clicked = st.button('登録', type='primary', on_click=append) if (clicked): st.switch_page('index.py') |
図11 「2_new.py」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import streamlit as st import pandas as pd df = pd.read_excel('TODO.xlsx') title = st.text_input(label='タイトル') memo = st.text_area(label='内容') date = st.date_input(label='締切') done = False def append(): global df df_new = pd.DataFrame({'タイトル': [title], '内容': [memo], '締切': [date], '実施': [done]}) df = pd.concat([df, df_new]) df.to_excel('TODO.xlsx', index=False) clicked = st.button('登録', type='primary', on_click=append) if (clicked): st.switch_page('index.py') |
p.36の「2024年6月31日」は「2024年6月30日」の誤りです。同じページの最初から4行目の「をして」は「として」の誤りです。p.37の右段下から2行目の「Ubuntu Server」は「AlmaLinux」の誤りです。お詫びして訂正します。
p.37で紹介したISOイメージの書き込みツール「balenaEtcher」の最新版(バージョン1.19.×)では、「PORTABLE」版が提供されていません。記事で紹介したバージョン1.18.11のPORTABLE版(balenaEtcher-Portable-1.18.11.exe)はここからダウンロードできます。
情報は随時更新致します。

004 レポート 米OpenAI社の新LLM「GPT-4o」登場
005 レポート JPEGライブラリ「jpegli」公開
006 製品レビュー バイク用エアバッグ「TECH-AIR 5」
007 NEWS FLASH
008 特集1 2024年大注目のMicrosoft Copilot/三沢友治
018 特集2 NVIDIA NeMoで独自の生成AIを構築する/澤井理紀、藤田充矩 コード掲載
026 特集3 Raspberry Pi 5を使う/麻生二郎
036 特別企画 AlmaLinux 9のインストール/麻生二郎
042 Raspberry Pi Pico W/WHで始める電子工作/米田聡 コード掲載
046 Markdownを活用する/藤原由来 コード掲載
052 中小企業手作りIT化奮戦記/菅雄一
056 香川大学SLPからお届け!/小河原直道 コード掲載
062 Pythonあれこれ/飯尾淳 コード掲載
068 これだけは覚えておきたいLinuxコマンド/大津真
076 Techパズル/gori.sh
077 コラム「プロジェクトリーダーの役割」/シェル魔人
著者:澤井 理紀、藤田 充矩
大規模言語モデル(LLM)に基づく生成AIが注目を集めています。そうした生成AIを活用するには、LLMを特定のタスクやデータセットに最適化する「ファインチューニング」が欠かせません。本特集では、生成AIを支える技術を解説した上で、NVIDIAが提供する生成AI向けのフレームワーク「NVIDIA NeMo」を使って独自の生成AIをローカル環境に構築し、LLMをファインチューニングする方法を紹介します。
シェルスクリプトマガジン Vol.90は以下のリンク先でご購入できます。![]()
![]()
図2 LLMダウンロード用のPythonスクリプト
|
1 2 3 4 5 6 7 8 9 10 |
import os from huggingface_hub import snapshot_download MODEL_DIR = "./models" os.makedirs(MODEL_DIR, exist_ok=True) snapshot_download( repo_id="elyza/ELYZA-japanese-Llama-2-7b", local_dir=f"{MODEL_DIR}/ELYZA-japanese-Llama-2-7b", local_dir_use_symlinks=False ) |
図4 PEFTに使用するデータセットの形式を変換する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 |
import json import os import random INPUT_TRAIN = "./JGLUE/datasets/jcommonsenseqa-v1.1/train-v1.1.json" INPUT_VALID = "./JGLUE/datasets/jcommonsenseqa-v1.1/valid-v1.1.json" OUTPUT_DIR = "./data/jcommonsenseqa-v1.1" random.seed(42) os.makedirs(OUTPUT_DIR, exist_ok=True) def write_jsonl(fname, json_objs): with open(fname, 'wt') as f: for o in json_objs: f.write(json.dumps(o, ensure_ascii=False)+"\n") def form_question(obj): st = "" st += "### 指示:\n" st += "与えられた選択肢の中から、最適な答えを選んでください。" st += "出力は以下から選択してください:\n" st += f"- {obj['choice0']}\n" st += f"- {obj['choice1']}\n" st += f"- {obj['choice2']}\n" st += f"- {obj['choice3']}\n" st += f"- {obj['choice4']}\n" st += "### 入力:\n" st += f"{obj['question']}\n" st += "### 応答:" return st def prosess(input_path, train=False): with open(input_path) as f: dataset = [json.loads(line) for line in f.readlines()] processed = [] for data in dataset: prompt = form_question(data) answer = data[f"choice{data['label']}"] processed.append({"input": prompt, "output": f"{answer}"}) if train: random.shuffle(processed) train_ds = processed[:-1000] valid_ds = processed[-1000:] write_jsonl(f"{OUTPUT_DIR}/train-v1.1.jsonl", train_ds) write_jsonl(f"{OUTPUT_DIR}/valid-v1.1.jsonl", valid_ds) else: write_jsonl(f"{OUTPUT_DIR}/test-v1.1.jsonl", processed) return def main(): prosess(INPUT_TRAIN, train=True) prosess(INPUT_VALID) if __name__ == "__main__": main() |
著者:米田 聡
本連載では、人気のマイコンボード「Raspberry Pi Pico W/WH」を活用していきます。同ボードは、無
線LANやBluetoothの通信機能を搭載し、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第6回は、DHT-11で測った湿温度をグラフ化します。
シェルスクリプトマガジン Vol.90は以下のリンク先でご購入できます。![]()
![]()
図2 10 分おきに湿温度を測定して配列に保存する「DhtDataLogger」クラスのコード(ddl.py)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
import dht import time import gc import micropython import _thread from machine import Pin from machine import RTC from machine import Timer DHT_PIN = 15 TICK = 10 SIZE = 144 class DhtDataLogger(): def __init__(self, dht_gpio = DHT_PIN, tick=TICK, size=SIZE): self.rtc = RTC() self.dht = dht.DHT11(Pin(dht_gpio)) self.data = [] self.tick = tick self.size = size # 1分おきにタイマー割り込みを呼ぶ self.timer = Timer(period=60*1000, mode=Timer.PERIODIC, callback=self._timer_handler) self.lock = _thread.allocate_lock() def _timer_handler(self, t): now = self.rtc.datetime() if now[5] % self.tick == 0: micropython.schedule(self._logger, now) def _logger(self, now): # ロックをかける self.lock.acquire() try: self.dht.measure() except OSError as e: pass # 測定日時 nowstr = "{0:02d}:{1:02d}:{2:02d}".format(now[4],now[5],now[6]) self.data.append([nowstr, self.dht.temperature(), self.dht.humidity()]) if len(self.data) > self.size: del self.data[0] # debug # print(self.data[-1]) # ロック解除 self.lock.release() # ガベージコレクト gc.collect() def get_data(self): if len(self.data) == 0: return None # 配列のコピーを返す rval = [] self.lock.acquire() for v in self.data: rval.append(v) self.lock.release() return rval |
図3 蓄積した湿温度をグラフとして表示する簡易Webサーバーのプログラム(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 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 |
import usocket as socket import network import time import ujson as json import gc from machine import Timer from ddl import DhtDataLogger http_header = ''' HTTP/1.1 200 OK\r Content-Type: text/html;charset=UTF-8\r Cache-Control: no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate\r Connection: close\r \r ''' html_base=''' <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>Pico W</title> </head> %s </html> ''' graph_body = ''' <body> <canvas id="DHTChart"></canvas> <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js"></script> <script> var dhtc = document.getElementById("DHTChart"); var DHTChart = new Chart(dhtc, { type: 'line', data: { labels: %s, datasets: [ { label: '気温(度)', data: %s, borderColor: "rgba(255,0,0,1)", backgroundColor: "rgba(0,0,0,0)" }, { label: '湿度(%)', data: %s, borderColor: "rgba(0,0,255,1)", backgroundColor: "rgba(0,0,0,0)" } ], }, options: { title: { display: true, text: '気温と湿度' }, scales: { yAxes: [{ ticks: { suggestedMax: 100, suggestedMin: -10, stepSize: 10, } }] }, } }); </script> </body> ''' logger = DhtDataLogger() sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('', 80)) sock.listen(4) try: while True: conn, addr = sock.accept() connect_from = str(addr[0]) req = conn.recv(1024).decode() if req.startswith('GET / HTTP/1.1'): tlist = logger.get_data() if tlist is None: body = html_base % ("<body><h1>Data not ready</h1></body>") else: labels = [] temps = [] hums = [] for v in tlist: labels.append(v[0]) temps.append(v[1]) hums.append(v[2]) body = graph_body % (json.dumps(labels), json.dumps(temps), json.dumps(hums)) html = html_base % (body) # 送信 conn.send(http_header) conn.sendall(html) conn.close() else: conn.sendall('HTTP/1.1 404 Not Found\r\n') conn.close() gc.collect() except KeyboardInterrupt: pass sock.close() |

004 レポート 進研ゼミに生成AI活用新サービス
005 レポート HHKB Studioのキートップカスタム
006 製品レビュー スマートリング「SOXAI RING 1」
007 NEWS FLASH
008 特集1 Amazon EC2入門/飯村望、清水祥伽
023 Hello Nogyo!
024 特集2 コンテンツクラウドのBox/有賀友三、結城亮史、葵健晴、辰己学
040 Markdownを活用する/藤原由来 コード掲載
050 Raspberry Pi Pico W/WHで始める電子工作/米田聡 コード掲載
054 中小企業手作りIT化奮戦記/菅雄一
058 香川大学SLPからお届け!/宇佐美志久眞
062 Pythonあれこれ/飯尾淳 コード掲載
068 Linux定番エディタ入門/大津真 コード掲載
076 Techパズル/gori.sh
077 コラム「顧客と一心同体の精神を育成」/シェル魔人
著者:米田 聡
本連載では、人気のマイコンボード「Raspberry Pi Pico W/WH」を活用していきます。同ボードは、無線LANやBluetoothの通信機能を搭載し、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第5回は、DHT-11で測った湿温度をスマートフォンに通知します。
シェルスクリプトマガジン Vol.89は以下のリンク先でご購入できます。![]()
![]()
図4 LINE Notifyを使って通知を送る「notify()」関数を定義したPythonプログラム
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import usocket as socket from urequests import post from micropython import const TOKEN = const("your_access_token") def notify(msg): url = 'https://notify-api.line.me/api/notify' rheaders = { 'Content-type':'application/x-www-form-urlencoded', 'Authorization':'Bearer ' + TOKEN } message = "message=" + msg req = post(url,headers=rheaders,data=message) return req.status_code |
図6 1時間おきに湿温度をスマートフォンに通知するサンプルプログラム(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 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 |
from machine import Pin from machine import Timer from micropython import const from micropython import schedule import _thread import dht import line import time DHT_PIN = const(15) TICK = const(60) class Climate(): def __init__(self, dht_gpio=DHT_PIN, tick=TICK, callback=None, expire=TICK): self.dht = dht.DHT11(Pin(dht_gpio)) self.tick = tick self.lock = _thread.allocate_lock() self.timer = Timer(period=tick*1000, mode=Timer.PERIODIC, callback=self._timer_handler) self._humidity = 0 self._temperature = 0 self._measurement_dt = 0 self.measure() self._callback_func = callback self._expire = expire self._counter = 0 def set_callback(self, callback, expire): self._callback_func = callback self._expire = expire def measure(self, arg=0): self.dht.measure() self.lock.acquire() self._humidity = self.dht.humidity() self._temperature = self.dht.temperature() self._measurement_dt = time.time() self.lock.release() def _timer_handler(self,t): schedule(self.measure, 0) self._counter -= 1 if self._counter <= 0: self._counter = self._expire if self._callback_func is not None: schedule(self._callback_func, self) @property def temp(self): self.lock.acquire() rval = self._temperature self.lock.release() return rval @property def hum(self): self.lock.acquire() rval = self._humidity self.lock.release() return rval @property def measurement_date_time(self): self.lock.acquire() rval = self._measurement_dt self.lock.release() return rval def mycalback(c): tm = time.localtime(c.measurement_date_time) line.notify("temp=%d,hum=%d@%4d/%02d/%02d_%02d:%02d:%02d" % (c.temp, c.hum, tm[0],tm[1],tm[2],tm[3],tm[4],tm[5])) if __name__ == '__main__': cl = Climate(callback = mycalback) while(True): machine.idle() |
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第19回では、データの可視化などにも活用できるWebアプリケーションフレームワーク「Streamlit」の基本的な使い方を紹介します。
シェルスクリプトマガジン Vol.89は以下のリンク先でご購入できます。![]()
![]()
図7 「magic.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 streamlit as st # 素のテキストはマークダウンで表示される(docstring以外) ''' # Magicという仕組み 説明を _markdown_ で書けます。 ''' '### 文字列や変数の取り扱い' a = st.number_input("整数aの値を入れてください:+1:", value=0) b = st.number_input("整数bの値を入れてください:+1:", value=0) x = a + b 'a + b = ', x # 文字列と変数の値を並べて直接描画する '### 表の取り扱い' import pandas as pd df = pd.DataFrame({'名前': ['あや','ゆみ','みなこ','とめ'], '年齢': [24,22,32,95]}) df # データフレームを直接描画する '### チャートの取り扱い' import matplotlib.pyplot as plt import numpy as np arr = np.random.normal(1, 1, size=100) fig, ax = plt.subplots() ax.hist(arr, bins=20) fig # チャートも直接描画できる |
図9 キャッシュを設定するコードの例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
'### チャートの取り扱い' import matplotlib.pyplot as plt import numpy as np @st.cache_data def make_chart(): arr = np.random.normal(1, 1, size=100) fig, ax = plt.subplots() ax.hist(arr, bins=20) return fig fig = make_chart() fig # チャートも直接描画できる |
図10 山手線の駅の位置を地図上にプロットするWebアプリのコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import streamlit as st import pandas as pd import json with open('yamate.geojson','r') as f: data = json.load(f) coords = [f['geometry']['coordinates'] \ for f in data ['features'] if f['geometry']['type'] == 'Point'] map_data = pd.DataFrame(coords, columns=['lon', 'lat']) st.map(map_data) |
図12 ウィジェットをサイドバーに配置するコードの例
|
1 2 3 4 5 6 7 8 |
import streamlit as st selected = st.sidebar.selectbox( 'なに食べる?', ('ramen', 'curry', 'katsudon'), ) st.image(selected + '.jpg') |
筆者:大津 真
文章の作成やプログラミングに欠かせないのがテキストエディタ(以下、エディタ)です。この連載では、Linuxで利用できる定番エディタの特徴と使い方を解説していきます。最終回となる今回も、LinuxだけでなくWindowsやmacOSでも利用可能なGUIエディタの定番「Visual Studio Code(VSCode)」を引き続き紹介します。
シェルスクリプトマガジン Vol.89は以下のリンク先でご購入できます。![]()
![]()
図1 「test1.md」ファイルに記述するテキストデータ
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# VSCode 入門 VSCode はシンプルで扱いやすいエディタです。 ## いろいろなエディタ 1. VSCode 2. Vim 3. nano ## VSCode の特徴 - プラグインによる拡張 - ワークスペースによる管理 - コマンドパレットによるコマンド実行 |
p.14の「Windows 10のサポートが終了する2024年6月末ごろ」は「Windows 10(バージョン22H2)のサポートが終了する2025年10月14日」の誤りです(詳しくサポート期間は、こちら)。お詫びして訂正いたします。
Windows 10(バージョン22H2)は、まだ1年半以上のサポートが受けられますので、安心してお使いください。
情報は随時更新致します。

004 レポート Open Source Summit Japan開催
005 レポート 国内開発の日本語LLM
006 製品レビュー スマートテレビ「32S2」
007 NEWS FLASH
008 特集1 CentOS Linux 7からの移行先を考える/弦本春樹
013 Hello Nogyo!
014 特集2 自宅Linuxサーバー構築/麻生二郎 コード掲載
028 特別企画 ゆっくりMovieMaker4で映像制作 シナリオ作り編/ふる(FuruyamD)
038 Raspberry Pi Pico W/WHで始める電子工作/米田聡 コード掲載
042 大規模言語モデル/桑原滝弥、イケヤシロウ
044 Pythonあれこれ/飯尾淳 コード掲載
049 中小企業手作りIT化奮戦記/菅雄一
054 法林浩之のFIGHTING TALKS/法林浩之
056 タイ語から分かる現地生活/つじみき
062 香川大学SLPからお届け!/谷知紘
068 Linux定番エディタ入門/大津真
076 Techパズル/gori.sh
077 コラム「SELiunxとユニケージ」/シェル魔人
著者:麻生 二郎
Linuxやサーバーソフト、インターネットの仕組みを知りたいのなら、Linuxサーバーを構築するのが一番です。使わなくなった古いノートパソコンを活用し、自宅内にLinuxサーバーを構築してみましょう。本特集では、その手順を分かりやすく紹介します。
シェルスクリプトマガジン Vol.88は以下のリンク先でご購入できます。![]()
![]()
図1 Sambaの設定(/etc/samba/smb.conf)
|
1 2 3 4 5 6 7 8 9 10 11 |
[global] workgroup = SHMAG dos charset = CP932 unix charset = UTF8 [share] path = /var/share browsable = yes writable = yes create mask = 0777 directory mask = 0777 |
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第18回では、米Google社が提供する機械学習フレームワーク「MediaPipe」の顔検出機能を用いて、お遊びのプログラム作成に挑戦します。
シェルスクリプトマガジン Vol.88は以下のリンク先でご購入できます。![]()
![]()
図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 |
import cv2 import mediapipe as mp spec = mp.solutions.drawing_utils.DrawingSpec(thickness=1, circle_radius=1) cap = cv2.VideoCapture(0) with mp.solutions.face_mesh.FaceMesh( min_detection_confidence=0.5, min_tracking_confidence=0.5) as face_mesh: while True: success, image = cap.read() if not success: print("Warning: No camera image"); continue image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB) image.flags.writeable = False results = face_mesh.process(image) # Annotate the face mesh image.flags.writeable = True image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) if results.multi_face_landmarks: for landmarks in results.multi_face_landmarks: mp.solutions.drawing_utils.draw_landmarks( image=image, landmark_list=landmarks, connections=mp.solutions.face_mesh.FACEMESH_TESSELATION, landmark_drawing_spec=spec, connection_drawing_spec=spec) cv2.imshow('MediaPipe FaceMesh', image) if cv2.waitKey(5) & 0xFF == 27: break cap.release() |
図4 「顔ハメ」アプリのコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
import cv2 import numpy as np from PIL import Image, ImageDraw import mediapipe as mp def scaleToHeight(img, height): h, w = img.shape[:2] width = round(w * (height / h)) dst = cv2.resize(img, dsize=(width, height)) return dst def convertToRGBA(src, type): return Image.fromarray(cv2.cvtColor(src, type))\ .convert('RGBA') def trimOutside(base, over, loc): drw = ImageDraw.Draw(base) drw.rectangle([(0, 0), (loc[0]-1, over.size[1]-1)], fill=(0, 0, 0)) drw.rectangle([(loc[0]+over.size[0], 0), (base.size[0]-1,over.size[1]-1)], fill=(0, 0, 0)) def overlayImage(src, overlay, location): # convert images to PIL format pil_src = convertToRGBA(src, cv2.COLOR_BGR2RGB) pil_overlay = convertToRGBA(overlay, cv2.COLOR_BGRA2RGBA) # conpose two images pil_tmp = Image.new('RGBA', pil_src.size, (0, 0, 0, 0)) pil_tmp.paste(pil_overlay, location, pil_overlay) trimOutside(pil_tmp, pil_overlay, location) result_image = Image.alpha_composite(pil_src, pil_tmp) # convert result to OpenCV format return cv2.cvtColor(np.asarray(result_image), cv2.COLOR_RGBA2BGRA) def decrementTimer(timer, image, p_idx): h, w = image.shape[:2] if timer < 0: p_idx = (p_idx + 1) % len(panels) return TIMER_INITIAL_VALUE, p_idx elif timer == 30: global still still = image cv2.rectangle(image, (0, 0), (w, h), (255,255,255), thickness=-1) return timer - 1, p_idx elif timer < 30: image = still return timer - 1, p_idx d, r = timer // 30, timer % 30 c = 255 / 60 * r + 128 cv2.putText(image, org=(int(w/2-100), int(h/2+100)), text=str(d), fontFace=cv2.FONT_HERSHEY_DUPLEX, fontScale=10.0, color=(c, c, c), thickness=30, lineType=cv2.LINE_AA) return timer - 1, p_idx # prepare the kao_hame_panels panels = [] panels.append(cv2.imread('img1.png', cv2.IMREAD_UNCHANGED)) panels.append(cv2.imread('img2.png', cv2.IMREAD_UNCHANGED)) panels.append(cv2.imread('img3.png', cv2.IMREAD_UNCHANGED)) # capture from a camera cap = cv2.VideoCapture(0) ret, frame = cap.read() # rescale the kao_hame image height, width = frame.shape[:2] for i in range(len(panels)): panels[i] = scaleToHeight(panels[i], height) p_idx = 0 panel = panels[p_idx] p_height, p_width = panel.shape[:2] # timing counter TIMER_INITIAL_VALUE = 119 timer = TIMER_INITIAL_VALUE with mp.solutions.face_mesh\ .FaceMesh(max_num_faces=1, refine_landmarks=True, min_detection_confidence=0.5, min_tracking_confidence=0.5)\ as face_mesh: while cap.isOpened(): success, image = cap.read() if not success: print("Ignoring empty camera frame.") continue image = cv2.flip(image, 1) location = ((width-p_width)//2, 0) image = overlayImage(image, panel, location) image2 = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) results = face_mesh.process(image2) if results.multi_face_landmarks != None: timer, p_idx = decrementTimer(timer, image, p_idx) panel = panels[p_idx] p_height, p_width = panel.shape[:2] else: timer = TIMER_INITIAL_VALUE # reset timer # triming the image image = image[0:p_height, location[0]:location[0]+p_width] cv2.imshow('Virtual Face-in-Hole Cutout', image) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() |
著者:米田 聡
本連載では、人気のマイコンボード「Raspberry Pi Pico W/WH」を活用していきます。同ボードは、無
線LANやBluetoothの通信機能を搭載し、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第4回は、モノクロ有機ELディスプレイと湿温度センサー「DHT-11」で日本語表示の湿温度計を作ります。
シェルスクリプトマガジン Vol.88は以下のリンク先でご購入できます。![]()
![]()
図9 温度と湿度を日本語で表示するプログラム(temp_hum.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 |
from machine import Pin from machine import I2C from ssd1306 import SSD1306_I2C from display import PinotDisplay from pnfont import Font import dht import time # ELパネル i2c=I2C(0, freq=400000) ssd = SSD1306_I2C(128, 32, i2c) # pinotライブラリ fnt = Font('/fonts/shnmk12u.pfn') disp = PinotDisplay(panel = ssd, font = fnt) # DHT-11センサー sensor = dht.DHT11(Pin(15)) while True: os_error = False try: sensor.measure() except OSError as e: os_error = True line1 = "温度: {0:2d} C".format(sensor.temperature()) line2 = "湿度: {0:2d} %".format(sensor.humidity()) line3 = "DHT-11エラー" if os_error else " " # 温度表示 disp.locate(1, 0) disp.text(line1) # 湿度表示 disp.locate(1,12) disp.text(line2) # エラー表示 disp.locate(1,24) disp.text(line3) time.sleep(2) |
著者:大村 空良
プログラミングにおいて重要な要素の一つが、プログラムの実行速度です。プログラムの実行速度は、より良いアルゴリズムを選択することで大きく改善できます。今回は、巡回セールスマン問題を解くC++プログラムを、アルゴリズムを変更して高速化した例を紹介します。
シェルスクリプトマガジン Vol.87は以下のリンク先でご購入できます。![]()
![]()
図2 図1の巡回セールスマン問題を解くC++コード「sample1.cpp」
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#include <bits/stdc++.h> using namespace std; int main() { int n = 4, ans = 1e9-1, sum; int a[4][4] = { {0, 3, 7, 8}, {3, 0, 4, 4}, {7, 4, 0, 3}, {8, 4, 3, 0} }; vector<int> v_ans, v = {0, 1, 2, 3}; do { //初期化 sum = 0; //距離を足す for (int i = 1; i < n; i++) { sum += a[v[i-1]][v[i]]; } sum += a[v[n-1]][v[0]]; //比較 if (ans > sum) { ans = sum; v_ans = v; } } while (next_permutation(v.begin(), v.end())); //結果表示 for_each(v_ans.begin(), v_ans.end(), [](int& item) { item++; }); copy(v_ans.begin(), v_ans.end(), ostream_iterator<int>(cout, "→")); cout << v_ans[0] << endl; cout << ans << endl; return 0; } |
図3 都市数を変えられるように図2のコードを拡張した「sample2.cpp」
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include <bits/stdc++.h> #include "sales.h" using namespace std; int main() { int ans = 1e9-1, sum; vector<int> v_ans; do { sum = 0; for (int i = 1; i < n; i++) { sum += a[v[i-1]][v[i]]; } sum += a[v[n-1]][v[0]]; if (ans > sum) { ans = sum; v_ans = v; } } while (next_permutation(v.begin(), v.end())); for_each(v_ans.begin(), v_ans.end(), [](int& item) { item++; }); copy(v_ans.begin(), v_ans.end(), ostream_iterator<int>(cout, "→")); cout << v_ans[0] << endl; cout << ans << endl; return 0; } |
図4 ヘッダーファイル生成用のPythonスクリプト「make_header.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 |
import random import sys text = "dummy" while not text.isdecimal(): print("整数値を入力してください: ", file=sys.stderr, end='') text = input() num = int(text) if num > 15 or num < 1: sys.exit(1) array = [[0] * num for i in range(num)] random.seed() for i in range(num): for j in range(num): if i != j: if array[j][i] != 0: array[i][j] = array[j][i] else: array[i][j] = random.randint(1, 9) print('#include <vector>') print('using namespace std;') print(f'int n = {num};') print(f'int a[{num}][{num}] = {{') for i in range(num): line = str(array[i])[1:-1] print('{' + line + '},') print('};') line = list(range(0, num)) line = str(line)[1:-1] print(f'vector<int> v = {{{line}}};') |
図5 フィボナッチ数を求めるC++コード「sample3.cpp」
|
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <bits/stdc++.h> using namespace std; unsigned long long fib(char n) { if ((n <= 0) || (n > 93)) return 0; if (n == 1) return 1; return fib(n - 1) + fib(n - 2); } int main() { char n = 5; cout << fib(n) << endl; return 0; } |
図7 動的計画法を用いて改良したコード「sample4.cpp」
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <bits/stdc++.h> using namespace std; unsigned long long fib(char n) { if ((n <= 0) || (n > 93)) return 0; if (n == 1) return 1; unsigned long long f[128]; f[0] = 0; f[1] = 1; for (char i = 2; i <= n; i++) { f[i] = f[i - 1] + f[i - 2]; } return f[n]; } int main() { char n = 5; cout << fib(n) << endl; return 0; } |
図10 動的計画法を用いて改良したコード「sample5.cpp」
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <bits/stdc++.h> #include "sales.h" using namespace std; int main() { int dp[(1<<n)][n]; for (int i = 0; i < (1<<n); i++) { for (int j = 0; j < n; j++) { dp[i][j] = 1e9-1; } } dp[0][0] = 0; for (int i = 0; i < (1<<n); i++) { for (int j = 0; j < n; j++) { for (int k = 0; k < n; k++) { if (i==0 || !(i & (1<<k)) && (i & (1<<j))) { if( j != k ) { dp[i | (1<<k)][k] = min(dp[i | (1<<k)][k], dp[i][j] + a[j][k]); } } } } } cout << dp[(1<<n) - 1][0] << endl; return 0; } |

004 レポート Raspberry Pi 5が登場
005 レポート キーボード「HHKB Studio」が発売
006 製品レビュー 小型パソコン「X68000 Z PRODUCT EDITION BLACK MODEL」
007 NEWS FLASH
008 特集1 ゆっくりMovieMaker4で映像制作 編集作業編/ふる(FuruyamD)
014 特集2 電子帳簿保存法を知る/永禮啓大
021 Hello Nogyo!
022 特別企画 アプリバで業務システム開発/前佛雅人
032 Raspberry Pi Pico W/WHで始める電子工作/米田聡
035 行動経済学と心理学で円滑に業務を遂行/請園正敏
038 Pythonあれこれ/飯尾淳 コード掲載
044 法林浩之のFIGHTING TALKS/法林浩之
046 中小企業手作りIT化奮戦記/菅雄一 コード掲載
052 エコシステム/桑原滝弥、イケヤシロウ
054 香川大学SLPからお届け!/大村空良
060 タイ語から分かる現地生活/つじみき
066 ユニケージ通信/ぱぺぽ
070 Linux定番エディタ入門/大津真
076 Techパズル/gori.sh
077 コラム「ユニケージが実現・目指しているところ」/シェル魔人
著者:飯尾淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第17回では、データを記録して残す「永続化」の手法の続編として、O/Rマッパーを使ってデータベースにアクセスする方法について解説します。
シェルスクリプトマガジン Vol.87は以下のリンク先でご購入できます。![]()
![]()
図3 宣言的マッピングをするコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
from typing import List from typing import Optional from sqlalchemy import ForeignKey from sqlalchemy import String from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column from sqlalchemy.orm import relationship class Base(DeclarativeBase): pass class User(Base): __tablename__ = "user_account" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(30)) fullname: Mapped[Optional[str]] addresses: Mapped[List["Address"]] = relationship( back_populates="user", cascade="all, delete-orphan" ) def __repr__(self) -> str: return f"User(id={self.id!r},\ name={self.name!r}, fullname={self.fullname!r})" class Address(Base): __tablename__ = "address" id: Mapped[int] = mapped_column(primary_key=True) email_address: Mapped[str] user_id: Mapped[int] = mapped_column(ForeignKey("user_account.id")) user: Mapped["User"] = relationship(back_populates="addresses") def __repr__(self) -> str: return f"Address(id={self.id!r},\ email_address={self.email_address!r})" |
図4 データベースエンジンとなるオブジェクトを作成するコード
|
1 2 |
from sqlalchemy import create_engine engine = create_engine("sqlite:///a_db.sqlite3", echo=True) |
図5 発行されるSQL
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
CREATE TABLE user_account ( id INTEGER NOT NULL, name VARCHAR(30) NOT NULL, fullname VARCHAR, PRIMARY KEY (id) ) CREATE TABLE address ( id INTEGER NOT NULL, email_address VARCHAR NOT NULL, user_id INTEGER NOT NULL, PRIMARY KEY (id), FOREIGN KEY(user_id) REFERENCES user_account (id) ) |
図6 データ操作用オブジェクトを作成するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
from sqlalchemy.orm import Session with Session(engine) as session: spongebob = User( name="spongebob", fullname="Spongebob Squarepants", addresses=[Address(email_address="spongebob@sqlalchemy.org")], ) sandy = User( name="sandy", fullname="Sandy Cheeks", addresses=[ Address(email_address="sandy@sqlalchemy.org"), Address(email_address="sandy@squirrelpower.org"), ], ) patrick = User(name="patrick", fullname="Patrick Star") session.add_all([spongebob, sandy, patrick]) session.commit() |
図7 図6のコードを実行した際の画面出力(抜粋)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
BEGIN (implicit) INSERT INTO user_account (name, fullname) VALUES (?, ?) RETURNING id (略)('spongebob', 'Spongebob Squarepants') INSERT INTO user_account (name, fullname) VALUES (?, ?) RETURNING id (略)('sandy', 'Sandy Cheeks') INSERT INTO user_account (name, fullname) VALUES (?, ?) RETURNING id (略)('patrick', 'Patrick Star') INSERT INTO address (email_address, user_id) VALUES (?, ?) RETURNING id (略)('spongebob@sqlalchemy.org', 1) INSERT INTO address (email_address, user_id) VALUES (?, ?) RETURNING id (略)('sandy@sqlalchemy.org', 2) INSERT INTO address (email_address, user_id) VALUES (?, ?) RETURNING id (略)('sandy@squirrelpower.org', 2) COMMIT |
図8 簡単な検索をするコード
|
1 2 3 4 5 6 7 |
from sqlalchemy.orm import Session from sqlalchemy import select session = Session(engine) stmt = select(User).where(User.name.in_(["spongebob", "sandy"])) for user in session.scalars(stmt): print(user) |
図9 図8のコードを実行した際に発行されるSQL
|
1 2 3 4 5 |
BEGIN (implicit) SELECT user_account.id, user_account.name, user_account.fullname FROM user_account WHERE user_account.name IN (?, ?) (略) ('spongebob', 'sandy') |
図10 図8のコードを実行した際に表示される検索結果
|
1 2 |
User(id=1, name='spongebob', fullname='Spongebob Squarepants') User(id=2, name='sandy', fullname='Sandy Cheeks') |
図11 少し複雑な検索をするコード
|
1 2 3 4 5 6 7 |
stmt = ( select(Address) .join(Address.user) .where(User.name == "sandy") .where(Address.email_address == "sandy@sqlalchemy.org") ) sandy_address = session.scalars(stmt).one() |
図12 図11のコードを実行した際に発行されるSQL
|
1 2 3 4 |
SELECT address.id, address.email_address, address.user_id FROM address JOIN user_account ON user_account.id = address.user_id WHERE user_account.name = ? AND address.email_address = ? (略) ('sandy', 'sandy@sqlalchemy.org') |
図13 データ変更のシンプルなコード例
|
1 2 |
sandy_address.email_address = "sandy_cheeks@sqlalchemy.org" session.commit() |
図14 図13のコードを実行した際に発行されるSQL
|
1 2 3 |
UPDATE address SET email_address=? WHERE address.id = ? (略)('sandy_cheeks@sqlalchemy.org', 2) COMMIT |
図15 データ変更の少し複雑なコード例
|
1 2 3 4 5 |
stmt = select(User).where(User.name == "patrick") patrick = session.scalars(stmt).one() patrick.addresses \ .append(Address(email_address="patrickstar@sqlalchemy.org")) session.commit() |
図16 図15のコードを実行した際に発行されるSQL
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
SELECT user_account.id, user_account.name, user_account.fullname FROM user_account WHERE user_account.name = ? (略)('patrick',) SELECT address.id AS address_id, address.email_address AS address_email_address, address.user_id AS address_user_id FROM address WHERE ? = address.user_id (略)(3,) INSERT INTO address (email_address, user_id) VALUES (?, ?) (略)('patrickstar@sqlalchemy.org', 3) COMMIT |

004 レポート Python in Excelの開発版登場
005 レポート iPhone 15シリーズを発表
006 製品レビュー 電子小物「koko Tag(ココタグ)」
007 NEWS FLASH
008 特集1 Markdown入門ガイド/藤原由来 コード掲載
024 特集2 最新COBOL入門/比毛寛之 コード掲載
034 特別企画 CMSのPlone/酒井忠臣、烈
046 Raspberry Pi Pico W/WHで始める電子工作/米田聡 コード掲載
050 CMS/桑原滝弥、イケヤシロウ
052 Pythonあれこれ/飯尾淳 コード掲載
057 Hello Nogyo!
058 中小企業手作りIT化奮戦記/菅雄一
062 法林浩之のFIGHTING TALKS/法林浩之
064 香川大学SLPからお届け!/石上椋一 コード掲載
072 行動経済学と心理学で円滑に業務を遂行/請園正敏
076 タイ語から分かる現地生活/つじみき
082 Linux定番エディタ入門/大津真
088 ユニケージ通信/田渕智也、高橋未来哉 コード掲載
091 Techパズル/gori.sh
093 コラム「ユニケージアーキテクチャ」/シェル魔人
著者:藤原 由来
本特集では、文書の装飾や構造付けを手軽に実施できる記法「Markdown」について解説します。Markdownを使うと、テキストファイルでレポートやスライド、書籍などを作成でき、GitHubなどの各種Webサービスでの投稿やコミュニケーションがより便利になります。
シェルスクリプトマガジン Vol.86は以下のリンク先でご購入できます。![]()
![]()
図2 Markdownの記述内容
|
1 2 3 4 5 6 7 8 9 10 11 |
# カレーの作り方 おいしいカレーを作りましょう。 ## 材料 - カレールー - 牛肉 - 野菜 - サラダ油 - 水 |
図19 Markdownテキストの例
|
1 2 3 4 5 6 7 8 9 10 11 |
#␣カレーの作り方 おいしいカレーを作りましょう。 ##␣材料 -␣カレールー -␣牛肉 -␣野菜 -␣サラダ油 -␣水 |
図46 MarpによるスライドのMarkdownテキスト例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
--- marp: true --- # カレーの作り方 おいしいカレーを作りましょう。 --- # 材料 - カレールー - 牛肉 - 野菜 - サラダ油 - 水 |
著者:比毛 寛之
レガシー問題や技術者不足などで話題の多いプログラミング言語が「COBOL」です 。古い言語というイメージがありますが、ISOから2023年の新規格が公表されたことでも分かるように、現在も進化を続けています。本特集では、COBOLの現状を紹介しつつ、「opensource COBOL 4J」というオープンソースソフトウエアのCOBOLコンパイラを使ってCOBOL言語の基本を解説します。
シェルスクリプトマガジン Vol.86は以下のリンク先でご購入できます。![]()
![]()
図1 「HELLO WORLD!」という文字列を表示するCOBOLのサンプルコード
|
1 2 3 4 5 6 7 8 9 10 11 12 |
----+----1----+----2----+----3----+----4----+----5 IDENTIFICATION DIVISION. PROGRAM-ID. HELLOWORLD. DATA DIVISION. WORKING-STORAGE SECTION. 01 MY-TEXT PIC X(20). PROCEDURE DIVISION. MAIN-RTN. MOVE "HELLO WORLD!" TO MY-TEXT. DISPLAY MY-TEXT. STOP RUN. ----+----1----+----2----+----3----+----4----+----5 |
図4 COBOLのサンプルコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8 IDENTIFICATION DIVISION. PROGRAM-ID. MYCOBOL. DATA DIVISION. WORKING-STORAGE SECTION. 01 MY-GRP. * 03 MY-WORK-X PIC X(10). 20230804 03 MY-WORK-X2 PIC X(10). 20230804 03 MY-WORK-9 PIC 9(05). ******************************************** PROCEDURE DIVISION. ******************************************** PROC1 SECTION. PROC1-000. * MOVE "ABC" TO MY-WORK-X. 20230804 MOVE "DEF" TO MY-WORK-X2. 20230804 MOVE 100 TO MY-WORK-9. PROC1-900. STOP RUN. ----+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8 |
図5 コンソールからの入力と画面への出力をするサンプルコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
----+----1----+----2----+----3----+----4----+----5----+ IDENTIFICATION DIVISION. PROGRAM-ID. HELLO. DATA DIVISION. WORKING-STORAGE SECTION. 01 MY-NAME PIC X(20). PROCEDURE DIVISION. MAIN-RTN. DISPLAY "Enter your name: " NO ADVANCING. ACCEPT MY-NAME. DISPLAY "Hello " MY-NAME. MAIN-EXIT. STOP RUN. ----+----1----+----2----+----3----+----4----+----5----+ |
図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 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7 IDENTIFICATION DIVISION. PROGRAM-ID. EMPWRITE. ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL. SELECT EMP-FILE ASSIGN TO "EMPFILE" ORGANIZATION IS INDEXED ACCESS MODE IS DYNAMIC RECORD KEY IS EMP-CD FILE STATUS IS EMP-STS. DATA DIVISION. FILE SECTION. FD EMP-FILE. 01 EMP-REC. 03 EMP-CD PIC X(04). 03 EMP-NAME PIC X(20). 03 EMP-DPT-CD PIC X(02). 03 EMP-ENT-DATE PIC 9(08). WORKING-STORAGE SECTION. 01 EMP-STS PIC 9(02). PROCEDURE DIVISION. MAIN-CONTROL SECTION. MAIN-000. DISPLAY "*** Creating Employee file ***". OPEN OUTPUT EMP-FILE. * MOVE "0011" TO EMP-CD. MOVE "Saitama Saburo" TO EMP-NAME. MOVE "01" TO EMP-DPT-CD. MOVE 20020401 TO EMP-ENT-DATE. WRITE EMP-REC. * ----+----+----+----+----+----+----+ WRITE EMP-REC FROM "0012Chiba Jiro 0219990401". WRITE EMP-REC FROM "0013Tokyo Taro 0319970401". WRITE EMP-REC FROM "0014Kanagawa Shiro 0120120401". WRITE EMP-REC FROM "0015Niigata Goroo 0220010401". * ----+----+----+----+----+----+----+ CLOSE EMP-FILE. MAIN-900. STOP RUN. ----+----1----+----2----+----3----+----4----+----5----+----6----+----7 |
図7 ファイル中のレコードを読み出して画面に表示するサンプルコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
----+----1----+----2----+----3----+----4----+----5----+----6----+ IDENTIFICATION DIVISION. PROGRAM-ID. EMPLIST. ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL. SELECT EMP-FILE ASSIGN TO "EMPFILE" ORGANIZATION IS INDEXED ACCESS MODE IS DYNAMIC RECORD KEY IS EMP-CD FILE STATUS IS EMP-STS. DATA DIVISION. FILE SECTION. FD EMP-FILE. 01 EMP-REC. 03 EMP-CD PIC X(04). 03 EMP-NAME PIC X(20). 03 EMP-DPT-CD PIC X(02). 03 EMP-ENT-DATE PIC 9(08). WORKING-STORAGE SECTION. 01 EMP-STS PIC 9(02). 01 DSP-REC. 03 DSP-CD PIC X(04). 03 FILLER PIC X. 03 DSP-NAME PIC X(20). 03 FILLER PIC XX. 03 DSP-DPT-CD PIC X(02). 03 FILLER PIC X. 03 DSP-ENT-DATE PIC 9999/99/99. PROCEDURE DIVISION. MAIN-CONTROL SECTION. MAIN-000. OPEN INPUT EMP-FILE. DISPLAY "*** Employee List ***". DISPLAY "ID Employee Name Dpt Enter date". DISPLAY "---- -------------------- --- ----------". PERFORM UNTIL (EMP-STS NOT = ZERO) READ EMP-FILE NEXT AT END DISPLAY "EOF" NOT AT END MOVE EMP-CD TO DSP-CD MOVE EMP-NAME TO DSP-NAME MOVE EMP-DPT-CD TO DSP-DPT-CD MOVE EMP-ENT-DATE TO DSP-ENT-DATE DISPLAY DSP-REC END-READ END-PERFORM. CLOSE EMP-FILE. MAIN-900. STOP RUN. ----+----1----+----2----+----3----+----4----+----5----+----6----+ |
図9 「EMPSEARCH.cbl」ファイルに記述するコード
|
1 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 |
----+----1----+----2----+----3----+----4----+----5----+----6----+- IDENTIFICATION DIVISION. PROGRAM-ID. EMPSEARCH. DATA DIVISION. WORKING-STORAGE SECTION. 01 WK-AREA. 03 WK-CD PIC X(04). 03 WK-NAME PIC X(20). 03 WK-DPT-CD PIC X(02). 03 WK-ENT-DATE PIC 9(08). 03 WK-RETURN PIC 9(01). PROCEDURE DIVISION. MAIN-RTN. DISPLAY "*** Employee Search ***". DISPLAY "Code: : " NO ADVANCING. ACCEPT WK-CD. CALL "EMPREAD" USING WK-CD, WK-NAME, WK-DPT-CD, WK-ENT-DATE, WK-RETURN. IF WK-RETURN = ZERO DISPLAY "Name : " WK-NAME DISPLAY "Dept code : " WK-DPT-CD DISPLAY "Enter date: " WK-ENT-DATE ELSE DISPLAY "Employee not found!" END-IF. MAIN-EXIT. STOP RUN. ----+----1----+----2----+----3----+----4----+----5----+----6----+- |
図10 「EMPREAD.cbl」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7 IDENTIFICATION DIVISION. PROGRAM-ID. EMPREAD. ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL. SELECT EMP-FILE ASSIGN TO "EMPFILE" ORGANIZATION IS INDEXED ACCESS MODE IS DYNAMIC RECORD KEY IS EMP-CD FILE STATUS IS EMP-STS. DATA DIVISION. FILE SECTION. FD EMP-FILE. 01 EMP-REC. 03 EMP-CD PIC X(04). 03 EMP-NAME PIC X(20). 03 EMP-DPT-CD PIC X(02). 03 EMP-ENT-DATE PIC 9(08). WORKING-STORAGE SECTION. 01 EMP-STS PIC 9(02). LINKAGE SECTION. 01 LK-CD PIC X(04). 01 LK-NAME PIC X(20). 01 LK-DPT-CD PIC X(02). 01 LK-ENT-DATE PIC 9(08). 01 LK-RETURN PIC 9(01). PROCEDURE DIVISION USING LK-CD, LK-NAME, LK-DPT-CD, LK-ENT-DATE, LK-RETURN. MAIN-CONTROL SECTION. MAIN-000. INITIALIZE EMP-REC. MOVE ZERO TO LK-RETURN. OPEN INPUT EMP-FILE. MOVE LK-CD TO EMP-CD. READ EMP-FILE KEY IS EMP-CD INVALID KEY MOVE 1 TO LK-RETURN END-READ. MOVE EMP-NAME TO LK-NAME. MOVE EMP-DPT-CD TO LK-DPT-CD. MOVE EMP-ENT-DATE TO LK-ENT-DATE. CLOSE EMP-FILE. MAIN-900. EXIT PROGRAM. ----+----1----+----2----+----3----+----4----+----5----+----6----+----7 |
図11 「EmpSearchDemo.java」ファイルに記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import jp.osscons.opensourcecobol.libcobj.ui.*; public class EmpSearchDemo { public static void main(String[] args) throws Exception { EMPREAD prog = new EMPREAD(); CobolResultSet rs = prog.execute("0011", "", "", 0, 0); System.out.println("*** Employee Search from Java ***"); System.out.println("Code : " + rs.getString(1)); System.out.println("Name : " + rs.getString(2)); System.out.println("Dept code : " + rs.getString(3)); System.out.println("Enter date: " + rs.getInt(4)); } } |
著者:米田 聡
本連載では、人気のマイコンボード「Raspberry Pi Pico W」を活用していきます。同ボードは、無線LANやBluetoothの通信機能を搭載し、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第2回は、有機ELディスプレイを取り付けます。
シェルスクリプトマガジン Vol.86は以下のリンク先でご購入できます。![]()
![]()
図6 SSD1306搭載有機ELディスプレイを動作させるサンプルプログラム(ssd1306_sample.py)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
from machine import I2C from ssd1306 import SSD1306_I2C import network i2c = I2C(0) ssd = SSD1306_I2C(width=128, height=32, i2c=i2c, addr=0x3C) ssd.fill(0) # 0クリア ssd.text("Hello, world", 0, 0) # IPアドレスを描画 conn = network.WLAN(network.STA_IF) if conn.isconnected(): conf = conn.ifconfig() ssd.text(conf[0], 0, 8) ssd.show() # フレームバッファをSSD1306に転送 |
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温
かく見守ってください。皆さんと共に勉強していきましょう。第16回では、データを永続的に残す「永続化」の手法の一つとして、Pythonプログラムからデータベースにアクセスする方法について解説します。
シェルスクリプトマガジン Vol.86は以下のリンク先でご購入できます。![]()
![]()
図3 データベースにテーブルを作成してデータを格納するコード
|
1 2 3 4 5 6 7 8 |
from sqlalchemy import text with engine.connect() as conn: conn.execute(text("CREATE TABLE a_table (id int, name string)")) conn.execute( text("INSERT INTO a_table (id, name) VALUES (:id, :name)"), [{"id":1, "name":"John"}, {"id":2, "name":"Bob"}], ) conn.commit() |
図5 テーブルのデータを読み出すコード
|
1 2 3 4 |
with engine.connect() as conn: result = conn.execute(text("SELECT * FROM a_table")) for record in result: print(f"id: {record.id}, name: {record.name}") |
図6 commit()を忘れたコードの例
|
1 2 3 4 5 6 |
with engine.connect() as conn: conn.execute( text("INSERT INTO a_table (id, name) VALUES (:id, :name)"), [{"id":3, "name":"Kate"}, {"id":4, "name":"Bob"}], ) conn.commit() |
図8 データベースに含まれるテーブルを表示するコード
|
1 2 3 4 5 6 7 8 |
from sqlalchemy import create_engine engine = create_engine("sqlite:///:memory:", echo=True) with engine.connect() as conn: result = conn.execute( text("SELECT name FROM sqlite_master WHERE type='table'") ) for record in result: print(record) |
図9 ファイル内にデータベースを作成するコード
|
1 2 3 4 5 6 7 8 9 |
from sqlalchemy import create_engine, text engine = create_engine("sqlite:///db.sqlite3") with engine.connect() as conn: conn.execute(text("CREATE TABLE a_table (id int, name string)")) conn.execute( text("INSERT INTO a_table (id, name) VALUES (:id, :name)"), [{"id": 1, "name": "John"}, {"id": 2, "name": "Bob"}], ) conn.commit() |
図10 ファイル内データベースのテーブルからデータを読み出すコード
|
1 2 3 4 5 6 |
from sqlalchemy import create_engine, text engine = create_engine("sqlite:///db.sqlite3") with engine.connect() as conn: result = conn.execute(text("SELECT * FROM a_table")) for record in result: print(f"id: {record.id}, name: {record.name}") |
著者:石上 椋一
今回は、私が開発した文章校正用のWebアプリケーションについて紹介します。文章校正機能は「textlint」というNode.js上で稼働するアプリケーションを使って実現しているため、少ないコード量で実装できました。米Google社のアプリケーション開発プラットフォーム「Firebase」を利用したユーザー認証機能を付加する方法も解説します。
シェルスクリプトマガジン Vol.86は以下のリンク先でご購入できます。![]()
![]()
図1 テンプレートファイル「index.html」に記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<!doctype html> <html lang="ja"> <head> <title>文章校正アプリ</title> </head> <body> <h1>こんにちは!文章校正アプリです</h1> <h2>PDFファイルアップローダー</h2> <form action="/result" method="POST" enctype="multipart/form-data"> <input type=file name="pdf_file"> <button>ファイル送信</button> </form> </body> </html> |
図2 テンプレートファイル「result.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 |
<!doctype html> <html lang="ja"> <head> <title>文章校正アプリ 結果表示</title> </head> <body> <h1>こんにちは!文章校閲アプリです</h1> <h2>PDFファイルアップローダー</h2> <form action="/result" method="POST" enctype="multipart/form-data"> <input type=file name="pdf_file"> <button>ファイル送信</button> </form> <table border="1"> <thead> <tr> <th>何行目</th> <th>何文字目</th> <th>修正内容</th> </tr> </thead> <tbody> {% for result in result_table %} <tr> <td>{{result[0]}}</td> <td>{{result[1]}}</td> <td>{{result[2]}}</td> </tr> {% endfor %} </tbody> </table> <br> <p>修正した文章</p> <textarea rows="10" cols="80">{{result_text}}</textarea> </body> </html> |
図3 「web_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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
from flask import Flask, request, render_template, \ redirect, url_for, session import subprocess import re app = Flask(__name__) def upload_pdf_file(pdf_file): pdf_file.save("uploads/target.pdf") def run_pdf2text(): cmd = "pdftotext -nopgbrk uploads/target.pdf uploads/target.md" subprocess.run(cmd, shell=True, capture_output=True, text=True) def run_textlint_and_get_result_list(): cmd = "npx textlint uploads/target.md" result = subprocess.run(cmd, shell=True, capture_output=True, text=True) result_list = result.stdout.split("\n") result_table = [] for result in result_list[2:-4]: tmp_result = result.split(" error ") if len(tmp_result) >= 2: row = int(tmp_result[0].split(":")[0].strip()) colum = tmp_result[0].split(":")[1].strip() error_data = re.sub("ja-technical-writing/[a-z | -]*", "",tmp_result[1]).strip() result_table.append([row, colum, error_data]) return result_table def run_textlint_fix_data(): cmd = "npx textlint --fix ./uploads/target.md" subprocess.run(cmd, shell=True, capture_output=True, text=True) with open("./uploads/target.md", ) as md_file: data_lines = md_file.read() return data_lines @app.route("/index", methods=["GET"]) def index(): return render_template("index.html") @app.route("/", methods=["GET"]) def root(): return redirect(url_for("index")) @app.route("/result", methods=["GET", "POST"]) def result(): if request.method == "POST": pdf_file = request.files["pdf_file"] upload_pdf_file(pdf_file) run_pdf2text() result_table = run_textlint_and_get_result_list() result_text = run_textlint_fix_data() return render_template("result.html", result_table=result_table, result_text=result_text) return redirect(url_for('index')) if __name__ == "__main__": app.debug = True app.run(host='0.0.0.0', port=5000) |
図4 「.textlintrc.json」ファイルに記述する設定の例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
{ "rules": { "preset-ja-technical-writing": { "sentence-length": { "max": 100 }, "max-comma": { "max": 4 }, "max-ten": { "max": 4 }, "max-kanji-continuous-len": { "max": 7, "allow": [] }, "arabic-kanji-numbers": true, "no-mix-dearu-desumasu": { "preferInHeader": "", "preferInBody": "である", "preferInList": "である", "strict": true }, "ja-no-mixed-period": { "periodMark": "。" }, "no-double-negative-ja": true, "no-dropping-the-ra": true, "no-doubled-conjunctive-particle-ga": true, "no-doubled-conjunction": true, "no-doubled-joshi": { "min_interval": 1 }, "no-invalid-control-character": true, "no-zero-width-spaces": true, "no-exclamation-question-mark": true, "no-hankaku-kana": true, "ja-no-weak-phrase": true, "ja-no-successive-word": true, "ja-no-abusage": true, "ja-no-redundant-expression": true, "ja-unnatural-alphabet": true, "no-unmatched-pair": true } } } |
図10 「~/webapp/static/json/firebase.json」ファイルに記述する設定の例
|
1 2 3 4 5 6 7 8 9 |
{ "apiKey": "AIzaSyA1LQooSfvSda0jkBQl20ZfGR6lHOC5XBw", "authDomain": "test-1d03e.firebaseapp.com", "databaseURL": "https://test-1d03e-default-rtdb.firebaseio.com", "projectId": "test-1d03e", "storageBucket": "test-1d03e.appspot.com", "messagingSenderId": "919322583901", "appId": "1:919322583901:web:abb5d744e9ed04b8927eb4" } |
図11 テンプレートファイル「create_account.html」に記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<!doctype html> <html lang="ja"> <head> <title>文章校正アプリ アカウント作成ページ</title> </head> <body> <form class="login-form" action='/create_account' method='POST'> <input type="text" name="email" placeholder="email address"/> <input type="password" name="password" placeholder="password"/> <button>Create an account</button> <p>{{msg}}</p> <p>Already registered? <a href="/login">Login</a> </p> </form> </body> </html> |
図12 テンプレートファイル「login.html」に記述するコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<!doctype html> <html lang="ja"> <head> <title>文章校正アプリ ログインページ</title> </head> <body> <form class="login-form" action="/login" method="POST"> <input type="text" name="email" placeholder="email address"/> <input type="password" name="password" placeholder="password"/> <button>Login</button> <p>{{msg}}</p> <p>Not registered? <a href="/create_account">Create an account</a> </p> </form> </body> </html> |
図13 「web_app.py」ファイルの「app = Flask(name)」行の前後に追加するコード
|
1 2 3 4 5 6 7 8 9 |
import os, json import pyrebase app = Flask(__name__) app.config["SECRET_KEY"] = os.urandom(24) with open("static/json/firebase.json") as f: firebaseConfig = json.loads(f.read()) firebase = pyrebase.initialize_app(firebaseConfig) auth = firebase.auth() |
図14 「web_app.py」ファイルの既存のルーティング設定にコードを追加
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
(略) @app.route("/index", methods=["GET"]) def index(): if session.get("usr") == None: return redirect(url_for("login")) return render_template("index.html") @app.route("/", methods=["GET"]) def root(): return redirect(url_for("index")) @app.route("/result", methods=["GET", "POST"]) def result(): if session.get("usr") == None: return redirect(url_for("login")) if request.method == "POST": pdf_file = request.files["pdf_file"] (略) |
図15 「web_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 |
@app.route("/login", methods=["GET", "POST"]) def login(): if request.method == "GET": return render_template("login.html", msg="") email = request.form["email"] password = request.form["password"] try: auth.sign_in_with_email_and_password(email, password) session["usr"] = email return redirect(url_for("index")) except: message = "メールアドレスまたはパスワードが違います" return render_template("login.html", msg=message) @app.route("/create_account", methods=["GET", "POST"]) def create_account(): if request.method == "GET": return render_template("create_account.html", msg="") email = request.form["email"] password = request.form["password"] try: auth.create_user_with_email_and_password(email, password) session["usr"] = email return redirect(url_for("index")) except: message = "アカウントを作成できませんでした" return render_template("login.html", msg=message) @app.route('/logout') def logout(): del session["usr"] return redirect(url_for("login")) |

004 レポート Wasm向けPOSIX互換ライブラリ「WASIX」
005 レポート 手のひらネットワーク機器
006 製品レビュー ロボット「NICOBO(ニコボ)」
007 NEWS FLASH
008 特集1 ゆっくりMovieMaker4で映像制作 準備編/ふる(FuruyamD)
015 Hello Nogyo!
016 特集2 ZabbixのAWS監視機能を使ってみよう/寺島広大、渡邊隼人、水谷和弘、狩俣樹
026 特集3 ChatGPTで注目の対話型AI/三沢友治
034 特別企画 成功するアジャイル開発/奥村剛史
044 Raspberry Pi Pico W/WHで始める電子工作/米田聡 コード掲載
054 Pythonあれこれ/飯尾淳
060 行動経済学と心理学で円滑に業務を遂行/請園正敏
064 中小企業手作りIT化奮戦記/菅雄一
068 SEO/桑原滝弥、イケヤシロウ
070 タイ語から分かる現地生活/つじみき
076 法林浩之のFIGHTING TALKS/法林浩之
078 香川大学SLPからお届け!/谷知紘 コード掲載
084 Linux定番エディタ入門/大津真
091 ユニケージ通信/田渕智也、高橋未来哉
096 Techパズル/gori.sh
097 コラム「ユニケージはデータ移行も得意」/シェル魔人
著者:米田 聡
本連載では、人気のマイコンボード「Raspberry Pi Pico W/WH」を活用していきます。同ボードは、無線LANやBluetoothの通信機能を搭載し、入手しやすく、価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第1回は、プログラムの開発環境を構築します。
シェルスクリプトマガジン Vol.85は以下のリンク先でご購入できます。![]()
![]()
図17 初期化プログラム(boot.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 |
import sys import time import network import ntptime from machine import RTC SSID='your_ssid' PASS='your_passphrase' IFCONFIG=('192.168.1.80', '255.255.255.0', '192.168.1.1', '192.168.1.1') def wifi_connect(ssid, passkey, timeout=20): conn = network.WLAN(network.STA_IF) if conn.isconnected(): return conn conn.active(True) conn.connect(ssid, passkey) while not conn.isconnected() and timeout > 0: time.sleep(1) timeout -= 1 if conn.isconnected(): return conn else: return None conn = wifi_connect(SSID, PASS) if conn is None: print('Can not connect to ' + SSID) else: conn.ifconfig(IFCONFIG) print('Connect to ' + SSID) # 日時設定 rtc = RTC() ntptime.host = 'ntp.nict.jp' now = time.localtime(ntptime.time() + 9 * 60 * 60) rtc.datetime((now[0], now[1], now[2], now[6], now[3], now[4], now[5], 0)) now = rtc.datetime() print("%04d-%02d-%02d %02d:%02d:%02d" %(now[0], now[1], now[2], now[4], now[5], now[6])) |
図18 簡易ネットワークサーバー(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 31 |
import usocket as socket import network import time html = ''' <html> <head><title>Pico W</title></head> <body> <h1>Welcome to Pico W</h1> </body> </html> ''' sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('', 80)) sock.listen(4) while True: conn, addr = sock.accept() print('Connect from %s' %(str(addr))); req = conn.recv(1024).decode() if not req.startswith('GET / HTTP/1.1'): conn.send('HTTP/1.1 404 Not Found\r\n') conn.close() else: conn.send('HTTP/1.1 200 OK\r\n') conn.send('Content-Type: text/html\r\n') conn.send('Connection: close\r\n\r\n') conn.sendall(html) conn.close() |
著者:谷 知紘
今回は、C言語を用いて作成した、コンピュータと対戦できるリバーシを紹介します。あまり強くはありませんが、きちんとコンピュータが相手をしてくれます。C99以降の規格に対応するCコンパイラと標準Cライブラリがあれば、環境を問わずに動作するプログラムになっていますので、皆さんもコンパイルして楽しんでください。
シェルスクリプトマガジン Vol.85は以下のリンク先でご購入できます。![]()
![]()
図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 |
//盤面 空白(0) 黒(-1) 白(1) 番兵(2) int board[10][10] = {0}; //スコアを格納する配列 int weightdata[10][10] = { {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 30,-12, 0, -1, -1, 0,-12, 30, 0}, {0,-12,-15, -3, -3, -3, -3,-15,-12, 0}, {0, 0, -3, 0, -1, -1, 0, -3, 0, 0}, {0, -1, -3, -1, -1, -1, -1, -3, -1, 0}, {0, -1, -3, -1, -1, -1, -1, -3, -1, 0}, {0, 0, -3, 0, -1, -1, 0, -3, 0, 0}, {0,-12,-15, -3, -3, -3, -3,-15,-12, 0}, {0, 30,-12, 0, -1, -1, 0,-12, 30, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }; //盤面の生成 void make_board(){ //番兵 for(int i = 0; i < 10; i++){ board[0][i] = 2; board[9][i] = 2; board[i][0] = 2; board[i][9] = 2; } //初期配置する石 board[4][4] = 1; board[5][5] = 1; board[4][5] = -1; board[5][4] = -1; } |
図6 check_plc()関数のコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//指定したマスに石を置けるかどうかを判定する関数 bool check_plc(int i, int j, int now_board[10][10]){ //マスが空かどうか if(board[i][j] == 0){ //全方向を探索 for(int dir_i = -1; dir_i < 2; dir_i++){ for(int dir_j = -1; dir_j < 2; dir_j++){ if(check_dir(i,j,dir_i,dir_j,now_board)){ //配置可能であればtrueを返す return true; } } } } return false; } |
図8 check_dir()関数のコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//指定方向に何個の石を挟めるかを調べる関数 int check_dir(int i, int j, int dir_i, int dir_j, int now_board[10][10]){ //指定方向に相手の石がある場合は次のマスを探索 int times = 1; while(now_board[i+dir_i*times][j+dir_j*times] == player*-1){ times++; } //指定方向の最後に自分の石がある場合 if(now_board[i+dir_i*times][j+dir_j*times] == player){ //指定方向に相手の石が何個あるかを返す return times-1; } //指定方向の最後に自分の石がなければ0を返す return 0; } |
図9 place_stn()関数のコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//指定したマスに石を配置して、挟んだ石を自分の石にする関数 void place_stn(int i, int j, int now_board[10][10]){ //全方向を調査 for(int dir_i = -1; dir_i < 2; dir_i++){ for(int dir_j = -1; dir_j < 2; dir_j++){ //挟んだ石の数 int change_num = check_dir(i,j,dir_i,dir_j,now_board); //挟んだ石の数だけ自分の石に変更 for(int k = 1; k < change_num+1; k++){ now_board[i+dir_i*k][j+dir_j*k] = player; } } } //指定したマスに自分の石を配置 now_board[i][j] = player; } |
図10 think()関数のコード
|
1 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 |
//コンピュータの手番で呼び出される関数 void think(){ //ハイスコアの初期化 int hightscore = -1000; int px, py; for(int y = 0; y < 10; y++){ for(int x = 0; x < 10; x++){ //石を置けない場合はスキップ if(!check_plc(y, x, board)){ continue; } int tmpdata[10][10] = {0}; //盤面データをコピーした仮の盤面を作成 copydata(tmpdata); //仮の盤面に石を置く place_stn(y, x, tmpdata); //総スコアを計算する int score = calcweight(tmpdata); //ハイスコアよりも総スコアが良ければ更新する if(score > hightscore){ hightscore = score; px = x; py = y; } } } //総スコアが最大のマスに石を置く place_stn(py, px, board); printf("PCは(x , y) = (%d , %d)に置きました\n", px, py); } |
図11 copydata()関数のコード
|
1 2 3 4 5 6 7 8 |
//盤面データをコピーする関数 void copydata(int tmpdata[10][10]){ for(int y = 0; y < 10; y++){ for(int x = 0; x < 10; x++){ tmpdata[y][x] = board[y][x]; } } } |
図12 calcweight()関数のコード
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//総スコアを計算する関数 int calcweight(int tmpdata[10][10]){ int score = 0; for(int y = 0; y < 10; y++){ for(int x = 0; x < 10; x++){ //番兵はスキップ if(tmpdata[y][x] == 2){ continue; } //自分の石がある場所のスコアを足す if(tmpdata[y][x] == 1){ score += weightdata[y][x]; } } } return score; } |
p.12の表1の一番下の行にある「00d.png」は「00e.png」の誤りです。お詫びして訂正いたします。
p.18に記した「後述するAWS用のテンプレートの利用を含め、すべてAWSの無料利用枠で試せる」という情報は誤りでした。正しくは、AWS用のテンプレート利用時には、CloudWatchからGetMetricData APIでメトリクスを取得する料金がかかります。料金は、メトリクス1000件当たり0.01ドルです。お詫びして訂正いたします。なお、Kindle版とPDF版(定期購読特典)は修正済みです。
情報は随時更新致します。
著者:大津 真
文章の作成やプログラミングに欠かせないのがテキストエディタ(以下、エディタ)です。この連載では、Linuxで利用できる定番エディタの特徴と使い方を解説していきます。第1回に取り上げるのは、CU(I Character User Interface)環境で利用できるシンプルで扱いやすい「GNU nano」です。
シェルスクリプトマガジン Vol.84は以下のリンク先でご購入できます。![]()
![]()
図12 nanoの設定ファイル「~/.nanorc」の記述例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# バックアップファイルを作成 set backup # 各行の左に行番号を表示 set linenumbers # 文字列検索時に大文字小文字を区別 set casesensitive # 長い行を画面の右端で折り返す set softwrap # オートインデント set autoindent # タブサイズを「4」に(デフォルトは「8」) set tabsize 4 # マウスのサポートを有効に set mouse |

004 レポート オープンソースLLM「MPT-7B」登場
005 レポート メタバースファッションフェア初開催
006 製品レビュー 調理家電「コーヒー豆焙煎機 MR-F60A」
007 NEWS FLASH
008 特集1 pandasを使いこなそう/鶴長鎮一
020 特集2 AutoML徹底解説 応用編/田村孝、山口武彦、新田陸、細野友基
034 特別企画 Raspberry Piを100%活用しよう 拡大版/米田聡 コード掲載
046 注目技術「XR BASE」/井上香
048 Pythonあれこれ/飯尾淳 コード掲載
054 法林浩之のFIGHTING TALKS/法林浩之
056 中小企業手作りIT化奮戦記/菅雄一 コード掲載
060 ツールキット/桑原滝弥、イケヤシロウ
062 タイ語から分かる現地生活/つじみき
068 香川大学SLPからお届け!/三井颯剛 コード掲載
075 Hello Nogyo!
076 行動経済学と心理学で円滑に業務を遂行/請園正敏
080 Linux定番エディタ入門/大津真 コード掲載
086 AWKでデジタル信号処理/斉藤博文 コード掲載
094 Techパズル/gori.sh
095 コラム「ユニケージにおける独特なドキュメント」/シェル魔人
著者:米田 聡
小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。本企画では、前回に引き続き、ラズパイと連携して使用するマイコンボードを扱います。
シェルスクリプトマガジン Vol.84は以下のリンク先でご購入できます。![]()
![]()
図8 I★2★CアドレスとGPIOピン番号の設定(ADRS2040U_i2c.h)
|
1 2 3 4 5 6 |
// ADRS2040U I2Cアドレス #define I2C0_SLAVE_ADDR 0x41 // I2Cで使うGPIO #define GPIO_SDA0 0 #define GPIO_SCK0 1 |
図9 I2CとI2C Slaveライブラリの初期化
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include <stdio.h> #include <pico/stdlib.h> #include "hardware/i2c.h" ← hardware_i2cライブラリのヘッダーファイル #include "pico/i2c_slave.h" ← I2C Slaveライブラリのヘッダーファイル (略) #include "ADRS2040U_i2c.h" (略) static void i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event) ← I★★2C割り込みコールバック関数 { (略) } void i2c_setup(void) { i2c_init(i2c0, 100 * 1000); ← I★2★Cコントローラの初期化(通信速度100Kビット/秒) gpio_set_function(GPIO_SDA0, GPIO_FUNC_I2C); ← I★2★Cで利用するGPIOピンの設定 gpio_set_function(GPIO_SCK0, GPIO_FUNC_I2C); // ADRS2040Uでは基板上でプルアップされているので // プルアップを無効化する gpio_disable_pulls(GPIO_SDA0); gpio_disable_pulls(GPIO_SCK0); (略) // I2Cスレーブ初期化 i2c_slave_init(i2c0, I2C0_SLAVE_ADDR, &i2c_slave_handler); } |
図10 コールバック関数「i2c_slave_handler()」
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
static void i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event) { (略) switch(event) { // I2Cデータ受信 case I2C_SLAVE_RECEIVE: (略) break; // I2Cデータ要求 case I2C_SLAVE_REQUEST: (略) break; // STOP or RESTARTコンデション case I2C_SLAVE_FINISH: break; default: break; } } |
図11 ADCの初期化で呼び出す関数
|
1 2 3 4 |
adc_init(); adc_gpio_init(26); adc_select_input(0); |
図12 adc_fifo_setup()関数の引数パラメータ
|
1 2 3 4 5 6 7 |
adc_fifo_setup( en, dreq_en, dreq_threash, error_in_fifo, byte_shift ); |
図13 ADCの受信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 |
uint16_t adc_buffer[1024]; static int p = 0; (略) // ADC FIFO割り込み関数 void adc_interrupt(void) { // FIFOが空になるまでデータをadc_bufferに読み取る while(! adc_fifo_is_empty()) { adc_buffer[p++] = adc_fifo_get() & 0xFFF; } } void main(void) { (略) adc_fifo_setup(true, false, 1, true, false); (略) // 排他的割り込みの設定 irq_set_exclusive_handler(ADC_IRQ_FIFO, adc_interrupt); // 割り込みの有効化 irq_set_enabled(ADC_IRQ_FIFO, true); // フリーランスタート adc_run(true); (略) } |
図14 サンプリングレート設定のサンプルコード
|
1 2 3 4 |
int sample_rate = 1000; // 1000 サンプリング/秒 adc_clk = clock_get_hz(clk_adc); adc_set_clkdiv(adc_clk/sample_rate); |
図15 リングバッファのサンプルコード
|
1 2 3 4 5 6 7 8 9 |
uint16_t buffer[0x100]; int write_pointer, read_pointer; write_pointer = read_pointer = 0; // バッファの読み出し data = buffer[(read_pointer++) & 0xFF]; // バッファへの書き込み buffer[(write_pointer++) & 0xFF] = data; |
図16 クリティカルセクションの保護
|
1 2 3 4 5 |
mutex_enter_blocking(&my_mutex); ここがクリティカルセクション mutex_exit(&my_mutex); |
図17 サンプルファームウエアのレジスタ番号定義(ADRS2040U_i2c.h)
|
1 2 3 4 5 6 7 8 9 |
// I2Cレジスタ enum ADRS2040_CMD { ADRS2040_CMD_INVALID, // 無効 ADRS2040_CMD_ADC_START, // ADCフリーラン開始 ADRS2040_CMD_ADC_STOP, // ADCフリーラン停止 ADRS2040_CMD_SET_RATE, // サンプリングレート設定 ADRS2040_CMD_GET_COUNT, // バッファデータ数 ADRS2040_CMD_GET_VALUE, // ADCデータ読み取り }; |
図18 I2Cスレーブ動作時に使えるFIFOバッファ読み書き関数
|
1 2 3 4 5 6 7 8 |
// 1バイト読みだし i2c_read_byte_raw(i2c_inst_t *); // 指定バイト数の読み出し i2c_read_raw_blocking(i2c_inst_t *, uint8_t *, size_t); // 1バイト書き込み i2c_write_byte_raw(i2c_inst_t *, uint8_t); // 指定バイト数の書き込み i2c_write_raw_blocking(i2c_inst_t *, uint8_t *, size_t); |
図19 サンプルファームウエアのi2c_slave_handler()関数とその関連個所(main.cpp)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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 |
// ADCドライバ ADC_Driver adcd; typedef union { uint16_t d; uint8_t b[2]; } I2C_WORD_t; static void i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event) { static uint8_t ADRS2040U_cmd = ADRS2040_CMD_INVALID; switch(event) { // I2Cデータ受信 case I2C_SLAVE_RECEIVE: if(ADRS2040U_cmd == ADRS2040_CMD_INVALID) { ADRS2040U_cmd = i2c_read_byte_raw(i2c); } if(ADRS2040U_cmd == ADRS2040_CMD_ADC_START) { DEBUG_PRINT("ADC START\n"); adcd.run(true); ADRS2040U_cmd = ADRS2040_CMD_INVALID; } else if(ADRS2040U_cmd == ADRS2040_CMD_ADC_STOP) { DEBUG_PRINT("ADC STOP\n"); adcd.run(false); ADRS2040U_cmd = ADRS2040_CMD_INVALID; } else if(ADRS2040U_cmd == ADRS2040_CMD_SET_RATE) { I2C_WORD_t rate; i2c_read_raw_blocking(i2c, rate.b, sizeof(I2C_WORD_t)); DEBUG_PRINT("Rate = %d\n", rate.d * 10); adcd.set_sample_rate(rate.d * 10); ADRS2040U_cmd = ADRS2040_CMD_INVALID; } break; // I2Cデータ要求 case I2C_SLAVE_REQUEST: I2C_WORD_t sdata; sdata.d = 0; if(ADRS2040U_cmd == ADRS2040_CMD_GET_COUNT) { sdata.d = adcd.count(); } else if(ADRS2040U_cmd == ADRS2040_CMD_GET_VALUE) { int value = adcd.get_value(); if( value >= 0) { sdata.d = value & 0xFFF; } else { sdata.d = 0xFFFF; } } i2c_write_raw_blocking(i2c, sdata.b, sizeof(I2C_WORD_t)); ADRS2040U_cmd = ADRS2040_CMD_INVALID; break; // STOP or RESTARTコンデション case I2C_SLAVE_FINISH: break; default: break; } } (略) int main(void) { stdio_init_all(); i2c_setup(); while (true) { ; } } |
図20 受信FIFOバッファからデータを取り出すための前処理
|
1 2 3 4 5 6 7 |
i2c_hw_t *i2chw = i2c_get_hw(i2c0); uint32_t datacmd = i2chw->data_cmd; // FIFOからデータを取り出す uintu_t value = datacmd & I2C_IC_DATA_CMD_DAT_BITS; // 下位8ビットがデータ本体 if(datacmd & I2C_IC_DATA_CMD_FIRST_DATA_BYTE_BITS) { // I2Cアドレスに続く最初の書き込みバイト // つまりレジスタ番号 } |
図22 ADCへアクセスするサンプルプログラム(adcsample.py)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
from smbus2 import SMBus ADRS2040_ADDR=0x41 ADRS2040_CMD_INVALID = 0 ADRS2040_CMD_ADC_START = 1 ADRS2040_CMD_ADC_STOP = 2 ADRS2040_CMD_SET_RATE = 3 ADRS2040_CMD_GET_COUNT = 4 ADRS2040_CMD_GET_VALUE = 5 with SMBus(1) as i2c: # 500 spsで初期化・スタート self.i2c.write_word_data(ADRS2040_ADDR, ADRS2040_CMD_SET_RATE, 50) self.i2c.write_byte(ADRS2040_ADDR, ADRS2040_CMD_ADC_START) while True: nod = 0 nod = self.i2c.read_word_data(ADRS2040_ADDR,ADRS2040_CMD_GET_COUNT) for i in range(nod): value = self.i2c.read_word_data(ADRS2040_ADDR,ADRS2040_CMD_GET_VALUE) print(value) |
図23 ADCから取得したデータをグラフ化するサンプルプログラム(adctest.py)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
from smbus2 import SMBus import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtGui import numpy as np import sys import time # 定数 ADRS2040_ADDR=0x41 ADRS2040_CMD_INVALID = 0 ADRS2040_CMD_ADC_START = 1 ADRS2040_CMD_ADC_STOP = 2 ADRS2040_CMD_SET_RATE = 3 ADRS2040_CMD_GET_COUNT = 4 ADRS2040_CMD_GET_VALUE = 5 class ADCGraph: def __init__(self): # グラフウィンドウ self.win = pg.GraphicsWindow() self.win.setWindowTitle('ADC Input') self.plt = self.win.addPlot() self.plt.setYRange(0,1024) self.curve = self.plt.plot(pen=(0, 0, 255)) # グラフデータを用意 self.data = np.zeros(100) self.i2c = SMBus(1) # 500 spsで初期化・スタート self.i2c.write_word_data(ADRS2040_ADDR, ADRS2040_CMD_SET_RATE, 50) self.i2c.write_byte(ADRS2040_ADDR, ADRS2040_CMD_ADC_START) self.timer = QtCore.QTimer() self.timer.timeout.connect(self.update) # 10msごとに更新 self.timer.start(10) # グラフ更新 def update(self): nod = 0 nod = self.i2c.read_word_data(ADRS2040_ADDR,ADRS2040_CMD_GET_COUNT ) for i in range(nod): value = self.i2c.read_word_data(ADRS2040_ADDR,ADRS2040_CMD_GET_VALUE) self.data = np.delete(self.data, 0) self.data = np.append(self.data, value) self.curve.setData(self.data) if __name__ == "__main__": graphWin = ADCGraph() if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): QtGui.QApplication.instance().exec_() |
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温
かく見守ってください。皆さんと共に勉強していきましょう。第14回では、話題のAIチャットボット「ChatGPT」を使ったPythonプログ「ラムの作成にチャレンジします。ChatGPTは、どの程度のプログラムを生成してくれるのでしょうか。
シェルスクリプトマガジン Vol.84は以下のリンク先でご購入できます。![]()
![]()
図2 1回目にChatGPTが生成したPythonプログラム
|
1 2 3 4 5 6 7 8 9 |
import sys if len(sys.argv) < 3: print("Usage: python program_name.py num1 num2") else: num1 = float(sys.argv[1]) num2 = float(sys.argv[2]) result = num1 + num2 print("Result:", result) |
図3 2回目にChatGPTが生成したPythonプログラム
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import sys from fractions import Fraction if len(sys.argv) < 3: print("Usage: python program_name.py num1 num2") else: try: num1 = float(sys.argv[1]) num2 = float(sys.argv[2]) if num1.is_integer() and num2.is_integer(): num1, num2 = int(num1), int(num2) elif '/' in sys.argv[1] and '/' in sys.argv[2]: num1 = Fraction(sys.argv[1]) num2 = Fraction(sys.argv[2]) result = num1 + num2 print("Result:", result) except ValueError: print("Please enter numbers only.") |
図4 3回目にChatGPTが生成したPythonプログラム
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import sys from fractions import Fraction if len(sys.argv) < 3: print("Usage: python program_name.py num1 num2") else: try: num1 = 0 if '/' in sys.argv[1]: num1 = Fraction(sys.argv[1]) else: num1 = float(sys.argv[1]) num2 = 0 if '/' in sys.argv[2]: num2 = Fraction(sys.argv[2]) else: num2 = float(sys.argv[2]) result = num1 + num2 print("Result:", result) except ValueError: print("Please enter numbers only.") |
図5 4回目にChatGPTが生成した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 sys from fractions import Fraction def check_arg_validity(arg): try: if '/' in arg: num = Fraction(arg) else: num = float(arg) return num except ValueError: return None if len(sys.argv) < 3: print("Usage: python program_name.py num1 num2") else: try: num1 = check_arg_validity(sys.argv[1]) num2 = check_arg_validity(sys.argv[2]) if num1 is None or num2 is None: print("Please enter numbers only.") else: result = num1 + num2 print("Result:", result) |
図6 5回目にChatGPTが生成した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 sys from fractions import Fraction def check_number(num_str): try: float(num_str) except ValueError: try: Fraction(num_str) except ValueError: print(f"{num_str} is not a valid number.") return None return num_str def add2(num1, num2): result = Fraction(num1) + Fraction(num2) print(f"{num1} + {num2} = {result}") if __name__ == '__main__': num1 = check_number(sys.argv[1]) num2 = check_number(sys.argv[2]) if num1 and num2: add2(num1, num2) |
筆者:菅 雄一
自社サーバーからレンタルサーバーへの移行が世の流れだ。本連載の第58回「PHP+MySQL環境にシステムを移行」に書いたが、筆者の勤務先でも保守作業の軽減や故障回避のためにレンタルサーバーに移行する方針になった。2023年1月に、四苦八苦しながら顧客向けWeb検索システムを移行し、ようやくレンタルサーバーへの移行が一段落した。今回は、この難航した移行作業について書くことにする。
シェルスクリプトマガジン Vol.84は以下のリンク先でご購入できます。![]()
![]()
図2 バックアップファイル内の文字コード設定を変更
|
1 2 3 4 5 6 7 8 9 10 |
-- -- PostgreSQL database dump -- SET client_encoding = 'EUC'; SET standard_conforming_strings = off; SET check_function_bodies = false; SET client_min_messages = warning; SET escape_string_warning = off; (略) |
著者:三井 颯剛
SLPでは、Webページの公開などに使用しているサーバーを立て直す計画が進行中です。再建後のサーバーでは、「Traefik(トラフィック) 」というコンテナ環境向けのリバースプロキシソフトウエアを採用する予定です。今回は、Dockerでサーバーコンテナを稼働させて、それに対するアクセスをTraefikで制御する場合を例に、Traefikの利用方法について紹介します。
シェルスクリプトマガジン Vol.84は以下のリンク先でご購入できます。![]()
![]()
図3 「docker-compose.yml」ファイルに記述する設定
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
services: reverse-proxy: image: traefik:latest restart: always ports: - "80:80" environment: - TZ=Asia/Tokyo volumes: - /var/run/docker.sock:/var/run/docker.sock command: - "--providers.docker.exposedbydefault=false" - "--entrypoints.web.address=:80" whoami: image: traefik/whoami labels: - "traefik.enable=true" - "traefik.http.routers.whoami.rule=PathPrefix(`/whoami`)" - "traefik.http.routers.whoami.entrypoints=web" |
図6 ダッシュボードを有効にする場合の「docker-compose.yml」ファイルの記述
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
services: reverse-proxy: image: traefik:latest restart: always ports: - "80:80" - "8080:8080" environment: - TZ=Asia/Tokyo volumes: - /var/run/docker.sock:/var/run/docker.sock command: - "--providers.docker.exposedbydefault=false" - "--entrypoints.web.address=:80" - "--entrypoints.dashboard.address=:8080" - "--api.dashboard=true" labels: - "traefik.enable=true" - "traefik.http.routers.api.entrypoints=dashboard" - "traefik.http.routers.api.rule=Host(`localhost`)" - "traefik.http.routers.api.service=api@internal" whoami: image: traefik/whoami labels: - "traefik.enable=true" - "traefik.http.routers.whoami.rule=PathPrefix(`/whoami`)" - "traefik.http.routers.whoami.entrypoints=web" |
図9 「traefik.yml」ファイルに記述する内容
|
1 2 3 4 5 6 7 8 9 10 |
providers: docker: exposedByDefault: false entryPoints: web: address: ":80" dashboard: address: ":8080" api: dashboard: true |
図10 設定を分離した場合の「docker-compose.yml」ファイルの記述例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
services: reverse-proxy: image: traefik:latest restart: always ports: - "80:80" - "8080:8080" environment: - TZ=Asia/Tokyo volumes: - /var/run/docker.sock:/var/run/docker.sock - ./traefik.yml:/etc/traefik/traefik.yml labels: - "traefik.enable=true" - "traefik.http.routers.api.entrypoints=dashboard" - "traefik.http.routers.api.rule=Host(`localhost`)" - "traefik.http.routers.api.service=api@internal" whoami: image: traefik/whoami labels: - "traefik.enable=true" - "traefik.http.routers.whoami.rule=PathPrefix(`/whoami`)" - "traefik.http.routers.whoami.entrypoints=web" - "traefik.http.services.myservice.loadbalancer.server.port=80" |
著者:斉藤 博文
プログラミング言語「AWK」は、データストリーム(データの流れ)を逐次処理するのに適しています。本連載では、電子回路の分野でその特徴を生かし、シェルスクリプトを組み合わせてデジタル信号を処理します。最終回は前回の続きとして、心電図データの解析について解説します。
シェルスクリプトマガジン Vol.84は以下のリンク先でご購入できます。![]()
![]()
図5 ecg.awk内のlpf()関数
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# low pass filter function lpf(arr_x, arr_y, idx_x, idx_y, ord, len_x, len_y, _ret, _gain) { _ret = 0.0; _gain = 1.0 / (ord * ord); _ret += 2.0 * get_buffer(arr_y, idx_y - 1, len_y); _ret -= 1.0 * get_buffer(arr_y, idx_y - 2, len_y); _ret += 1.0 * _gain * arr_x[idx_x]; _ret -= 2.0 * _gain * get_buffer(arr_x, idx_x - ord, len_x); _ret += 1.0 * _gain * get_buffer(arr_x, idx_x - 2 * ord, len_x); return _ret; } |
図6 ecg.awk内の最終結果を出力するprintf()文で群遅延を補正
|
1 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 |
# print results printf("%f,%f,%f,%f,%f,%f,%f,%f\n", get_buffer( \ arr_data_raw, idx_data - (val_gd_lpf + val_gd_hpf + val_gd_dif + val_gd_sma) - 2 * val_fs, len_data), get_buffer( \ arr_data_lpf, idx_data - (val_gd_hpf + val_gd_dif + val_gd_sma) - 2 * val_fs, len_data), get_buffer( \ arr_data_hpf, idx_data - (val_gd_dif + val_gd_sma) - 2 * val_fs, len_data), get_buffer( \ arr_data_dif, idx_data - val_gd_sma - 2 * val_fs, len_data), get_buffer( \ arr_data_squ, idx_data - val_gd_sma - 2 * val_fs, len_data), get_buffer( \ arr_data_sma, idx_data - 2 * val_fs, len_data), cnt_rri_curr * 0.005 * 1000, get_buffer( \ arr_data_flg, idx_data - (val_gd_dif + val_gd_sma) - 2 * val_fs, len_data)); } |
図12 ecg.awk内のしきい値(val_data_sma_threshold)を設定している箇所
|
1 2 3 4 5 6 7 8 9 |
# update threshold every interval if (num_data % tap_interval == 0) { val_data_sma_threshold = val_data_sma_max * val_peak_rate; val_data_sma_max = 0; } else { if (val_data_sma_max < arr_data_sma[idx_data]) { val_data_sma_max = arr_data_sma[idx_data]; } } |
図13 ecg.awk内のval_data_flgを「1」に設定している箇所
|
1 2 3 4 5 |
if (val_data_sma_curr > val_data_sma_threshold && val_data_sma_threshold >= val_data_sma_last) { val_data_hpf_max = 0; idx_data_hpf_max = 0; val_data_flg = 1; } |
図14 ecg.awk内のidx_data_hpf_maxを求めている箇所
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# search maximum index if (val_data_flg == 1) { val_data_hpf_curr = get_buffer(arr_data_hpf, idx_data - (val_gd_dif + val_gd_sma), len_data); idx_data_hpf_curr = idx_data - (val_gd_dif + val_gd_sma); idx_data_hpf_curr = idx_data_hpf_curr >= 0 ? idx_data_hpf_curr : idx_data_hpf_curr + len_data; if (val_data_hpf_max < val_data_hpf_curr) { val_data_hpf_max = val_data_hpf_curr; idx_data_hpf_max = idx_data_hpf_curr; } } |
図15 ecg.awk内のarr_data_flg[idx_data_hpf_max]を「1」に設定している箇所
|
1 2 3 4 |
# end point to detect main peak if (val_data_sma_curr < val_data_sma_threshold && val_data_sma_threshold <= val_data_sma_last) { arr_data_flg[idx_data_hpf_max] = 1; |
図18 ecg.awk内のRRIを求めている箇所
|
1 2 3 4 5 6 7 8 9 |
# calculate peak to peak interval if (idx_data - idx_data_hpf_max >= 0) { cnt_peak_curr = num_data - (idx_data - idx_data_hpf_max); } else { cnt_peak_curr = num_data - (idx_data - idx_data_hpf_max + len_data); } cnt_rri_curr = cnt_peak_curr - cnt_peak_last; cnt_peak_last = cnt_peak_curr; |
同企画で扱いました、PlatformIO向けのRP2040用ソフトウエア基盤(フレームワーク)が「https://github.com/Wiz-IO/wizio-pico」から入手できなくなってしまいました。原作者に問い合わせましたが、返答はなく理由は不明です。ご迷惑をおかけして申し訳ございません。
「https://github.com/Wiz-IO/wizio-pico」にアクセスして図1のように表示されたときは、以下の方法で入手・導入してください。
(1)特別企画記事の通りに開発ツール「Visual Studio Code」(VSCode)とWindows版Gitクライアント「Git for Windows」をインストールします。
(2)PlatformIO向けのRP2040用ソフトウエア基盤(フレームワーク)を「https://future.quake4.jp/wizio-pico.zip」からダウンロードします。
(3)VSCodeを起動しているならいったん終了します。
(4)「C:¥Users¥ユーザー名¥.platformio¥」(ユーザー名はWindowsのアカウント名)の下にダウンロードした「wizio-pico.zip」ファイルを展開します。

図1 ページが見つからない場合の表示
情報は随時更新致します。
カテゴリー コード のアーカイブを表示しています。