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

test

作りながら学ぶVue.js入門(Vol.100掲載)

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

著者:大津 真

JavaScriptフレームワークの中でも、学びやすさと柔軟さで人気を集めている「Vue.js」。本連載では、Vue.jsの基礎から実践的な使い方までを、分かりやすく丁寧に解説していきます。一緒にフロントエンド開発の楽しさを体験してみましょう。第5回では、v-modelディレクティブによる双方向データバインディングと、計算結果をプロパティとして返す算出プロパティについて説明します。

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

図1 「v-model1/src/App.vue」ファイルのコード

図5 v-modelディレクティブを使う場合と使わない場合のコードの比較

図6 「kaibun1/src/App.vue」ファイルのコード

図10 「computed-prop1/src/App.vue」ファイルのコード

図11 「computed-prop2/src/App.vue」ファイルのコード

図12 「computed-prop3/src/App.vue」ファイルのコード

図13 「computed-prop4/src/App.vue」ファイルのコード

図17 「reiwa1/src/App.vue」ファイルのコード

Vol.100

投稿日:2026.01.25 | カテゴリー: バックナンバー

 Linuxでは、コマンドをパイプ(|)などでつなぐことで、単体では実現できない処理を1行で実行できます。こうした「ワンライナー」を使いこなすことで、端末操作の自由度が大きく広がり、日々の作業効率も向上します。
特集1では、このワンライナーを実務で活かすために、10分野に分けて100本紹介します。ファイルシステム、プロセス監視、ネットワーク管理、ログ解析、セキュリティ、バックアップ、テキスト操作、ネットワーク診断、システム情報、計算・日時処理など、サーバー運用の現場で頻出する領域を幅広くカバーしています。
 一方、開発の現場では生成AIの活用が急速に進み、プログラムコーディングを支援するツールが続々と登場しています。特に近年は、コマンドラインで利用できるAIコーディング支援ツールも増え、AIが開発を支える時代が目前に迫っています。特集2では、その代表的な存在である「Claude Code」の始め方を分かりやすく紹介します。
 さらに特別企画では、生成AIを活用したサービスや、最新のクラウド基盤にも注目します。1本目では、さくらインターネットが提供するフルマネージド生成AIサービス「さくらのAI Engine」を取り上げ、サービスの概要とAPIを使ったプログラミングの基本を解説します。
 続く2本目では、企業の電話基盤として長く利用されてきたPBXのクラウド移行に焦点を当て、AIを搭載したクラウドPBX「Zoom Phone」を紹介します。従来型PBXとの違い、導入メリット、クラウド化による運用負荷の軽減、AIによる通話支援など、これからのビジネスコミュニケーションを支える新しい電話基盤の特徴を踏まえて解説します。
 読み応え十分のシェルスクリプトマガジン Vol.100。最終号ではありますが、いつも通りの充実した内容をお届けします。どうぞゆっくりお楽しみください。

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

レポート2(Vol.100掲載)

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

著者:末安 泰三

米Microsoft社は2026年1月8日、コードエディタ「Virtual Studio Code」(以下、VS Code)のバージョン1.108を公開した。同版の大きな特徴は、AIエージェントに業務知識やワークフローを伝える標準的な仕組みの「Agent Skills」に対応したことである。

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

図1 「SKILL.md」ファイルに記述する設定の例

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

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


004 レポート Open Source Summit Japan開催
005 レポート Agent Skills対応VSCode コード掲載
006 製品レビュー スマートカメラ「GH-SMCC-WH」
007 NEWS FLASH
008 特集1 シェルワンライナー100選/麻生二郎
018 特集2 Claude Codeの始め方/しょっさん
024 特別企画 「さくらのAI Engine」で始める生成AI APIプログラミング/前佛雅人
031 インタビュー テイラー 柴田 陽氏
032 特別企画 クラウドPBXのZoom Phone/坂井悠樹、福本勝功
042 Generative AIコンパス by GenAI(ジェナイ)/為藤アキラ コード掲載
048 ARM/RISC-V搭載コンピュータボード/米田聡
052 Pythonあれこれ/飯尾淳 コード掲載
058 作りながら学ぶVue.js入門/大津真 コード掲載
066 知っておきたいシェル関連技術/麻生二郎
072 Techパズル/gori.sh
073 コラム「SEユニケージ」/シェル魔人

Pythonあれこれ(Vol.100掲載)

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

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第30回では、筆者らが実施している、異文化間交流を体験する国際プロジェクトの評価アンケートを対象に、回答状況を分析してみます。

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

図4 NPSを計算するコード

図5 NPS計算用のnps()関数を定義するコード

図6 感情分析をするコード

図7 文単位に感情分析をするコード

図8 ネガティブな文を含むコメントを抽出するコード

図10 コメントの長さを国別に集計するコード

Generative AIコンパス(Vol.100掲載)

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

著者:為藤 アキラ

AI技術の進化は、社会や産業のあり方を大きく変えつつあります。本連載では、国内外の最新動向や実践的な技術トピックを、日本の現場視点も交えながら分かりやすく解説します。執筆は、産学連携で生成AI活用を推進するGenerative AI Japan(略称:GenAI)のメンバーが担当します。第2回では、生成
AIと人間の役割分担を3段階の自動化レベルで整理し、HITL(Human-in-the-Loop)を前提とした改善サイクルの回し方を解説します。

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

図4 自動化レベル1用のプロンプト例(ドラフト作成+自己チェック)

図6 自動化レベル2用のプロンプト例(条件付き承認フロー)

図8 自動化レベル3用のプロンプト例(自律実行の監視レポート)

Vol.100 補足情報

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

連載 作りながら学ぶVue.js入門

記事で紹介したサンプルコードの完全版と、その実行方法を記したMarkdown文書をまとめたZIPファイルは、ここからダウンロードできます。

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

[インタビュー]AIによるソフトウエアテスト自動化基盤を提供するグローバル企業 米Autify社

投稿日:2026.01.9 | カテゴリー: 記事

 米Autify社は、AIを活用したソフトウエアテスト自動化基盤を提供する、日本人創業のグローバル企業です。2016年に米国サンフランシスコで創業し、2019年頃からソフトウエアテストの非効率性というIT業界共通の課題解決に取り組んでいます。創業メンバーは日本人を中心とし、現在は米国と日本の両拠点で事業を展開しています。金融系や保険系など、ミッションクリティカルな大規模アプリケーションを持つ大手企業を含む、国内外の多くの採用事例があります。
 今回、Autify社 CEO/共同創業者の近澤 良氏(図1)に事業内容や創業の経緯、製品ラインナップ、日本と海外との違い、そしてテスト自動化やAIの未来について話を聞きました(聞き手は麻生)。

図1 米Autify社 CEO/共同創業者の近澤 良氏

――事業内容と、創業の経緯について教えてください。

 我々は、AIを使ってソフトウエアテストを自動化・効率化する事業を展開しています。会社自体は2016年に創業しましたが、当初は今とは異なる事業を行っていました。転機となったのは2018年、サンフランシスコのAlchemist Accelerator社によるシードアクセラレータ(早期に事業を立ち上げる支援プログラム)に日本人として初めて参加したことです。
 私自身、エンジニアとして日本、シンガポール、アメリカの3カ国で10年以上開発に携わってきました。テストはどこでも非常に大変な作業でした。そんな中、米Gartner社のレポートで「開発チームにAIデベロッパが加わる時代が来る」という予測を目にし、テストは必ずAIで代替されると確信しました。そして2019年に現在の事業を開始し、順調に成長を続けています。

――具体的な製品ラインナップと、それぞれの特徴を教えてください。

 主に「Autify Nexus」「Autify Genesis」、そしてAIエージェント機能を展開しています。
 Autify Nexus は、Autify社の現行主力製品であり、Webアプリケーションテストをノーコードで自動化する「Autify NoCode Web」の後継に位置づけられています。米 Microsoft 社が開発したオープンソースのテスト自動化フレームワーク「Playwright」を基盤としており、自然言語でAIエージェントに指示するだけでテストシナリオを生成できます。
 ノーコード操作にも対応し、ユーザーインタフェースの変更によるテストの破綻を抑える仕組みを備えることで、テストメンテナンスの負荷を大幅に軽減します。特に、オープンソースの柔軟性を活かしつつ、コード記述の煩雑さをAIが解消する点も高く評価されています。
 Autify Genesisは、仕様書を読み込んで内容や意図を理解し、テストケースやテストシナリオを自動生成するAIです。
 AIエージェント機能は、最終的には人がコードを書く必要すらなくなり、100台のAIエージェントが代わりにテストを実行し、バグを分析する未来を目指しています。

――日本と海外でのテスト自動化への意識に差はありますか。

 非常に大きな差があります。北米は金融機関であっても自社で自動化スクリプトを回すのが当たり前で、非常に進んでいます。
 一方、日本は依然としてベンダー依存が強く、自動化にたどり着いていない企業がほとんどです。しかし、開発コストの半分をテストに費やしている現状に、多くの企業が課題を感じています。我々は、ツールとプロフェッショナルサービスの両面から、この50%のコストをAIで効率化し、開発スピードを加速させる支援をしています。

――今後、ソフトウエア開発におけるテストの役割はどう変わっていくのでしょうか。

 将来的には、仕様とテストを書けば開発は終了という世界になるでしょう。AIがコードを生成するようになると、その成果物が正しいかどうかを判断する受け入れ要件の重要性がこれまで以上に高まります。人が実装の細かいコードを見る必要はなくなり、AIにどのような指示を出し、どのテストが通ればOKとするかという上流工程に集約されていくはずです。

――AIに期待すること、あるいは現在のAIの限界について教えてください。

 AIの精度や扱える情報量は日々向上しています。一方で、プロンプトに必ずしも従わないといった確率的な振る舞いは依然として課題です。現場では、AIにルールを繰り返し想起させるなどの工夫で対応しています。将来的には、AIが仕様を完全に理解し、自律的に動作するプラットフォームへと進化させていきたいと考えています。

シェルスクリプトマガジン休刊のお知らせ

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

拝啓
平素より格別のご愛読を賜り、心より御礼申し上げます。
このたび、2026年2月号(Vol.100)をもちまして「シェルスクリプトマガジン」は休刊させていただくこととなりました。創刊以来、長きにわたり多くの読者の皆様に支えられ、誌面を通じて情報をお届けできましたことは、編集部一同にとってかけがえのない喜びであり、誇りでございます。
時代の変化や読者ニーズの多様化に応えるべく、私たちも挑戦を続けてまいりましたが、今後は新たな形での情報発信に力を注ぐため、休刊という決断に至りました。
これまでご愛顧いただいた読者の皆様、執筆者の方々、関係者の皆様に心より感謝申し上げます。誌面は一区切りとなりますが、培った経験を活かし、皆様に役立つ情報をお届けできるよう新たな挑戦を続けてまいります。
末筆ながら、皆様のご健勝とご多幸をお祈り申し上げます。

今後の計画ですが、新しい読者層に向けたIT情報サイトを立ち上げる予定です。また、公式サイトや公式Xにおいても、デジタル版として情報発信を続けていくことも計画中です。
                                   敬具                       

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

Markdownを活用する(Vol.99掲載)

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

筆者:藤原 由来

本連載では文書の装飾・構造付けを手軽に行える記法であるMarkdownを用いて、さまざまな文書や成果物を作成する方法を紹介します。前回に引き続き、文書変換ツール「Pandoc」の文書作成方法を紹介します。今回はPandocのフィルタ機能により柔軟な文書変換を実施する方法を解説します。

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

図4 入力とするMarkdownテキストファイル(sample-input.md)

# カレーの作り方

おいしいカレーを作りましょう。

## 材料

- 牛肉
- 玉ねぎ
- にんじん
- カレールー
- 水

図5 フィルタ適用後に出力されるMarkdownテキストファイル(sample-output.md)

# カレーの作り方

おいしいカレーを作りましょう。

## ★材料

- 牛肉
- 玉ねぎ
- にんじん
- カレールー
- 水

図7 Pythonで記述したJSONフィルタ

#!/usr/bin/env -S uv run --script
import panflute as pf

def add_star_to_header2(elem, doc):
    """
    レベル2の見出しに星印「★」を追加するフィルタ関数
    """
    # 要素の種類がHeaderで、レベルが2の場合
    if isinstance(elem, pf.Header) and elem.level == 2:
        # 星印を表す要素を生成
        star = pf.Str("★")

        # 見出しの内容の先頭に星印を追加
        elem.content.insert(0, star)
    return elem

def main(doc=None):
    """
    メイン関数: フィルタを実行
    """
    return pf.run_filter(add_star_to_header2, doc=doc)

# スクリプトが直接実行された場合にmain()を呼び出す
if __name__ == "__main__":
    main()

図9 Luaフィルタの内容(add_star.lua)

function Pandoc(doc)
  -- 各ブロックについて処理
  for i, elem in ipairs(doc.blocks) do
    -- 要素の書類がHeaderで、レベルが2の場合
    if elem.t == "Header" and elem.level == 2 then
      -- 内容の先頭に星印「★」を追加
      table.insert(elem.content, 1, pandoc.Str("★"))
    end
  end
  return doc
end

図10 別の記述を用いたLuaフィルタの内容(add_star2.lua)

function Header(elem)
  -- 見出しのレベルが2の場合
  if elem.level == 2 then
    -- 内容の先頭に星印「★」を追加
    table.insert(elem.content, 1, pandoc.Str("★"))
  end
  return elem
end

図A-2 図表の定義と、図表の参照を組み合わせた例

[@fig:sample]は、サンプル図を示しています。
[@tbl:sample]は、サンプル表を示しています。

![サンプル図](cat1.png){#fig:sample}

| ヘッダー1 | ヘッダー2 |
|-----------|-----------|
| データ1   | データ2   |

: サンプル表 {#tbl:sample} 

Pythonあれこれ(Vol.99掲載)

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

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第29回では、物体の動きや力の作用など、現実世界の物理法則を数値的に計算してシミュレーションする「物理演算」にチャレンジします。

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

図6 playground.pyファイルの最後の部分にあるコード

def main():
    demo = PhysicsDemo()
    demo.run()

if __name__ == "__main__":
    doprof = 0
    if not doprof:
        main()
    else:
        import cProfile
        import pstats

        prof = cProfile.run("main()", "profile.prof")
        stats = pstats.Stats("profile.prof")
        stats.strip_dirs()
        stats.sort_stats("cumulative", "time", "calls")
        stats.print_stats(30)

図7 PhysicsDemoクラスのrun()メソッドのコード

def run(self):
    while self.running:
        self.loop()

図8 PhysicsDemoクラスのloop()メソッドのコード

def loop(self):
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            self.running = False
(略)
            self.space.gravity = g.rotated_degrees(45)

    mpos = pygame.mouse.get_pos()
    if pygame.key.get_mods() & \
          pygame.KMOD_SHIFT and pygame.mouse.get_pressed()[2]:
        p = self.flipyv(Vec2d(*mpos))
        self.poly_points.append(p)
    hit = self.space.point_query_nearest(
        self.flipyv(Vec2d(*mpos)), 0, pm.ShapeFilter()
    )
    if hit != None:
        self.shape_to_remove = hit.shape
    else:
        self.shape_to_remove = None

    ### Update physics
    if self.run_physics:
        x = 1
        dt = 1.0 / 60.0 / x
        for x in range(x):
            self.space.step(dt)
            for ball in self.balls:
                # ball.body.reset_forces()
                pass
            for poly in self.polys:
                # poly.body.reset_forces()
                pass

    ### Draw stuff
    self.draw()

    ### Check for objects outside of the screen,
    #   we can remove those Balls
    xs = []
    for ball in self.balls:
        if (
            ball.body.position.x < -1000
            or ball.body.position.x > 1000
            or ball.body.position.y < -1000
            or ball.body.position.y > 1000
        ):
            xs.append(ball)
    for ball in xs:
        self.space.remove(ball, ball.body)
        self.balls.remove(ball)

    # Polys
(略)

    ### Tick clock and update fps in title
    self.clock.tick(50)
    pygame.display.set_caption("fps: " + str(self.clock.get_fps()))

図9 create_ball()メソッドのコード

def create_ball(self, point, mass=1.0, radius=15.0):

    moment = pm.moment_for_circle(mass, 0.0, radius)
    ball_body = pm.Body(mass, moment)
    ball_body.position = Vec2d(*point)

    ball_shape = pm.Circle(ball_body, radius)
    ball_shape.friction = 1.5
    ball_shape.collision_type = COLLTYPE_DEFAULT
    self.space.add(ball_body, ball_shape)
    return ball_shape

図10 PhysicsDemoクラスのコンストラクタのコード

def __init__(self):
    self.running = True
    ### Init pygame and create screen
    pygame.init()
    self.w, self.h = 600, 600
    self.screen = pygame.display.set_mode((self.w, self.h))
    self.clock = pygame.time.Clock()

    ### Init pymunk and create space
    self.space = pm.Space()
    self.space.gravity = (0.0, -900.0)

    ### Walls
    self.walls = []
    self.create_wall_segments([(100, 50), (500, 50)])

    ## Balls
    # balls = [createBall(space, (100,300))]
    self.balls = []

    ### Polys
    self.polys = []
    h = 10
    for y in range(1, h):
        # for x in range(1, y):
        x = 0
        s = 10
        p = Vec2d(300, 40) + Vec2d(0, y * s * 2)
        self.polys.append(self.create_box(p, size=s, mass=1))
 
    self.run_physics = True

    ### Wall under construction
    self.wall_points = []
    ### Poly under construction
    self.poly_points = []

    self.shape_to_remove = None
    self.mouse_contact = None

図11 playground.pyファイルの変更差分

133a134,135
>         pygame.draw.circle(self.screen, 
>             pygame.Color("skyblue"), p, int(r), 0)
140c142
<         pygame.draw.lines(self.screen, pygame.Color("lightgray"), False, [pv1, pv2])
---
>         pygame.draw.lines(self.screen, pygame.Color("gray"), False, [pv1, pv2], 3)
148c150,151
<             color = pygame.Color("green")
---
>             color = pygame.Color("forestgreen")
>             color2 = pygame.Color("lightgreen")
151c154,156
<         pygame.draw.lines(self.screen, color, False, ps)
---
>             color2 = pygame.Color("orange")
>         pygame.draw.polygon(self.screen, color2, ps, 0)
>         pygame.draw.polygon(self.screen, color, ps, 2)

※「&#91;」を「[」に、「&lt; 」を「<」に置き換えてください。

作りながら学ぶVue.js入門(Vol.99掲載)

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

著者:大津 真

JavaScriptフレームワークの中でも、学びやすさと柔軟さで人気を集めている「Vue.js」。本連載では、Vue.jsの基礎から実践的な使い方までを、分かりやすく丁寧に解説していきます。一緒にフロントエンド開発の楽しさを体験してみましょう。第4回では、v-onディレクティブの活用と、VueアプリケーションからDOM(Document Object Model)を操作する方法について説明します。

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

図1 「event-props/src/App.vue」ファイルのコード

<script setup>
import { ref } from 'vue'

function eventTest(e) {
  console.log(e)
}
</script>

<template>
  <button @click="eventTest">Click me</button>
</template>

図1 「event-props/src/App.vue」ファイルのコード

<script setup>
import { ref } from 'vue'
const event = ref(null)

function eventTest(e) {
  event.value = e
}
</script>

<template>
  <div id="back" @click="eventTest" @dblclick="eventTest">
    <h1>Event Test</h1>
    <div id="box" @mouseover="eventTest" @mouseout="eventTest">
      <p>My Box</p>
    </div>
    <p>Event type: {{ event?.type }}</p>
    <p>x:{{ event?.clientX }} y: {{ event?.clientY }}</p>
  </div>
</template>

<style scoped>
#back {
  width: 100vw;
  height: 100vh;
}
#box {
  width: 100px;
  height: 100px;
  background-color: #f00;
  margin: 0 auto;
}
</style>

図6 「src/style.css」ファイルの変更例

(略)
#app {
  max-width: 1280px;
  margin: 0 auto;
  /* padding: 2rem; */  ←コメントにする
  text-align: center;
}
(略)

図7 useTemplateRef関数でDOM要素を取得するコードの例

<template>
(略)
  <img ref="fish" id="fish" src="./assets/fish1.jpg" />
(略)
</template>
<script setup>
(略)
  const fish = useTemplateRef("fish");
(略)
</script>

図8 「event-test2/src/App.vue」ファイルのコード

<!-- App.vue -->
<template>
  <div id="back" @click="moveImg">
    <img ref="fish" id="fish" src="./assets/fish1.jpg" />
  </div>
</template>

<script setup>
import { useTemplateRef, onMounted } from 'vue'
// テンプレート参照を取得
const fish_ref = useTemplateRef("fish")

function moveImg(e) {
  // transitionの設定
  const duration = "1s" // 移動時間
  fish_ref.value.style.transition = \
    left ${duration} ease, top ${duration} ease, \
    transform 0.3s ease

  // クリック位置に移動
  const x = e.clientX  - fish_ref.value.width / 2
  const y = e.clientY  - fish_ref.value.height / 2
  fish_ref.value.style.left = ${x}px
  fish_ref.value.style.top  = ${y}px

  // クリック方向に応じて向きを変える
  if (e.clientX < fish_ref.value.offsetLeft) {
    fish_ref.value.style.transform = 'scaleX(-1)' // 左向き
  } else {
    fish_ref.value.style.transform = 'scaleX(1)'  // 右向き
  }
}
</script>
<style scoped>
#back {
  position: fixed;
  inset: 0;
}
#fish {
  position: absolute;
  width: 100px;
  left:  100px; /* 初期位置 X */
  top:   100px; /* 初期位置 Y */
}
</style>

図10 HTMLテンプレート部分の変更箇所

(略)
<div id="back" @click="moveImg(false, $event)"
               @contextmenu="moveImg(true, $event)">
  <img ref="fish" id="fish" src="./assets/fish1.jpg" />
</div>
(略)

図11 JavaScript部分の変更箇所(抜粋)

(略)
function moveImg(isFast, e) {
  e.preventDefault(); // 右クリックメニューを防ぐ
(略)
  const duration = isFast ? "0.3s" : "0.8s";
  el.style.transition = left ${duration} ease, \
                        top ${duration} ease, \
                        transform 0.3s ease;
(略)

図12 不適切なコードの例

(略)
<script setup>
import { useTemplateRef, onMounted } from 'vue';

const fish = useTemplateRef("fish");

const el = fish.value
el.style.left = '100px'  // 左端の座標を設定
el.style.top  = '100px'  // 上端の座標を設定
(略)

図13 書き換えた部分のコード

onMounted(() => {
  const el = fish.value;
  if (el) {
    // 親要素の位置とサイズを取得
    const parentRect = el.offsetParent.getBoundingClientRect();
    // 初期位置を画面中央に設定
    const x = parentRect.width / 2 - el.width / 2;
    const y = parentRect.height / 2 - el.height / 2;
    el.style.left = ${x}px;
    el.style.top = ${y}px;
  }
});

※「&lt; 」を「<」に置き換えてください。

特集1 特集1 Playwright MCPによるWebシステム自動E2Eテスト(Vol.99記載)

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

著者:藤原 由来

Webシステムに対するE2E(End-to-End)テストの実施は、手間と労力のかかる作業です。しかし、生成AIを活用した「Playwright MCP」を使えば、誰でも簡単にE2Eテストの作成・自動実行が可能です。本特集では、Playwright MCPの基本的な使い方を紹介し、実際に日本語による指示だけでE2Eテストを作成・自動実行する方法を解説します。

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

図30 テストシナリオとなるプロンプト

@playwright-mcp Webブラウザを起動して、以下のWebアプリに対して、E2Eテストを実行してください。

## URL

http://localhost:3000/

## テストケース

- ログイン機能
  - ユーザー名 test / パスワード mypassword でログインできること
  - ユーザー名 test / パスワード bar の場合はエラーメッセージを出力すること
  - ユーザー名 foo  / パスワード mypassword の場合はエラーメッセージを出力すること
- ログアウト機能
  - ユーザー名 test / パスワード mypassword でログインし、ログアウトするとログイン画面に遷移すること
- TODO機能
  - タスクを作成できること
  - 作成済みのタスクを編集できること
  - タスクにチェックを入れると、完了済みの状態になること
  - 完了済みタスクを未完了に戻せること
  - タスクを削除できること

※「&#47;&#47;」を「//」に置き換えてください。

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

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

004 レポート 制作ソフト「Affinity」が無償化
005 レポート さくらのAI Engine一般提供開始
006 製品レビュー ポータブルSSD「SSD-SDHU3シリーズ」
007 NEWS FLASH
008 特集1 Playwright MCPによるWebシステム自動E2Eテスト/藤原由来 コード掲載
018 特別企画 Boxのワークフロー自動化とAIで業務効率化/藤井健志、葵健晴、武田新之助
026 ARM/RISC-V搭載コンピュータボード/米田聡
031 インタビュー パロアルトネットワークス Hiroshi Alley氏
032 Generative AIコンパス/為藤アキラ コード掲載
036 Markdownを活用する/藤原由来 コード掲載
046 Pythonあれこれ/飯尾淳 コード掲載
052 作りながら学ぶVue.js入門/大津真 コード掲載
060 知っておきたいシェル関連技術/麻生二郎 コード掲載
066 Techパズル
067 コラム「社内のERPプロジェクト」/シェル魔人

Vol.99

投稿日:2025.11.25 | カテゴリー: バックナンバー

 システム開発におけるテスト工程の中でも、最も手間と工数がかかるのがユーザーインタフェース(UI)のテストです。もし、言葉による指示だけでUIテストを作成し、自動実行できたらとても便利です。近年のAI(人工知能)の進化により、こうしたことが現実のものとなりつつあります。
 特集1では、AIを活用してUIテスト(End-to-Endテスト)を自動化できる「Playwright MCP」について、実例を交えながら解説します。
 特別企画では、クラウドストレージサービス「Box」が備えるワークフロー自動化とAI機能を紹介します。Boxは、単なるファイル保存の場にとどまらず、保存された文書を利用した承認フローなどの作成や自動実行を、自身の機能だけで実現できます。また、AI機能は、セキュリティを担保しながら、保存文書の検索、要約、抽出などを可能にし、ビジネスでの活用を支援します。
 このほか、二つの新連載が始まりました。国内外のAIに関する最新トレンドを分かりやすく紹介する「Generative AIコンパス」と、LinuxとRTOSが動作する小型コンピュータボード「Milk-V Duo 256M」と、電子工作を扱う「ARM/RISC-V搭載コンピュータボード」です。
 今回も読み応え十分のシェルスクリプトマガジン Vol.99。ぜひお見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

知っておきたいシェル関連技術(Vol.99掲載)

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

著者:麻生 二郎

シェルは、LinuxやUNIX系OSを操作するためのプログラムです。本連載では、クラウドやリモートなど、近年のコンピュータ環境でのシェル活用のヒントになる情報を扱っていきます。第4回は、crondによる定期ジョブ実行を管理するオープンソースソフトウエア「Cronicle」を紹介します。 

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

図4 /opt/cronicle/conf/config.jsonファイルの変更箇所

■STARTTLSの場合

"email_from": "Cronicle Admin <shell@example.com>",
"smtp_hostname": "smtp.example.com",
"mail_options":{
    "port":587,
    "secure": false,
    "auth":{
        "user":"shell@example.com",
        "pass":"password"
    }
},

■SMTPSの場合

"email_from": "Cronicle Admin <shell@example.com>",
"smtp_hostname": "smtp.example.com",
"mail_options":{
    "port":465,
    "secure": true,
    "auth":{
        "user":"shell@example.com",
        "pass":"password"
    }
},

Generative AIコンパス(Vol.99掲載)

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

筆者:為藤 アキラ

AI技術の進化は、社会や産業のあり方を大きく変えつつあります。本連載では、国内外の最新動向や実践的な技術トピックを、日本の現場視点も交えながら分かりやすく解説します。執筆は、産学連携で生成AI活用を推進するGenerative AI Japan(略称:GenAI)のメンバーが担当します。第1回では、人間がAIの処理に積極的に関与する仕組みである「HITL(Human In The Loop)」のパターンについて解説します。

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

図2 標準化したプロンプトのひな型の例

【入力】
目的/想定読者/素材/制約(文字数・禁止表現)/評価観点
【AI出力構成】
1. 本文案(Markdown形式・見出し3階層まで)
2. 自己検証結果
   - 不確実な主張に★マーク
   - 各主張の根拠URL(最大3件)
   - リスク箇所の指摘
3. 人間レビュー推奨箇所(優先度順3件)

図4 判定結果に関する情報の、人向けの要約を生成するプロンプトの例

① 判定:自動可 / 要承認 / 却下
② 理由(3点)
③ 不足情報(Required)
④ 実行提案(短文)
⑤ ロールバック(失敗時の戻し方)
⑥ 監査メモ:承認者/日時/版数

図5 判定結果に関する、システム連携や監査用の情報を生成するプロンプトの例

【役割】承認ゲート:入力を要約し、実行可否と根拠を機械可読で返す
【返却JSON】
{
  "auto_action": "needs_approval",
  "confidence": 0.85,
  "risk_score": 35,
  "reasons": ["金額が社内閾値を超過", "在庫残が3日分で変動大", "取引先情報が更新待ち"],
  "required_context": ["最新在庫CSV", "購買稟議URL"],
  "proposed_action": "在庫再評価後に数量50%で発注",
  "human_checklist": ["金額閾値を満たすか", "在庫の確度", "契約条件の例外有無", "広報リスク", "ロールバック可否"],
  "logs": "s3://audit/workflows/po-2025-10/ 24ヶ月保持",
  "rollback": "API: /orders/cancel を実行し在庫を戻す。再開条件:在庫確度80%以上"
}
【判定基準】
- confidence ? 0.90 かつ risk_score ? 20 → approve
- 0.70 ? confidence < 0.90 または 20 < risk_score ? 60 → needs_approval
- それ以外 → reject

Vol.99 補足情報

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

連載 作りながら学ぶVue.js入門

記事で紹介したサンプルコードの完全版と、その実行方法を記したMarkdown文書をまとめたZIPファイルは、ここからダウンロードできます。

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

特集1 自宅で本格ネットワーク環境構築(Vol.98記載)

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

著者:麻生 二郎

スマートフォンやパソコン、スマート家電をインターネットに接続するだけなら市販のルーターを購入するだけでよいでしょう。サーバーソフト、ネットワーク関連のソフトや機器を使ってみたい,試してみたい場合、普段使うネットワークとは分離した環境を用意しておくと便利です。本特集では、そのようなネットワーク環境を構築します。

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

図16 /etc/hostapd/hostapd.confファイルの設定例

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ファイルの設定例

interface=wlan0
dhcp-range=192.168.2.2,192.168.2.50,12h
domain-needed
bogus-priv

図18 /etc/network/interfacesファイルの設定例

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ファイルの設定例

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の設定例

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

Vol.98

投稿日:2025.09.25 | カテゴリー: バックナンバー

 近年、便利で安価なクラウドサービスが登場しているため、自宅でサーバーを立てる、サーバーソフトを試すといった需要が減っています。ただし、DockerやKubernetesなどの登場により、サーバー環境の構築や運用管理がかなり複雑になっています。また、インターネットが急速に広がったため、さまざまなセキュリティ対策が求められています。これらの技術を学習・実験・テストするには、自由に使えるネットワーク環境が必要です。特集1では、さまざまなサーバーソフトやセキュリティ対策の学習・実験・テストに使えるように自宅のネットワーク環境を整備する方法を紹介しています。
 特集2では、耐量子コンピュータ暗号(PQC)と公開鍵基盤(PKI)を解説しています。今までの暗号化方式では、将来、量子コンピュータに破られる可能性が出てきています。すでに実装が始まっているPQCの仕組みや動向、公開鍵基盤(PKI)に及ぼす影響などをまとめました。
 特別企画では、2025年5月20日にリリースされた企業向けLinuxディストリビューションの新版「Red Hat Enterprise Linux 10」の新機能と、個人ユーザーにおける無料利用方法を紹介しています。
 このほか、レポートでは米OpenAI社の最新LLM(大規模言語モデル)の「GPT-5」、香川大学の学生が執筆している連載「香川大学SLPからお届け!」ではAI駆動のプログラミング学習アプリを作成方法を扱っています。
 今回も読み応え十分のシェルスクリプトマガジン Vol.98。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

作りながら学ぶVue.js入門(Vol.98掲載)

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

著者:大津 真

JavaScriptフレームワークの中でも、学びやすさと柔軟さで人気を集めている「Vue.js」。本連載では、Vue.jsの基礎から実践的な使い方までを、分かりやすく丁寧に解説していきます。一緒にフロントエンド開発の楽しさを体験してみましょう。第3回では、HTMLやCSS、JavaScriptのコードをまとめた「SFCファイル」を使用したVueアプリケーションの作成について説明します。

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

図6 「index.html」ファイルのbody要素

<body>
  <div id="app"></div>
  <script type="module" src="/src/main.js"></script>
</body>

図7 「src/main.js」ファイルの内容

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'

createApp(App).mount('#app')

図8 デフォルトの「src/App.vue」ファイルの内容(抜粋)

<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」のコード

<!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ファイルに記述した例

<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」ファイルの変更例

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vite.dev/config/
export default defineConfig({
  plugins: [vue()],
  base: '/myapp/'
})

Pythonあれこれ(Vol.98掲載)

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

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第28回では、インタラクティブなグラフを手軽に作成できる可視化ライブラリ「Plotly」を使って、さまざまなグラフを作成します。

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

図2 散布図を描くコード

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 花弁の長さのデータを表示できるようにしたコード

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 がくの幅の分布をヒストグラムで表示するコード

fig = px.histogram(df, x='sepal_width', color='species')
fig.update_layout(width=800, height=600)
fig.show()

図11 カラースキームを変更するコードの例

# 離散的なデータ向けのカラースキーム設定
px.defaults.color_discrete_sequence = px.colors.qualitative.T10
# 連続的なデータ向けのカラースキーム設定
px.defaults.color_continuous_scale = px.colors.sequential.GnBu

図13 四つのヒストグラムをまとめて表示するコードの例

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のコードの改良版

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データセットを読み込んで散布図を描くコード

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 曜日ごとのチップの額を箱ひげ図で描くコード

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 二つの箱ひげ図を並べて描くコード

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 バイオリン図を描くコード

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 ヒートマップを描くコード

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()

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

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

著者:井上竜輔

最終回では、筆者が作成したプログラミング学習アプリの概要を紹介します。このアプリは、AIなどの外部APIを活用して、オンラインで動的に問題を生成、実行、評価できるものです。安全性を考慮して、ユーザーが提出したプログラムは「Judge0 CE」というオンラインコード実行システムで、実行、評価します。

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

図7 「components/CodeEditor.tsx」ファイルのコード(抜粋)

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関数の定義コード

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 フロントエンドからのリクエストを受け取るコード

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にリクエストを送り、結果をフロントエンドに返すコード

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つ作成してください。

# 制約
- 難易度: 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による解答例のコード"
}

Vol.98 補足情報

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

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

レポート2(Vol.98掲載)

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

著者:末安 泰三

Pythonソフトウエア財団が2025年10月1日にリリース予定のPython 3.14では、性能向上の足かせだった大域ロック(GIL)を無効化できる。それにより、マルチスレッド処理を大幅に高速化可能だ。マルチスレッド処理のコードを使って、実際にどの程度高速化するのかを検証する。

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

図1 検証に使用したPythonコード

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 Web掲載記事まとめ

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

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人材育成支援事業の取り組み」/シェル魔人

Markdownを活用する(Vol.98掲載)

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

著者:藤原 由来

本連載では文書の装飾・構造付けを手軽に行える記法であるMarkdownを用いて、さまざまな文書や成果物を作成する方法を紹介します。前回に引き続き、文書変換ツール「Pandoc」の文書作成方法を紹介します。今回はPandocを用いて、EPUB形式電子書籍を作成する方法を紹介します。

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

図1 Markdown形式にした「銀河鉄道の夜」の冒頭部分

---
title: 銀河鉄道の夜
author: 宮沢賢治
---

# 一、午后《ごご》の授業

「ではみなさんは、そういうふうに川だと云《い》われたり、乳の流れたあとだと云われたりしていたこのぼんやりと白いものがほんとうは何かご承知ですか。」先生は、黒板に吊《つる》した大きな黒い星座の図の、上から下へ白くけぶった銀河帯のようなところを指《さ》しながら、みんなに問《とい》をかけました。
 カムパネルラが手をあげました。それから四五人手をあげました。ジョバンニも手をあげようとして、急いでそのままやめました。たしかにあれがみんな星だと、いつか雑誌で読んだのでしたが、このごろはジョバンニはまるで毎日教室でもねむく、本を読むひまも読む本もないので、なんだかどんなこともよくわからないという気持ちがするのでした。
 ところが先生は早くもそれを見附《みつ》けたのでした。
「ジョバンニさん。あなたはわかっているのでしょう。」
 ジョバンニは勢《いきおい》よく立ちあがりましたが、立って見るともうはっきりとそれを答えることができないのでした。ザネリが前の席からふりかえって、ジョバンニを見てくすっとわらいました。ジョバンニはもうどぎまぎしてまっ赤になってしまいました。先生がまた云いました。
(略)

図12 本文中の画像を挿入するために追記

(略)
 そして教室中はしばらく机《つくえ》の蓋《ふた》をあけたりしめたり本を重ねたりする音がいっぱいでしたがまもなくみんなはきちんと立って礼をすると教室を出ました。

![](img/Gemini_Generated_Image.png){height=90%}

# 二、活版所

 ジョバンニが学校の門を出るとき、同じ組の七八人は家へ帰らずカムパネルラをまん中にして校庭の隅《すみ》の桜《さくら》の木のところに集まっていました。それはこんやの星祭に青いあかりをこしらえて川へ流す烏瓜《からすうり》を取りに行く相談らしかったのです。
(略)

※ 「&#91」の箇所は「[」です。

図14 オプションをまとめたデフォルトファイル

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

[イベント]AI博覧会 Summer 2025開催

投稿日:2025.09.22 | カテゴリー: 記事

 AI(人工知能)に関する国内イベント「AI博覧会 Summer 2025」が2025年8月27日と28日の2日間、東京千代田区にある東京国際フォーラムで開催されました(図1)。同博覧会では、100社が出展、200以上の製品が展示されました。また、55名の登壇者による45セミナーも行われました。
 来場者は、27日が5177名、28日が5117名で合計1万294名と盛況でした。ほとんどのセミナーは、ほぼ満席で、立ち見が出るものもありました。国内におけるAIに関する関心の高さが伺えるイベントでした。

図1 AI博覧会 Summer 2025の様子

 AI博覧会 Summer 2025の開催中に、主催者であるアイスマイリー 代表取締役の板羽 晃司氏(図2)に、AI博覧会の開催経緯や狙い、今までの開催を含むAI博覧会で感じたこと、来場者の反応、改善点、今後の計画・目標などの話を聞きました(聞き手は、麻生)。

図2 アイスマイリー 代表取締役の板羽 晃司氏

――AI博覧会を始められた背景を教えてください。

 世界のグローバル企業におけるAI普及率が約7割に達しているのに対し、日本の企業の普及率はわずか1割程度にとどまっていました。この状況に強い危機感を抱いており、ここを何とかしたいという思いからAI博覧会が始まっています。
 私は、2007年にWeb制作会社であるバイタリフィに入社しました。キャンペーンサイトの企画提案、制作事業、アプリ開発、ベトナムでのオフショア開発などに携わってきました。その後、2018年にはIT技術に特化したメディア事業を新たに立ち上げ、現在はバイタリフィとアイスマイリーの2社を運営しています。
 アイスマイリーは、AIに特化したWebメディアを運営していて、AIに関する記事作成やイベント取材などを行っています。このメディア事業は、広告ビジネスだけでなく、AI関連の製品やサービスを掲載し、ユーザー企業からの資料請求をベンダー企業に提供するというビジネスも行っています。
 最初は、Web上だけでビジネスを展開していましたが、顧客であるAIベンダーやAI開発会社から「Webだけだと相手の顔が見えないため、マッチング率が低い」「リアルな場が欲しい」という声が多く寄せられました。そのような要望を受け、2024年3月にAI博覧会を立ち上げました。AI博覧会は、オンラインのメディア事業のリアル版という位置付けになります。

――AI博覧会の具体的な目標や狙いについて教えてください。

 主な狙いはビジネスの拡大ですが、それ以上に日本のAI普及率を高めることを重要視しています。2026年中に企業のAI普及率を50%にしたいという目標を掲げています。

――今回でAI博覧会の開催は5回目とのことですが、開催頻度や、他のAIイベントとの関係性について教えてください。

 開催間隔は、状況によって異なります。前回が3月開催、今回は8月開催といった感じです。同種のイベントには「AI・人工知能EXPO」があります。AI・人工知能EXPOの主催者とは、当社のWebメディアを通じて連携し、集客も支援しています。リアルイベントの開催時期については、お互いの日時や開催地が被らないように調整し、AI業界全体を盛り上げていこうという方針で協力し合っています。

――これまでの開催を振り返り、来場者からの反応や会場規模の拡大について感じたことはありますか。

 来場者からは、新しいAIの技術や製品、サービスを求めているという強い意欲を感じます。「もっと大きな会場でやってほしい」という要望が多く寄せられています。そのため、年々会場の規模を拡大してきました。初回は、御茶ノ水ソラシティで2600人から3000人規模でした。その後、渋谷、大阪、浜松町、今回は東京国際フォーラムに拡大していきました。このような規模の拡大は、出展企業と来場者双方にとってより良いマッチング機会を提供するために不可欠だと考えています。

――今回の東京国際フォーラムでの開催における手ごたえはどうですか。今後の改善点について、何か考えていますか。

 東京国際フォーラムは、会場が広く、駅からも近いため、出展企業も来場者も大変満足されています。出展企業数も増加していることから、これくらいの規模で開催できて本当に良かったと感じています。
 改善点としては、分野ごとに出展企業の明確な分類ができていないことです。今後は、来場者にとって分かりやすいように、出展内容を分類してブースの配置などを見直す必要があると感じています。

――AI博覧会は、展示会と商談会のどちらの性質が強いのでしょうか。

 さまざまなAI技術や製品、サービスが出展されているという展示会、その流れでAI導入意欲の高い来場者と出展企業が商談できる商談会という両方の側面を持っています。

――今後の計画について教えてください。

 次回は、2025年12月11日に「AIエージェント博」というAI博覧会の番外編となるミニイベントを開催する予定です。今年は、AIエージェント元年とも言われていますが、まだその使い方が分からない状況です。このイベントを通じてAIエージェントの普及に貢献したいと考えています。

――東京・大阪以外の開催予定はありますか。また、地域による来場者の違いはありますか。

 今後、名古屋での開催も予定しています。これまでの経験から、東京の来場者は情報収集に来る人が多いのに対し、関西などの来場者は、よりじっくりと話を聞いてくれる人が多いという印象です。東京は情報過多でリテラシが高い一方、その他の地域ではまだそこまでAIが浸透し切れていないと感じています。

――今後のAI博覧会の長期的な目標と運営方針についてお聞かせください。

 先に述べたように、引き続き、日本の企業のAI普及率を引き上げることを目指しています。博覧会の規模については、これ以上大きく広げすぎるとマッチング効果が薄れる可能性があるため、最適な規模感を維持し、企業間のマッチングに最大限貢献できるイベントとして作り上げていきたいと考えています。

領収書の電子化開始のお知らせ

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

いつもUSP研究所の通信販売をご利用いただき、誠にありがとうございます。

USP研究所では、環境保護および業務効率化の取り組みの一環として、2025年9月1日(月曜日)より、領収書を電子化いたします。

ご注文・お支払い完了後に、領収書をご希望された方へメールにてPDF形式の領収書をお送りいたします。なお、紙の領収書をご希望の場合は、印刷・郵送にかかる事務手数料が必要です。以下のご対応をお願いいたします。

紙の領収書をご希望のお客様へ

紙の領収書が必要な場合は、商品ご購入時に「URX0001 紙の領収書発行手数料」(税込み220円)を必ずカートに追加してください。こちらのご購入が確認でき次第、紙の領収書を郵送いたします。

電子領収書は迅速にお届けでき、保管も容易です。理解とご協力のほどよろしくお願い申し上げます。

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

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

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 コラム「意識教育」/シェル魔人

Raspberry Pi Pico W/WHで始める電子工作(Vol.97掲載)

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

筆者:米田 聡

本連載では、人気のマイコンボード「Raspberry Pi Pico/Pico 2」シリーズを活用していきます。同ボードは、無線LANやBluetoothの通信機能を搭載するモデルもあり、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。今回は、前回Raspberry Pi Pico 2に取り付けた小型LCDに画像を表示するプログラムを紹介します。

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

図1 RGB565の非圧縮ビットマップ画像に変換するPythonプログラム(rgb565.py)

#!/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)

#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 埋め込んだアセンブリ言語のリスト

    .section ".rodata"
    .balign 4

    .global _sample_picture
    .global _picture_size

_sample_picture:
    .incbin "include/sample.bin"
    .set _picture_size, . - _sample_picture

    .section ".text

Vol.97

投稿日:2025.07.25 | カテゴリー: バックナンバー

 米Broadcom社の仮想化ソフトウエア「VMWare」のライセンス体系変更により多くの企業が移行先を検討しました。米Red Hat社が提供する仮想化基盤「Red Hat OpenShift Virtualization」もその一つです。ただし、他の移行先とは異なり、Red Hat OpenShift Virtualizationではコンテナ型仮想化基盤「OpenShift」上に仮想マシンを移行します。Kubernetesによる管理が主流になりつつあるコンテナ型クラウド環境で、仮想マシンも一元管理できることは大きなメリットになります。特集1では、Red Hat OpenShift Virtualizationについて分かりやすく解説しています。
 特集2は、オラクルが提供するクラウドサービス「Oracle Cloud Infrastructure」(OCI)でのクラウドネイティブアプリケーションの開発です。クラウドネイティブアプリケーションとは、初めからクラウド上で動かすことを前提に設計・開発されたアプリケーションです。新規のアプリケーション開発のみならず、既存アプリケーションのクラウドへの移行にも十分役立つ内容になっています。
 このほか、レポートではWindowsのLinux環境「Windows Subsystem for Linux 2」で動作するLinuxディストリビューション「Red Hat Enterprise Linux」、連載「知っておきたいシェル関連技術」では、次世代プロトコル「QUIC」を用いたSSH通信を紹介しています。
 今回も読み応え十分のシェルスクリプトマガジン Vol.97。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

Markdownを活用する(Vol.97掲載)

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

筆者:藤原 由来

本連載では文書の装飾・構造付けを手軽に行える記法であるMarkdownを用いて、さまざまな文書や成果物を作成する方法を紹介します。前回に引き続き、文書変換ツール「Pandoc」を扱います。今回は、Pandocの概要と基本的な使い方について説明します。

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

図3 Markdownテキストファイル(sample.md)の内容

# カレーの作り方

おいしいカレーを作りましょう。

## 材料

- 牛肉
- 玉ねぎ
- にんじん
- カレールー
- 水

図9 数式を用いたMarkdown記法

インライン数式 $f(x) = ax^2 + bx + c$ です。

## ブロック数式

$$
e^{i \theta} = \cos \theta + i \sin \theta
$$

Pythonあれこれ(Vol.97掲載)

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

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第27回では、異文化間交流教育プロジェクトで得られたオンライン交流データを分析する、筆者の研究例を紹介します。

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

図2 必要なライブラリをインポートするコード

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()関数を定義するコード

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 ファイルからターンテイキングの状況を抽出するコード

filename = 'data/file00.xlsx'
df = pd.read_excel(filename).drop(['Unnamed: 0'], axis=1)
d = mk_relations(df)
d

図7 conv_dict()関数を定義するコード

def conv_dict(d):
  v = d.values()
  return dict(zip(d.keys(), map(lambda x: x/max(v), v)))

図9 count_talk()関数を定義するコード

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()関数を定義するコード

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()関数を定義するコード

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 すべてのデータから参加者数と変動係数を計算するコード

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 })

作りながら学ぶVue.js入門(Vol.97掲載)

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

著者:大津 真

JavaScriptフレームワークの中でも、学びやすさと柔軟さで人気を集めている「Vue.js」。本連載では、Vue.jsの基礎から実践的な使い方までを、分かりやすく丁寧に解説していきます。一緒にフロントエンド開発の楽しさを体験してみましょう。第2回では、テンプレート構文において、各要素に特別な動作を与えるための機能であるディレクティブの基礎について説明します。

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

図1 オブジェクトリテラルの例

{
    name: name,
    age: age,
    scores: { math: [40, 50], eng: [70, 60] },  
    sayHello: function() {
        console.log('こんにちは');
    }
}

図2 シンプルなVueアプリ「sample1.html」の主要部分のコード

<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」のコード(抜粋)

<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」のコード(抜粋)

<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」のコード(抜粋)

<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」のコード(抜粋)

<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」のコード(抜粋)

<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」のコード(抜粋)

<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」のコード(抜粋)

<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」のコード(抜粋)

<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」のコード(抜粋)

<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>

Vol.97 補足情報

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

連載 作りながら学ぶVue.js入門

記事で紹介したサンプルコードの完全版(ZIPで圧縮)は、ここからダウンロードできます。

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

作りながら学ぶVue.js入門(Vol.96掲載)

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

著者:大津 真

JavaScriptフレームワークの中でも、学びやすさと柔軟さで人気を集めている「Vue.js」。本連載では、Vue.jsの基礎から実践的な使い方までを、分かりやすく丁寧に解説していきます。一緒にフロントエンド開発の楽しさを体験してみましょう。第1回では、Vue.jsの概要と、簡単なアプリの作成方法を紹介します。

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

図3 Options APIを利用したコードの例(抜粋)

<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を利用したコードの例(抜粋)

<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ファイルの記述例

<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文書のテンプレート例

<!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」ファイルに記述するコード

<!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()関数の定義コードをこのように変更

setup() {
    const msg = ref('ハローVue')
    const person = ref({name:'大津真', age: 35})
    // 3秒後にリアクティブな変数msgを変更する
    setTimeout(() => {
        msg.value = "ようこそVueの世界へ"
    }, 3000)
    return{
        msg, person
    }
}

図12 div要素の記述をこのように変更

<div id="app">
    <h1>{{ "反転→ " + msg.split("").reverse().join("")}}</h1>
    <p>{{ 名前: ${person.name}, 年齢: ${person.age}才  }}</p>
</div>

Vol.96

投稿日:2025.05.25 | カテゴリー: バックナンバー

 インターネットの進化や、企業への浸透、リモートワークの広がりなどにより、企業において、強固なセキュリティ対策が求められています。特集1では「常に検証し、何も暗黙のうちに信頼しない」という考えに基づく「ゼロトラストセキュリティ」を紹介しています。ゼロトラストセキュリティによる対策を実施し、企業の資産や信頼を守ってください。
 セキュリティ対策と同様に、企業でのシステム開発の内製化も注目されています。社員自らがシステムを開発して運用する内製化を進める上で、開発環境の選択はとても重要です。特集2では、コードを書かないノーコード、あるいは簡単なコードしか書かないローコードの開発ツールである「プリザンター」を解説しています。プリザンターは、国内で開発されていて、オープンソースであり、どの企業でも無料で利用できます。
 このほか、二つの連載が始まりました。「作りながら学ぶVue.js入門」では、Webのユーザーインタフェースを構築するためのJavaScriptフレームワーク「Vue.js」について分かりやすく紹介していきます。「知っておきたいシェル関連技術」では、毎回異なるテーマで、シェルを活用するためのさまざまな技術やソフトウエアを解説していきます。何かのヒントとして役立ててください。
 今回も読み応え十分のシェルスクリプトマガジン Vol.96。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

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

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

筆者:織田 悠暉

今回は、ゲームエンジン「Unity」で2Dキャラクタを動かす方法を紹介します。具体的には、プレーヤのキャラクタと、地形の作成、左右の移動と、ジャンプ動作の実装、各動作に合わせてキャラクタの画像を変更する方法について解説します。とても手軽に実装できるので、皆さんもぜひチャレンジしてください。

図9 「Move」ファイル(「Move.cs」ファイル)に記述するコード

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」ファイル)に挿入するコード

void OnTriggerStay2D(Collider2D collision)
{ 
  if (Keyboard.current.dKey.wasReleasedThisFrame ||
      Keyboard.current.aKey.wasReleasedThisFrame)
  {
    rigid2D.linearVelocity = Vector2.zero;
  }
}

図11 「Jump」ファイル(「Jump.cs」ファイル)に記述するコード

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」ファイル)に記述するコード

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];
  }
}

Pythonあれこれ(Vol.96掲載)

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

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第26回では、データを視覚化(ビジュアライゼーション)してデータの傾向を探るためのツール「PyGWalker」の利用に挑戦します。

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

図5 コードセルを追加して実行するコード

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)

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

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

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 コラム「毎週開催の金曜勉強会」/シェル魔人

Vol.96 補足情報

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

表紙

表紙の「辻陽 行氏」は、「辻 陽行氏」の誤りです。お詫びして訂正いたします。

製品レビュー

価格の「12万990円」は「1万2990円」の誤りです。お詫びして訂正いたします。

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

特集1 はじめてのシェルスクリプト(Vol.95記載)

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

著者:麻生 二郎

シェルスクリプトは、LinuxやUNIX系OSのコマンドのみで記述できるプログラムです。さまざまな処理をテキストファイルに書き込み、LinuxやUNIX系OSの環境ですぐに試せるので便利です。本特集では、はじめてシェルスクリプトを書く人向けにその記述テクニックを分かりやすく紹介します。

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

図5 引数の受け取りを試すシェルスクリプト(test1.sh)

#!/bin/bash -e

echo $1
echo $2
echo $3

図6 readコマンドを試すシェルスクリプト(test2.sh)

#!/bin/bash -e

read -p 'Please input: ' INPUT01
echo $INPUT01 | tee input.txt

図7 ifを試すシェルスクリプト(test3.sh)

#!/bin/bash -e

if [ $1 == '100' ]; then
    echo 'good'
elif [ $1 == '0' ]; then
    echo 'bad'
else
    echo 'unknown'
fi

図8 caseを試すシェルスクリプト(test4.sh)

#!/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)

#!/bin/bash -e

i=0
while [ $i -le 10 ]
do
    echo $i
    i=$((i + 1))
done

図10 forを試すシェルスクリプト(test6.sh)

#!/bin/bash -e

for i in 0 1 2 3 4 5
do
    echo $i
done

図11 関数を試すシェルスクリプト(test7.sh)

#!/bin/bash -e

function output()
{
    echo $1
}

output run1
output run2

Pythonあれこれ(Vol.95掲載)

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

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第25回では、人間がどのように文章の意味を理解するかという認知能力に関わる、少し不思議で面白い現象について取り上げます。

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

図4 タイポグリセミア度を算出する関数tl()の定義コード

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()の定義コード

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()の定義コード

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」の文を作成した例

org = 'The superpowered quick brown fox jumps over the beautiful lazy dog.'
typo = typoglycemia(org, 0.7, debug=True)
typo

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

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

著者:遠藤幸太郎

AIと自然言語処理技術の発展により、「ChatGPT」に代表される、人間と自然な形で対話できる会話型AIが注目を集めています。今回は、PythonでAIチャットボットアプリを開発していきます。米Google社の「Gemini API」を利用し、対話だけでなく、音声や映像ファイルをアップロードして分析できるようにします。

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

図3 「win.py」ファイルに記述するコード

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 基本的なチャットボットアプリのコード

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の③で示す箇所に挿入するコード

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の④で示す箇所に挿入するコード

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の⑥で示す箇所に挿入するコード

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の⑪で示す箇所に挿入するコード

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

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

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

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 コラム「小売業のビジネスリーダーを育成する講義」/シェル魔人

レポート2(Vol.95掲載)

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

著者:末安 泰三

米FerretDB社は2025年3月5日、MongoDB互換のドキュメント指向データベース管理システム「FerretDB」のバージョン2.0をリリースした。米Microsoft社のNoSQLデータベース実装「DocumentDB」を採用したことで、従来版比で20倍以上の性能を出せるケースがあるという。

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

図1 「docker-compose.yml」ファイルに記述する内容

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

Vol.95

投稿日:2025.03.25 | カテゴリー: バックナンバー

 シェルスクリプトは、UNIX/Linuxコマンドを組み合わせてプログラムを記述します。コマンドの羅列でも動作するので、プログラムとしてはとてもシンプルです。このシェルスクリプトは、UNIX系OSやLinuxの環境があれば、すぐに試せます。特集1では、はじめてシェルスクリプトを書く人向けにポイントとなる記法を紹介しています。
 特集2では、国産オープンソースソフトウエアのベクトル検索エンジン「Vald」を解説しています。ベクトル検索エンジンは、画像や音声などの非構造データを数値ベクトル化にして検索に利用します。大規模な高速検索に向くValdの仕組みや導入方法を理解しましょう。
 特集3では、エンタープライズシステムの監視やジョブ管理、運用の自動化などを実現する国産オープンソースの統合運用管理ソフトウエア「Hinemos」のジョブ管理機能を紹介しています。シェルスクリプトマガジン Vol.92の特集2に次ぐ第2弾です。
 このほか、連載「Pythonあれこれ」では、「タイポグリセミア」という最初と最後の文字さえ合っていれば、順番はめちゃくちゃでもちゃんと読める現象をPythonプロブラムと絡めています。とても面白い内容です。
 今回も読み応え十分のシェルスクリプトマガジン Vol.95。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

特集2 ベクトル検索エンジンのVald(Vol.95記載)

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

著者:湯川 輝一朗

ベクトル検索エンジン「Vald」は、純国産のオープンソースソフトウエア(OSS)です。画像や音声などの非構造データを数値ベクトル化にして検索に利用します。クラウド上でも動作し、大規模な高速検索に向いています。ライセンスはApache License 2.0であり、無料利用が可能です。本特集では、Valdについて解説します。

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

図7 values.yamlファイル

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

Vol.95 補足情報

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

特集3 Hinemos入門 ジョブ編

Hinemosの運用管理機能を提供する「Hinemosマネージャ」などのソフトウエアを、「Red Hat Enterprise Linux 9」が稼働するホストにインストールする手順を、こちらで紹介しています。

連載 Markdownを活用する

静的サイトジェネレータ「Hugo」およびバージョン管理システム「Git」のインストール、Gitのセットアップ、サイトの構築手順は、こちらから参照できます。

連載香川大学SLPからお届け!

記事で紹介したコードを拡張して、アプリのボタンなどの見栄えを調整しました。コードを記したファイルはここからダウンロードできます(ファイルはZIPで圧縮しています)。

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

第6回で紹介したHugoによるWebサイト構築手順

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

 連載「多種多様な文書作成が可能 Markdownを活用する」の第6回(シェルスクリプトマガジン Vol.94で掲載)で作成したWebサイトを第7回でも使用します。第7回で紹介する作業が進めやすいように、HugoとGitのインストール、Gitの初期設定、Webサイトの構築手順を以下にまとめてました。
 なお、手順自体は、第6回の「Hugoのインストール」「HugoによるWebサイトの構築」で紹介した内容と同じです。

HugとGitのインストール

 Hugoを使用するには、次のソフトウエアをインストールします。
・Hugo本体
・Git

Hugo本体のインストール

 まず、Hugo本体をインストールします。以降は、導入先のパソコンのOSとしてWindows 11を前提に進めます。導入後は、macOSやLinuxでも同じようにHugoを利用できます。また、Markdownを記述するためのテキストエディタとしてVisual Studio Code(VSCode)を利用します。VSCodeは、あらかじめ
インストールされていることを前提とします。
 [Windows]キーを押しながら[X]キーを押すと、図1のようなメニューが表示されます。このメニューの「ターミナル( 管理者)」を選択します*1。選択後に、「ユーザー アカウント制御」ダイアログが表示されるので、「はい」ボタンをクリックします(図2)。管理者権限付きのPowerShellが起動します*2

図1「ターミナル(管理者)」を選択  図2「ユーザーアカウント制御」ダイアログ

 PowerShellで、次のコマンドを実行してHugo本体をインストールします。

> winget install -e --id Hugo.Hugo.Extended ↵

 図3のようにインストールが実行されます。最後に「インストールが完了しました」のメッセージが表示されれば、インストール完了です。

図3 Hugoのインストール

 右上の「×」ボタンをクリックして、管理者権限付きのPowerShellを閉じます。再び、[Windows]キーを押しながら[X]キーを押し、表示されるメニューから「ターミナル」(もしくは「Windows PowerShell」)を選択します。一般権限でPowerShellが起動します。
 PowerShellで、次のコマンドを実行します。

> hugo version ↵

 次のようにバージョン番号の文字列が表示されれば、Hugoが利用できます。

hugo v0.139.4-3afe91d4b1b069abbedd6a96ed755b1e12581dfe+extended windows/amd64 BuildDate=2024-12-09T17:45:23Z VendorInfo=gohugoio

 右上の「×」ボタンをクリックして、PowerShellを閉じます。

Gitのインストール

 続いて、Gitをインストールします。その前に、Gitがすでにインストールされているかどうかを確認します。[Windows]キーを押しながら[X]キーを押し、表示されるメニューから「ターミナル(管理者)」(もしくは「Windows PowerShell( 管理者)」)を選択します。選択後、「ユーザー アカウント制御」ダイアログが表示されたら、「はい」ボタンをクリックします。管理者権限付きのPowerShellが起動します。
 このPowerShellで、次のコマンドを実行します。

> git --version ↵

 次のような形式でバージョン番号が表示されれば、Gitがインストールされています*4。このような状態ならGitのインストールをスキップしてください。

git version 2.47.0.windows.2

 一方、次のように表示された場合は、Gitがインストールされていません。 

git : 用語 'git' は、コマンドレット、関数、スクリプトファイル、または操作可能なプログラムの名前として認識されません。

 次のコマンドでGitをインストールします。

> winget install --id Git.Git -e --source winget ↵

 途中でダイアログが表示されますが、そのまま待ってください。最後に「インストールが完了しました」のメッセージが表示されればインストール完了です。
 右上の「×」ボタンをクリックして、管理者権限付きのPowerShellを閉じます。[Windows]キーを押しながら[X]キーを押し、表示されるメニューから「ターミナル」(もしくは「Windows PowerShell」)を選択します。一般権限でPowerShellが起動します。
 再度、

> git --version ↵

を実行し、バージョン番号が表示されればインストール完了です。

Gitのセットアップ

 Gitを初めて使用する場合、次のコマンドで自身の氏名とメールアドレスを設定します。氏名は、全角の平仮名や片仮名、漢字ではなく、半角のアルファベットやスペース、数字、記号で入力します。

git config --global user.name "氏名"
git config --global user.email "メールアドレス"

 ここで入力した氏名とメールアドレスは、次回の連載で使用する、ソースコードのホスティングサービス「GitHub」で公開される情報となります。したがって、自分の本名を隠したい場合、氏名はハンドルネーム(インターネット上のニックネーム)で構いません。

> git config --global user.name "free writer" ↵

 また、記事内で紹介した方法でGitHub専用のメールアドレスを発行します。まだ、GitHubのアカウントを持っていない場合は、次のように仮のメールアドレスを設定しておいてください。すでにGitHubアカウントを持っている場合は、そのアカウントに登録しているメールアドレスを設定します。

> git config --global user.email "dummy@example.com" ↵

Webサイトの構築

 Power Shellで次のコマンドを実行します。

> 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

> code . ↵

 「このフォルダー内のファイルの作成者を信頼しますか?」というダイアログが表示されたら「はい、作成者を信頼します」をクリックします。
 VSCodeが起動したら、VSCode上で次の三つのファイルを書き換えます。
 「hugo.yaml」ファイルでは、「languageCode」「title」「theme」の行を書き換えます。

baseURL: https://example.org/
languageCode: ja-jp
title: "はじめてのHugoサイト"
theme: ananke

 「first-post.md」ファイルでは、「title」の行を書き換えて、「# はじめての記事」から始まる本文を追加します。

---
date: '2024-11-30T13:02:12+09:00'
draft: true
title: 'はじめての記事'
---

# はじめての記事

こんにちは!

 「public-post.md」ファイルでは、「draft」と「title」の行を書き換えて、「# 公開テスト」から始まる本文を追加します。

---
date: '2024-11-30T13:50:00+09:00'
draft: false
title: '公開テスト'
---

# 公開テスト

この記事は公開されます。

 first-post.mdファイルは下書き状態(draft: true)であり、public-post.mdファイルは公開状態(draft: false)にしています。
 次のコマンドを実行してWebサーバーを起動します。

> hugo server ↵

 コマンドを実行すると、次のような表示がされます。

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サイトをビルドします。

> hugo ↵

*1 Windows 10では「Windows PowerShell( 管理者)」を選択します。

*2 「WSL」(Windows Subsystem for Linux)でLinuxディストリビューション「Ubuntu」などを導入している場合は、PowerShellではなくWSLが優先されます。その場合は、ターミナル上部タブの右にある下向き矢印のアイコンをクリックしてメニューから「Windows PowerShell」を選んでください。以降、PowerShellのターミナルを開くときはこの手順を実施してください。

*3 VSCodeインストール時の「追加タスクの選択」「- その他」で、「PATHへの追加(再起動後に使用可能)」にチェックが付けられていないとcodeコマンドを実行できません(デフォルトでチェックあり)。なお、チェックを付けてVSCodeを再インストールすれば、使えます。

Pythonあれこれ(Vol.94掲載)

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

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第24回では、高校で学ぶレベルの数学を用いて、問題を最適にする解は何かを探索します。題材は、線形計画法と組合せ最適問題の二つです。

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

図3 図1の問題を解くPythonコード

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コード

(略)
# ソルバーの定義
solver = pywraplp.Solver.CreateSolver('CBC')
# 変数の準備
x = solver.IntVar(0, solver.infinity(), 'x')
y = solver.IntVar(0, solver.infinity(), 'y')
(略)

図6 データを準備するためのPythonコード

teachers = ['田中', '山本', '竹村', '飯島', '佐藤']
days = ['月', '火', '水', '木', '金']
periods = ['1限', '2限', '3限', '4限']

図7 lessons連想配列を作成するPythonコード

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限'): 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コード

for d in days:
  for p in periods:
    model.Add(sum(lessons[(t, d, p)] for t in teachers) == 1)

図10 「先生は1日に1コマだけ授業を担当する」制約条件を与えるPythonコード

for t in teachers:
  for d in days:
    model.Add(sum(lessons[(t, d, p)] for p in periods) <= 1)

図11 「担当時間数に偏りが出ないように配置する」制約条件を与えるPythonコード

for t in teachers:
  model.Add(sum(lessons[(t, d, p)] \
                  for d in days for p in periods) == 4)

図12 requests連想配列を定義するコードの例

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コード

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コード

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コード

for t in teachers:
  model.Add(sum(lessons[(t, d, p)]*requests[t][d][p] \
                for d in days for p in periods) >= 3)

Vol.94 補足情報

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

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

特集2 さくらのレンタルサーバで始める情報発信ガイド(Vol.94記載)

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

著者:前佛雅人

さくらインターネットが提供する「さくらのレンタルサーバ」を利用すれば、「WordPress」を使ったブログサイトなどの制約が少ない、自分だけの情報発信拠点をインターネット上に構築できます。ファイル共有やWikiなどのアプリケーションも思い通りにカスタマイズして利用できます。本特集では、さくらのレンタルサーバを活用した情報発信の方法を紹介します。

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

図8 サンプルHTMLファイル「index.html」の内容

<!DOCTYPE html>
<html>
  <head><title>サンプルWebページ</title></head>
  <body>
    <h1>これはサンプルWebページです</h1>
    <p>次に示すのはカワセミの写真です。</p>
    <p><img src="image.jpg" width="320"></p>
  </body>
</html>

Vol.94

投稿日:2025.01.25 | カテゴリー: バックナンバー

 2024年は、仮想化ソフトウエア製品の「VMware」シリーズのライセンス変更が大きな話題となりました。それだけ仮想化技術は、現在のIT業界では重要となっています。VMwareシリーズに含まれる多くの製品は事実上値上がりしましたが、パソコンやMac向けハードウエア仮想化ソフトの「VMware Workstation Pro」と「VMware Fusion Pro」に関しては誰でも無料で使えるようになりました。特集1では、これらの中からVMware Workstation Proの入手・導入方法、仮想的なパソコンハードウエアの作り方と、(ゲスト)OSのインストール方法を解説しています。
 特集2では、国内事業者であるさくらインターネットが提供するクラウドサービス「さくらのレンタルサーバ」を紹介しています。さくらのレンタルサーバでは、月額121円や500円という低価格でWebやメールなどのサーバーをクラウド上で運用できます。サーバーのハードウエアやOSの詳しい知識がなくても運用管理ができるので、お薦めのサービスです。
 特集3では、生成AIのチューニング基盤「Red Hat Enterprise Linux AI」を解説しています。Red Hat Enterprise Linux AIは、大規模言語モデルなどの生成AIに関するチューニングを本格的に行いたいなどの目的に使える便利なソフトウエア環境です。OSのRed Hat Enterprise Linuxと、必要なツールやライブラリが一体化した状態で提供されます。
 このほか、連載「Pythonあれこれ」では、最適化問題を解くためのライブラリ「OR-Tools」について紹介しています。OR-Toolsを使うと、何をどのように割り当てたら最適になるかという問題の最適解が導き出せます。また、連載「Markdownを活用する」では、静的サイトジェネレータ「Hugo」を解説しています。静的サイトジェネレータは、サーバー側にプログラムを置かなくても見栄えの良いWebサイトが簡単に作れる便利なソフトウエアです。
 今回も読み応え十分のシェルスクリプトマガジン Vol.94。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

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

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

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 コラム「ユニケージ流のデータ設計法」/シェル魔人

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

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

著者:川本真子

 私は最近、To Doリストを管理するGU(I Graphical User Interface)アプリをPythonで作成しました。作成には、Pythonの標準GUIライブラリの「Tkinter」(https://docs.python.org/ja/3/library/tkinter.html)を用いています。今回は、そのGUIアプリの実装について紹介します。

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

図7 フレーム配置用のコード

# フレームの作成
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 グローバル変数を定義するコード

btnge = 'sc'
color = '#A8EEFF'
la_text = '学校のTo Do'
textlist = []

図10 ウィジェット配置用のコード

# 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 クラスと関数を定義するコード

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()

Raspberry Pi Pico W/WHで始める電子工作(Vol.94掲載)

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

著者:米田 聡

本連載では、人気のマイコンボード「Raspberry Pi Pico W/WH」を活用していきます。同ボードは、無
線LANやBluetoothの通信機能を搭載し、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第8回は前回に引き続き、「Raspberry Pi Pico 2」を扱います。

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

図10 LED点滅のプログラム(blink.c)

#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あれこれ(Vol.93掲載)

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

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第23回では、機械学習ライブラリ「scikit-learn」で何ができるのかを紹介した上で、データを二つのクラスに分類するPythonプログラムを作成します。

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

図9 テストデータを作成するPythonコード

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コード

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コード

from mlxtend.plotting import plot_decision_regions
plot_decision_regions(data, label, clf=clf)

図13 NuSVCを用いて学習と分類をするPythonコード

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)

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)

from sklearn.svm import SVC
clf = SVC(gamma=0.1, random_state=0)
clf.fit(data, label)
accuracy_score(clf.predict(data), label)

レポート1(Vol.93掲載)

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

著者:末安 泰三

クジラ飛行机氏は2024年11月7日、手軽にGUIを作成可能なPythonライブラリ「TkEasyGUI」のバージョン1.0系列をリリースした。バージョン1.0系列では、複数項目を入力できるフォームダイアログが追加されるなどの機能拡充やバグ修正が施されている。

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

図1 個人情報を入力させるウィンドウを作成するコードの例

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 フォームダイアログを表示するコードの例

iimport TkEasyGUI as eg

form = eg.popup_get_form(["氏名", "住所", "電話番号"],
                         title="個人情報の入力")

Markdownを活用する(Vol.93掲載)

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

著者:藤原 由来

本連載では文書の装飾・構造付けを手軽に行える記法であるMarkdownを用いて、さまざまな文書や成果物を作成する方法を紹介します。今回は、プレゼンテーション用のスライドを作成するためのプレゼンテーションアプリ「Marp」について解説します。

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

図1 Marp用にMarkdownで記述したテキストの例

---
marp: true
---

# 美味しいカレーの作り方  

シェル花子

---

# 材料

- 玉ねぎ
- にんじん
- じゃがいも
- 肉
- カレールー
- 水

---

# 作り方

1. 材料を切る
2. 肉を炒める
3. 野菜を加える
4. 水を加えて煮る
5. カレールーを入れる

図8 見出しレベル1をスライドの区切りにしたMarkdown形式のテキスト例

---
marp: true
headingDivider: 1
---

# 美味しいカレーの作り方  

## シェル花子

# 材料

## ここに
### 材料を書く

# 作り方

## 適当に作る

図11 スライドの背景や文字色を変更したMarkdown形式のテキスト例

---
marp: true
---

# 通常のスライド

---

<!-- 
_backgroundColor: black
_color: white
-->

# 背景が黒く文字が白いスライド

---

<!-- 
_backgroundImage: "linear-gradient(to bottom, #67b8e3, #0288d1)" 
_color: white
-->

# 背景が青いグラデーションのスライド

---

<!-- 
_backgroundImage: url('cat1.png')
-->

# 背景が画像のスライド

図13 背景画像表示とぼかしフィルタ適用のMarkdown形式のテキスト例

---
marp: true
---

# 背景画像を表示する

![bg cover](cat1.png)

---

# 背景画像にぼかしを入れる

![blur bg cover](cat1.png)

図15 背景画像の横並びと縦並びのMarkdown形式のテキスト例

---
marp: true
---

# 背景に横並びに配置

![bg](cat1.png)
![bg](cat1.png)
![bg](cat1.png)

---

# 背景に縦並びに配置

![bg vertical](cat1.png)
![bg](cat1.png)
![bg](cat1.png)

図20 ChatGPTが生成したMarkdown形式のテキスト

---
marp: true
theme: default
paginate: true
backgroundColor: #f0f0f0
---

# 知られざるインドカレー
### シェル花子

---

# 目次
1. 北インド: パニール・バターマサラ  
2. 南インド: チェッティナード・チキンカレー  
3. 西インド: ゴア・フィッシュカレー  
4. 東インド: コサ・マンシャ  
5. 中央インド: ダールバーフライ  
6. 北東インド: アクホニカレー  

---

# 1. 北インド: パニール・バターマサラ

- クリーミーで濃厚なグレイビー
- トマト、バター、クリームを使用
- パニール(カッテージチーズ)入り
- パンジャーブ地方で有名
- ナンやバスマティライスと食べる

---

(略)

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

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

著者:原口 莉奈

 私は最近、RPG(Role-Playing Game)の戦闘シーンをC++言語で作成しました。作成には、無償で利用できるゲーム開発用ライブラリ「DXライブラリ」(https://dxlib.xsrv.jp/)を用いています。今回は、その一部であるコマンド選択画面の実装について紹介します。

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

図2 ビルド用のバッチファイル「build.bat」に記述する内容

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」ファイルに記述するコード

#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」ファイルに記述するコード

#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」ファイルに記述するコード

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」ファイルに記述するコード

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()関数の記述を追加

(略)
  void HpDraw();
  void BattleDraw();
(略)

図10 「Battle.h」ファイルのBattleDraw()関数定義部分をこのように書き換える

(略)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();
}

特集1 Streamlitで作るデータ分析Webアプリ(Vol.93記載)

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

著者:邑川 真也

「Streamlit」は、OSS(オープンソースソフトウエア)のPython向けフレームワーク(ライブラリ)です。データの可視化機能や分析機能、ダッシュボードなどを備えるWebアプリケーションを、Pythonを使って迅速かつ容易に作成できます。本特集では、さまざまなWebアプリケーションを実際に作成しながら、基本的なデータの可視化方法から、ノーコードでデータ分析を実現する方法まで、Streamlitのさまざまな活用方法を紹介します。

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

図2 HTMLとCSS、JavaScriptを用いて棒グラフを描くコードの例

<!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を用いて棒グラフを描くコードの例

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アプリケーションのコード

import streamlit as st

# タイトルを表示
st.title("シェルスクリプトマガジン")

図9 架空の株価データを生成して表示するコード

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」ファイルに記述するデータ(抜粋)

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 ダッシュボードアプリのコード

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」ファイルに記述するテーマ設定の例

[theme]
primaryColor = "#F14143"              # プライマリカラー
backgroundColor = "#101216"           # 背景色
secondaryBackgroundColor = "#191B20"  # セカンダリ背景色
textColor = "#F9F9F9"                 # テキストカラー
font = "sans serif"                   # フォント

図18 「pygw_test.py」ファイルに記述するコード

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()

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

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

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」/シェル魔人

Vol.93

投稿日:2024.11.25 | カテゴリー: バックナンバー

 産業界のDX(デジタルトランスフォーメーション)が叫ばれている現在、さまざまなデータの価値に注目が集まっています。それらのデータをグラフなどで可視化できるオープンソースのPythonフレームワーク(ライブラリ)が「Streamlit」です。特集1では、初心者にも分かりやすいように、実際に手を動かすという形で、Streamlitによるグラフ作成方法を紹介しています。
 特集2では、オラクルのクラウドサービス「Oracle Cloud Infrastructure」(OCI)上で動作するデータベース管理システム「Oracleデータベース」を解説しています。OCI上でもOracleデータベースの全機能が利用できます。さらに「Autonomous Database」というサービスを利用すれば、日常的な運用管理を自動化できます。
 このほか、連載「Pythonあれこれ」では、機械学習ライブラリ「scikit-learn」について紹介しています。また、イベントレポートとして「第35回 全国高等専門学校プログラミングコンテスト」も掲載しています。
 今回も読み応え十分のシェルスクリプトマガジン Vol.93。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

Vol.93 補足情報

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

特集1 Streamlitで作るデータ分析Webアプリ

p.13の図15で示したコードで使用するデータファイル「data.csv」をここからダウンロードできます。

連載 香川大学SLPからお届け!

記事で紹介したコードを拡張して、コンピュータとの簡単な対戦ができるゲームを作成しました。ソースコードをまとめたアーカイブファイルはここからダウンロードできます。

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

Markdownを活用する(Vol.92掲載)

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

著者:藤原 由来

本連載では文書の装飾・構造付けを手軽に行える記法であるMarkdownを用いて、さまざまな文書や成果物を作成する方法を紹介します。今回は前回に引き続き、Markdownを用いて書籍を制作できる組版アプリ「Vivliostyle」について解説します。特に、技術同人誌の制作方法と、印刷所へ入稿する際の注意
点について説明します。 

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

図7 書き換えたVivliostyleの設定ファイル

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ファイル

# 科学の不思議
アンリイ・ファブル(大杉栄、伊藤野枝訳)

© 某サークル, 20xx

図9 奥付のcolophon.mdファイル

## 科学の不思議

20xx年x月x日 初版発行

----------------

- 発行 某サークル
- 著者 アンリイ・ファブル
- 翻訳 大杉栄、伊藤野枝
- 編集 シェル花子
- 連絡先 foo@example.com
- 印刷 サンプル印刷

----------------

底本は青空文庫から転載・改変しました。

- ファーブル ジャン・アンリ『科学の不思議』(大杉 栄、伊藤 野枝 訳)

© 某サークル, 20xx

図11 my-theme.cssファイル内のCSSを反映する設定

  theme: [
    '@vivliostyle/theme-techbook@^1.0.1',
    'themes/my-theme.css',
  ],

図12 扉と奥付用のCSS

/* 扉 */
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ファイルの変更

<section id="title-page">

# 科学の不思議

アンリイ・ファブル(大杉栄、伊藤野枝訳)

© 某サークル, 20xx

</section>

図14 奥付のcolophon.mdファイルの変更

<section id="colophon" role="doc-colophon">

## 科学の不思議

20xx年x月x日 初版発行

----------------

- 発行 某サークル
- 著者 アンリイ・ファブル
- 翻訳 大杉栄、伊藤野枝
- 編集 シェル花子
- 連絡先 foo@example.com
- 印刷 サンプル印刷

----------------

底本は青空文庫から転載・改変しました。

- ファーブル ジャン・アンリ『科学の不思議』(大杉 栄、伊藤 野枝 訳)

© 某サークル, 20xx

</section>

図17 目次を自動設定する設定

(略)
  entryContext: './manuscripts',
  toc: {
    htmlPath: 'toc.html', 
    title: '目次',
  },
  entry: [
   'index.md',
    { rel: 'contents' },
   '01.md',
(略)

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

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

著者:池内稜來斗

各都道府県の都道府県庁所在地を尋ねるクイズを出題するWebサイトをPHPで作成しました。今回は、そのWebサイトの概要や作成手順などを紹介します。PHPの実行環境や、各種サーバーソフトウエアは、「XAMPP」(https://www.apachefriends.org/)を使ってインストールします。

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

図2 「db.sql」ファイルに記述するSQL文

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」ファイルに記述するコード

<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」ファイルに記述するコード

<?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」ファイルに記述するコード

<?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」ファイルに記述するコード

<?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>

Pythonあれこれ(Vol.92掲載)

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

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第22回では、機械学習ライブラリ「PyTorch」を使って、画像内の、歩行者が写っている領域を自動認識するPythonプログラムを作成します。

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

図3 47番のデータを確認するPythonコード

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コード

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コード

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コード

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コード

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コード

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コード

# 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)

特別企画 イノシシ撃退機を作る ソフト作成編(Vol.92記載)

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

著者:米田 聡

「Interface」「CQ ham radio」「トランジスタ技術」などの雑誌を出版するCQ出版社の電子部品セット「イノシシ撃退機部品セットVer.1」を組み立て、ソフトウエアに一工夫を加えてイノシシ撃退機を作ります。今回は完成したハードウエア向けにソフトウエアを作成します。

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

図18 PWM機能の初期化

(略)
#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 割り込みハンドラ

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 ダブルバッファを使用する修正

■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
}

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

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

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の働き方の新しい仕組みを作る」/シェル魔人 

Vol.92

投稿日:2024.09.25 | カテゴリー: バックナンバー

 小型コンピュータボード「Raspberry Pi」が登場して約12年が過ぎました。現在では、AI(人工知能)処理ができ、パソコン並みの性能を持つものに進化しています。ただ、センサーなどの制御、液晶や有機ELなどのパネルへの出力といった分野では、初期のRaspberry Piでも十分活躍できます。特集1では、初期のRaspberry Piを復活する方法を紹介しています。具体的には、無線通信機能を付加して使い勝手を向上します。
 特集2では、NTTデータ最先端技術が開発する統合運用管理ソフトウエア「Hinemos」を解説しています。同ソフトウエアは、オープンソースであり、誰でも無料で利用できます。サーバーやネットワークなどのシステムの監視やジョブ管理、運用の自動化を実現します。
い。
 特別企画では、前号に引き続きイノシシ撃退機、加えてCentOS Linuxの代替となるLinuxディストリビューション「Rocky Linux」を扱っています。
 このほか、連載「Pythonあれこれ」では機械学習ライブラリ「PyTorch」でAIによる歩行者認識の実現方法を紹介しています。
 今回も読み応え十分のシェルスクリプトマガジン Vol.92。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

Vol.92 補足情報

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

連載 香川大学SLPからお届け!

記事中で作成したクイズWebサイト用のコードや、データベースへのデータ入力用のファイルをまとめたアーカイブファイルがここからダウンロードできます。

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

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

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

著者:佐藤 楓真

今回は、「焼きなまし法」と呼ばれる手法で、すべての都市を1回訪問して出発地点に帰るまでの最短経路を求める「巡回セールスマン問題」を近似的に解いてみます。焼きなまし法のベースとなる「山登り法」についても解説します。使用するプログラミング言語は、C++です。

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

図3 図1の巡回セールスマン問題を山登り法で解くC++プログラムの例

#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++プログラム

#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++プログラムの例

#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++プログラムの例

#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;
}

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

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

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 コラム「要求工学を取り入れる」/シェル魔人

これだけは覚えておきたいLinuxコマンド(Vol.91掲載)

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

著者:大津 真

本連載では、Linuxの初心者に覚えておいてほしいコマンドを紹介していきます。ここで紹介するコマンドさえ押さえておけば、基本的な操作に困ることはなくなるでしょう。第2回では、主にテキストファイルを処理するための基本コマンドについて解説します。

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

図6 「members.csv」ファイルの内容

001,大津一郎,男,43,東京
002,江南直子,女,8,埼玉
003,唐木田信ー,男,43,福岡
004,森岡一郎,男,41,東京
005,秋山敬一郎,男,44,秋田
006,山田謙一,男,9,福岡
007,山田一郎,男,45,東京
009,福岡花子,女,35,東京
010,白戸二郎,男,39,北海道

特集2 ChatGPTで手軽に実現(Vol.91記載)

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

著者:佐藤 秀輔

対話型AIサービス「ChatGPT」が注目を集めています。特に、2023年3月に登場した「GPT-4」という大規模言語モデル(LLM)を利用可能になってからは自然言語の理解力が飛躍的に向上し、指示に基づいて、さまざまな処理を自動化できるようになっています。本特集では、ChatGPTを個人で活用する例として、家計簿レビューをさせる方法と、API(Application Programming Interface)を利用してWebスクレイピングをさせる方法を紹介します。

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

図1 ランダムな家計簿データを生成するPythonスクリプト

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ページの例

<!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スクリプトの例

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)

Vol.91

投稿日:2024.07.25 | カテゴリー: バックナンバー

 オラクルといえば、基幹システムでも利用されるリレーショナルデータベース管理システム(RDBMS)製品を開発・販売する企業というイメージがあります。しかし、それだけではありません。「AWS」(Amazon Web Services)や「Microsoft Azure」、「GCP」(Google Cloud Platform)と同等のクラウドサービスとして「Oracle Cloud Infrastructure」(OCI)を提供しています。
 特集1では、このOCIの特徴を分かりやすく解説しています。また、30日間無料で試せる「OCI Cloud Free Tier」を利用してIaaS(Infrastructure as a Service)によるコンピュートインスタンスの立ち上げ方法も紹介しています。実際に触ってみて、データベースだけでないOCIの魅力を感じてください。
 特集2では、生成AIチャット「ChatGPT」の実用例として、支出を分析する「家計簿レビュー」と、Webページから必要な情報を抽出する「Webスクレイピング」の二つを紹介しています。前者は対話形式で分析、後者はAPI(Application Programming Interface)を用いて情報を抽出しています。話題のChatGPTの能力を体験してみてください。
 特別企画では、「Interface」「CQ ham radio」「トランジスタ技術」などの雑誌を出版するCQ出版社の協力を得て、同社が販売する「イノシシ撃退機部品セットVer.1」の組み立て方を紹介しています。農業分野でもITを活用することで、さまざまなメリットが生まれます。このイノシシ撃退機は、イノシシの鳴き声を使うため、安全にイノシシを追い払えます。
 このほか、ユニケージレポートでは、長崎県で2024年6月12~14日に開催された「ソフトウェア・シンポジウム2024」でのユニケージ技術発表と、シェルやシェルスクリプトに関するワークショップを紹介しました。
 今回も読み応え十分のシェルスクリプトマガジン Vol.91。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

Markdownを活用する(Vol.91掲載)

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

著者:藤原 由来

本連載では文書の装飾・構造付けを手軽に行える記法であるMarkdownを用いて、さまざまな文書や成果物を作成する方法を紹介します。今回は、Markdownを用いて書籍を制作できる組版アプリ「Vivliostyle」の導入と、書籍作成の基本について説明します。

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

図11 chap1.mdファイルの内容

# 後注と脚注の例

これは後注[^1]の例です。
インラインで後注^[これも章の後ろに表示される]もできます。

[^1]: 各章の後部にまとめて出力されるタイプの注釈

こちらは脚注<span class="footnote">ページ直下に表示される</span>です。

図12 chap2.mdファイルの内容

# {吾輩|わがはい}は猫である。

{吾輩|わがはい}は猫である。名前はまだ無い。

Pythonあれこれ(Vol.91掲載)

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

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第21回では、「ハノイの塔」の解法「GDHP(Gniibe Distributed Hanoi Protocol)」の手順をアニメーションで表示するPythonアプリを作成します。

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

図2 GDHPの手順をアニメーションで表示するJavaScriptコード

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コード

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コード

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)

Vol.91 補足情報

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

特集2 ChatGPTで手軽に実現

 クラウド型家計簿アプリ「Dr.Wallet」のサンプルデータがここからダウンロードできます。家計簿を付けていない人は、こちらを利用してください。なお、本サンプルデータ中のショップ名、電話番号、住所の情報はすべて架空のものです。実在の企業や事業者とは関係ありません。

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

Markdownを活用する(Vol.90掲載)

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

著者:藤原 由来

本連載では文書の装飾や構造付けを手軽に行える記法である「Markdown」を用いて、さまざまな文書や成果物を作成する方法を紹介します。今回も前回に引き続き、Markdownベースの高機能なノートアプリ「Obsidian」を扱います。前回の内容を踏まえて、Obsidianの発展的な使い方を紹介します。

図11 デイリーノートのテンプレート(テンプレート_カレー日記)

テンプレート_カレー日記
作成日時:{{date}} {{time}}

## 今日のカレー

- メニュー:
- 感想:
- 評価:★★★☆☆

## お店の情報

- 店名:
- 住所:
- 最寄り駅:
- URL:

Vol.90

投稿日:2024.05.25 | カテゴリー: バックナンバー

 AI(人工知能)による文章、画像、映像、会話、音楽などの自動生成技術「生成AI」がとても注目されています。特に、レポートで紹介した、米OpenAI社の大規模言語モデル(LLM)「GPT-4o」は、人と会話しているように話せる驚きの技術です。シェルスクリプトマガジンでもこの生成AIに着目し、特集1と特集2でも生成AIに関するサービスや技術を取り上げました。
 特集1では、マイクロソフトが提供する生成AIサービス「Microsoft Copilot」を紹介しています。同サービスは、オフィスソフト「Microsoft Office」を含むクラウドサービス「Microsoft 365」から利用できます。そのため、ビジネス分野における生成AIの活用が期待できます。
 特集2では、米NVIDIA社の生成AIフレームワーク「NVIDIA NeMo」による独自生成AI環境を構築する方法を解説しています。同社は、生成AIや機械学習などに不可欠な半導体チップ(素子)「GPU」(Graphics Processing Unit)において世界トップシェアの企業であり、AIに関する多くの最先端技術を有しています。
 特集3では、2024年2月に国内販売が開始された小型コンピュータボードの最上位機種「Raspberry Pi 5」を紹介しています。Raspberry Pi 5では、CPUやGPUの性能が向上し、PCI Expressの外部インタフェースが追加されています。このPCI ExpressにSSDを接続することで、ストレージの高速化と大容量化が期待できます。AI分野でも活用が進む小型コンピュータとして注目の製品です。
 特別企画では、2024年6月末でサポートが切れるLinuxディストリビューション「CentOS Linux」の代替となる「AlmaLinux」のインストール方法を紹介しました。AlmaLinuxは無料で利用でき、サポートが必要なら国内でも有償で受けられます。ビジネスの分野で利用する場合なら、お薦めの代替候補です。
 このほか、新連載として「これだけは覚えておきたいLinuxコマンド」が始まりました。数回に分けて、初心者向けに知っておくべきLinuxコマンドをまとめています。
 今回も読み応え十分のシェルスクリプトマガジン Vol.90。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

Raspberry Pi Pico W/WHで始める電子工作(Vol.90掲載)

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

著者:米田 聡

本連載では、人気のマイコンボード「Raspberry Pi Pico W/WH」を活用していきます。同ボードは、無
線LANやBluetoothの通信機能を搭載し、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第6回は、DHT-11で測った湿温度をグラフ化します。

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

図2 10 分おきに湿温度を測定して配列に保存する「DhtDataLogger」クラスのコード(ddl.py)

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)

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()

特集2 NVIDIA NeMoで独自の生成AIを構築する(Vol.90記載)

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

著者:澤井 理紀、藤田 充矩

大規模言語モデル(LLM)に基づく生成AIが注目を集めています。そうした生成AIを活用するには、LLMを特定のタスクやデータセットに最適化する「ファインチューニング」が欠かせません。本特集では、生成AIを支える技術を解説した上で、NVIDIAが提供する生成AI向けのフレームワーク「NVIDIA NeMo」を使って独自の生成AIをローカル環境に構築し、LLMをファインチューニングする方法を紹介します。

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

図2 LLMダウンロード用のPythonスクリプト

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スクリプト

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()

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

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

著者:小河原 直道

Raspberry Pi 5(以下、ラズパイ5)が日本でも発売され、私も購入しました。センサーとの接続やサーバーの構築をしてみたいと思ったため、湿温度/気圧/CO₂濃度を測定できるセンサーを接続し、その値をWebブラウザから確認できるシステムを作成しました。このシステムの作成ポイントについて解説します。

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

図8 BME280のチップIDを読み出すPythonコード

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コード

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コード

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コード

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」ファイルに記述するコード

const mariadb = require("mariadb");
const pool = mariadb.createPool({
  host:     "localhost",
  user:     ユーザー名,
  password: パスワード,
  database: "sensor1",
  connectionLimit: 5,
});
module.exports = pool;

図14 「route/api_v1.js」ファイルに記述するコード(抜粋)

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」ファイルに記述するコード

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」ファイルに記述するコード

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");
});

Vol.90 補足情報

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

特別企画 AlmaLinux 9のインストール

 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)はここからダウンロードできます。

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

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

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

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 コラム「プロジェクトリーダーの役割」/シェル魔人

Pythonあれこれ

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

筆者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第20回では、データの可視化などにも活用できるWebアプリケーションフレームワーク「Streamlit」の、より進んだ使い方を紹介します。

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

図4 プルダウンメニューでサブページに移動できるようにしたコード

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」ファイルに記述するコード

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」ファイルに記述するコード

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」ファイルに記述するコード

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')

Vol.89 補足情報

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

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

Linux定番エディタ入門

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

筆者:大津 真

文章の作成やプログラミングに欠かせないのがテキストエディタ(以下、エディタ)です。この連載では、Linuxで利用できる定番エディタの特徴と使い方を解説していきます。最終回となる今回も、LinuxだけでなくWindowsやmacOSでも利用可能なGUIエディタの定番「Visual Studio Code(VSCode)」を引き続き紹介します。

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

図1 「test1.md」ファイルに記述するテキストデータ

# VSCode 入門
VSCode はシンプルで扱いやすいエディタです。

## いろいろなエディタ
1. VSCode
2. Vim
3. nano

## VSCode の特徴
- プラグインによる拡張
- ワークスペースによる管理
- コマンドパレットによるコマンド実行

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

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

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 コラム「顧客と一心同体の精神を育成」/シェル魔人

Markdownを活用する(Vol.89掲載)

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

著者:藤原 由来

本連載では文書の装飾や構造付けを手軽に行える記法である「Markdown」を用いて、さまざまな文書や成果物を作成する方法を紹介します。第1回はMarkdownベースの高機能なノートアプリ「Obsidian」の初歩的な使い方と、Markdownの基本的な記法を説明します。

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

図2 Markdownの記述例

# カレーの作り方

おいしいカレーを作りましょう。

## 材料

- カレールー
- 牛肉
- 野菜
- サラダ油
- 水

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

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

著者:米田 聡

本連載では、人気のマイコンボード「Raspberry Pi Pico W/WH」を活用していきます。同ボードは、無線LANやBluetoothの通信機能を搭載し、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第5回は、DHT-11で測った湿温度をスマートフォンに通知します。

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

図4 LINE Notifyを使って通知を送る「notify()」関数を定義したPythonプログラム

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)

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あれこれ(Vol.89掲載)

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

著者:飯尾 淳

本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第19回では、データの可視化などにも活用できるWebアプリケーションフレームワーク「Streamlit」の基本的な使い方を紹介します。

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

図7 「magic.py」ファイルに記述するコード

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 キャッシュを設定するコードの例

'### チャートの取り扱い'
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アプリのコード

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 ウィジェットをサイドバーに配置するコードの例

import streamlit as st

selected = st.sidebar.selectbox(
    'なに食べる?',
    ('ramen', 'curry', 'katsudon'),
)

st.image(selected + '.jpg')

Vol.89

投稿日:2024.03.25 | カテゴリー: バックナンバー

 「Amazon Elastic Compute Cloud」(Amazon EC2)は、米Amazon Web Services(AWS)社のクラウドコンピューティングサービスの中で最も人気があります。Amazon EC2は、仮想化技術を用いて、CPUやメモリー、ストレージなどのサーバーハードウエア資源を提供する「IaaS」(Infrastructure as a Service)というサービスに分類されます。
 特集1では、このAmazon EC2のサービス内容について初めての人にも分かりやすく解説します。また、無料利用枠を用いてLinuxサーバーを稼働する手順も紹介します。
 特集2では、オンラインストレージ(クラウドストレージ)サービスの「Box」について解説します。最近では、データの重要性が高まってきています。そのデータは、データベースのような構造化されたものだけではありません。文書ファイルなども貴重なデータです。そのため、オンラインストレージは、単なるファイルの保管場所といった役割にとどまりません。ファイルというコンテンツを、いかに安全に共有・活用できるのかが大きな役割になってきています。
 このほか、新連載として「Markdownを活用する」が始まります。Markdownは、文書の装飾や構造付けを手軽に行えるテキスト記法です。このMarkdownを覚えることで、レポートやスライド、書籍などの多種多様な文書を作成できます。
 今回も読み応え十分のシェルスクリプトマガジン Vol.89。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

Vol.88

投稿日:2024.01.25 | カテゴリー: バックナンバー

 2024年6月末に「CentOS Linux 7」のサポートが終了します。CentOS Linuxは、無償のRed Hat Enterprise Linux(RHEL)互換Linuxディストリビューションで、多くの企業で利用されています。このような状況ですが、CentOS Linux 7の後継であるCentOS Linux 8はすでにサポートが終了し、現在CentOS Linuxの開発は行われていません。そのため、CentOS Linux 7の移行先を早急に考える必要があります。
 特集1では、CentOS Linux 7の移行先となる候補と、候補から移行先を選定するときのポイントを解説します。
 特集2では、自宅にLinuxサーバーを構築する方法を紹介します。数年前に比べ、クラウドサービスを利用すれば、個人でも簡単にLinuxサーバーを運用できます。よって、自宅にLinuxサーバーをわざわざ構築する意味はあまりありません。ただし、Linuxサーバー構築には、Linuxの基本操作、サーバーソフトの導入方法や使い方、インターネットの仕組み、セキュリティを含めたサーバーの運用管理方法など、ITに関する多くの知識が詰まっています。自宅にLinuxサーバーを構築してそれらの知識を身に付けましょう。
 特別企画は、動画投稿・共有サイト「YouTube」で人気の「ゆっくり実況」「ゆっくり解説」を作成できる無料動画編集ソフト「ゆっくりMovieMaker4」の第3弾(最終回)です。今回は、ストーリやシナリオの組み立て方、表示回数や視聴回数などのデータ分析など、人気が出る動画を制作するためのノウハウを解説します。
 このほか、連載の「Raspberry Pi Pico W/WHで始める電子工作」では日本語表示の湿温度計、「Pythonあれこれ」と「香川大学SLPからお届け!」ではそれぞれ、AI(人工知能)を使った顔検出とじゃんけんマシンを紹介します。
 今回も読み応え十分のシェルスクリプトマガジン Vol.88。お見逃しなく!

※記事掲載のコードはこちら。記事の補足情報はこちら

※読者アンケートはこちら

Raspberry Pi Pico W/WHで始める電子工作(Vol.88掲載)

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

著者:米田 聡

本連載では、人気のマイコンボード「Raspberry Pi Pico W/WH」を活用していきます。同ボードは、無
線LANやBluetoothの通信機能を搭載し、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第4回は、モノクロ有機ELディスプレイと湿温度センサー「DHT-11」で日本語表示の湿温度計を作ります。

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

図9 温度と湿度を日本語で表示するプログラム(temp_hum.py)

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)

Vol.88 補足情報

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

特集2 自宅Linuxサーバー構築

p.14の「Windows 10のサポートが終了する2024年6月末ごろ」は「Windows 10(バージョン22H2)のサポートが終了する2025年10月14日」の誤りです(詳しくサポート期間は、こちら)。お詫びして訂正いたします。

Windows 10(バージョン22H2)は、まだ1年半以上のサポートが受けられますので、安心してお使いください。

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

もっと見る

-->