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

test

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

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

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

作りながら学ぶ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 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 コラム「意識教育」/シェル魔人

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。お見逃しなく!

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

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

ユニケージ通信(第4回)

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

JOIN句のようなテーブル結合

 業務システム開発手法の「ユニケージ」では、データベース管理システムやフレームワークなどを使わずにシステムを構築します。システムの中身が見えやすい分、仕組みやルールを理解していないと正しい開発ができません。第4回はSQLの「JOIN」句のようなテーブル結合を紹介します。


 業務システムなどでは、リレーショナルデータベース管理システム(RDBMS)内に作成したテーブル上にデータを格納しています。一般的に、同じ物や人などに関連する情報を一つの大きなテーブルで管理しているわけではなく、用途別や属性別に複数のテーブルに分けられて保存されています。
 このような場合、ある業務アプリケーションが必要とする情報が複数のテーブルにまたがっている場合もあります。それらの情報を一つにまとめることができれば、アプリケーションからの利用が簡単になります。
 このような複数のテーブルを一つにまとめる処理が「テーブル結合」です。ユニケージではデータを管理するのがテーブルではなく、テキストファイルという違いがあるにせよ、テーブル結合は重要な機能です。

内部結合と外部結合

 テーブル結合には、主に「内部結合」(INNER JOIN)と「外部結合」(OUTER JOIN)の2種類があります。テーブル結合では、結合する二つのテーブル上にある「キー」となる値を使って結合します。キーは、レコード(行)ごとに管理しているデータの識別子となるカラム(列)のデータです。識別子とするため、キーの値はユニーク(唯一無二)でなければいけません。
 内部結合では、キーの値で指定した条件に一致するレコードが双方のテーブルに存在する場合、そのレコードを結合して抽出します(図1)。SQLで内部結合を実行する場合、「SELECT」文の中で「INNER JOIN」句を指定します。なお、「INNER」の文字は省略できます。
 外部結合は、キーの値で指定した条件に一致するレコードが基準となるテーブルに存在する場合、基準となるテーブル内のレコードを基にして結合・抽出を実施します(図2)。基準としないもう一つのテーブルに、キーの値で指定した条件に一致するレコードがなければ、カラムの値を「NULL」(空)として結合します。

 基準となるテーブルが左か右かで外部結合を実行するSQLが異なります。左の場合、SELECT文で「LEFT OUTER JOIN」句を指定します。こうすれば、「FROM」句で指定したテーブルが基準になります。右の場合は「RIGHT OUTER JOIN」句を指定します。こちらはRIGHT OUTER JOINの後に指定したテーブルが基準となります。なお、どちらも「OUTER」の文字は省略できます。
 外部結合には「完全外部結合」(FULL OUTER JOIN)もあります。完全外部結合では、条件に一致しないレコードも結合・抽出されます。結合するレコードがない場合、カラムの値はNULLとして結合します。
 このほか、内部結合でも外部結合でもない交差結合(CROSS JOIN)もあります。交差結合では、キーに関係なく、双方のテーブル上に存在する各レコードの組み合わせでテーブルを結合・抽出します。

ユニケージのテーブル結合

 ユニケージの専用コマンド群「usp Tukubai」には、タグ形式で保存されたデータファイルを結合するコマンドとして「tagjoin1」「tagjoin2」「tagloopj」「tagloopx」などが用意されています。内部結合はtagjoin1、外部結合はtagjoin2、完全外部結合はtagloopj、交差結合ではtagloopxが使えます。
 なお、いずれのコマンドも次の二つ条件を満たしていないと正しく動作しません。後述するマスタファイルの第1カラムに条件となるキーを配置すること、キーとなるカラムの値が昇順に並んでいることです。昇順に並んでいない場合、usp Tukubaiの「tagmsort」コマンドを使って並べ替えておきます。
 tagjoin1、tagjoin2、tagloopj、tagloopxはいずれも、SELECT文の「WHERE」句のように出力するカラムを絞り込んで抽出する機能を備えていません。カラムを絞り込むには、本連載第3回で紹介した「tagself」「tagawk」「tagcond」のコマンドと組み合わせます。ちなみに、SELECT文のように複雑な検索や結合ができるのも便利です。しかし、usp Tukubaiのように各コマンドを単機能にした方がプログラムの可読性が高まり、バグの発生を抑えられます。

 それでは、二つのデータファイルを用意し、ユニケージによる内部結合と外部結合を実行していきます。用意した二つのテーブルは図3の「master」ファイル、図4の「tran」ファイルです。masterファイルには「CODE」(社員番号)、「NAME」(名前)、「AGE」(年齢)が、tranファイルには「DATE」(日時)、「CODE」(社員番号)、「AMOUNT」(金額)が格納されています。

 ユニケージでは「マスタファイル」と「トランザクションファイル」という言葉でデータファイルの性質を表します。マスタファイルは、結合時や抽出時に参照される名簿や台帳のようなファイルです。トランザクションファイルは、結合や抽出を行う際に基準となるファイルです。

内部結合

 どのキーで結合するのかの「key=カラム名」オプションと、マスタファイル、トランザクションファイルの順にデータファイルをtagjoin1コマンドの引数に指定し、次のように実行すると、内部結合を実行できます。

$ tagjoin1 key=CODE master tran ↵
DATE CODE NAME AGE AMOUNT
20230401 0000003 杉山 26 200
20230402 0000003 杉山 26 400
20230405 0000003 杉山 26 600
20230401 0000005 崎村 50 250
20230402 0000005 崎村 50 450
20230402 0000007 梶川 42 210
20230404 0000007 梶川 42 410
20230406 0000007 梶川 42 610

 まず、マスタファイル内に存在するキーの値からトランザクションファイルのレコードを抽出します。抽出したレコードのキーとなるカラムの後に、マスタファイルのレコードを追加して内部結合の処理が完了します。

外部結合

 どのキーで連結するのかの「key=カラム名」オプションと、マスタファイル、トランザクションファイルの順にデータファイルをtagjoin2コマンドの引数に指定し、次のように実行すると、外部結合を実行できます。なお、後に指定したトランザクションファイルが基準となるので左右を入れ換えるには引数に指定するデータファイルの順番を変えます。

$ tagjoin2 key=CODE master tran ↵
DATE CODE NAME AGE AMOUNT
20230401 0000001 _ _ 300
20230403 0000001 _ _ 500
20230404 0000001 _ _ 700
20230401 0000003 杉山 26 200
20230402 0000003 杉山 26 400
20230405 0000003 杉山 26 600
20230401 0000005 崎村 50 250
20230402 0000005 崎村 50 450
20230402 0000007 梶川 42 210
20230404 0000007 梶川 42 410
20230406 0000007 梶川 42 610

 マスタファイルに存在しない、結合すべきレコードは、カラムの値にNULLとして「_」(アンダーバー)を挿入して結合・抽出します。


 今回は、ユニケージでのテーブル結合を紹介しました。実際のユニケージによる開発では何百万レコードにも及ぶデータファイルの結合を実施して帳票などを作成しています。本連載では、SQLとの違いが分かりやすいようにタグ付きのデータファイルを使っていますが、本来のユニケージ開発ではタグなし
のデータファイルが一般的です。よって、usp Tukubaiの「join1」と「join2」といったコマンドを使ってテーブル結合を実施します。
 次回は、ユニケージにおける排他制御について説明する予定です。

著者:田渕 智也、高橋 未来哉

※本記事は、シェルスクリプトマガジン Vol.85(2023年8月号)に掲載した記事からの転載です。

Vol.97 補足情報

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

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

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

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

ユニケージ通信(第3回)

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

SQLのようなデータ抽出操作

業務システム開発手法の「ユニケージ」では、データベース管理システムやフレームワークなどを使わずにシステムを構築します。システムの中身が見えやすい分、仕組みやルールを理解していないと正しい開発ができません。第3回は、専用コマンドでSQLのSELECT文のようなデータ抽出操作を実施します。


 今回は、ユニケージの専用コマンド群「usp Tukubai」を用いて、タグ形式で保存したデータファイルに対し、データベース問い合わせ言語「SQL」のようなデータ抽出の操作を実施します。タグ形式で保存したデータファイルとは、1行目に半角スペースで分けられたデータの項目(タグ名)が並んでいて、2行目以降にその項目ごとに分けられたデータを保存しているテキストファイルです。

SQLのような抽出操作ができるコマンド

 usp Tukubaiには、タグ形式のデータファイルを扱うコマンドが多数用意されています。例えば、「tagself」「tagawk」「tagsort」「tagcond」「tagjoin0」「tagjoin1」「tagjoin2」といったコマンドです。これらのコマンドでは、SQLの「SELECT」(検索)文のようなデータ抽出やデータ結合などの操作が可能です(表1)。このほかにもタグ形式のデータファイルを処理するコマンドは30種類以上あり、条件によって組み合わせて使用できます。

 usp Tukubaiに含まれるすべてのコマンドに共通していますが、ファイルに対してコマンドを適用しても、元のファイルを書き換えるわけではありません。コマンドの実行結果は、標準出力に渡され、元のファイルは残っています。このような特徴からSQLの「INSERT」(挿入)文、「DELETE」(削除)文、「UPDATE」(更新)文のような操作をするユニケージ専用コマンドはありません。しかし、シェルのリダイレクトである「>」を使って標準出力の内容をファイルとして出力することによって、INSERT文やDELETE文、UPDATE文、さらにテーブルを作成する「CREATE」文を実現しています。
 それでは、先ほどのtagself、tagawk、tagsort、tagcondのコマンドをSQL文と比較しながら紹介します。なお、tagjoin0、tagjoin1、tagjoin2といった、テーブルを結合するコマンドは次回に詳しく解説する予定です。
 紹介時に用いる元データとして「SAMPLE」ファイル(図1)を用意します。これらのファイルやテーブルには、「id」(社員番号)、「name」(名前)、「english」(英語名)、「residence」(居住地)、「age」(年齢)、「start」(入社年度)、「os_name」(使用パソコン)が書き込まれています。
 また、図2のSQLを実行し、MySQLなどのデータベース管理システムにSAMPLEファイルと同じ内容となる「SAMPLE」テーブルを作成します。

指定した項目のデータを抽出

 tagselfは、引数に指定した項目のデータを抽出するコマンドです。tagselfコマンドの基本構文は、次のようになります。抽出したい項目(タグ)の名前を半角スペースで区切って並べて、最後にデータファイル名を指定します。

tagself 項目名 項目名… データファイル名

 例えば、SAMPLEファイルから社員の名前と年齢だけを抽出するなら、

$ tagself name age SAMPLE ↵
name age
山田 24
佐藤 39
吉田 28
鈴木 27
小林 36
高橋 42

のようにtagselfコマンドを実行します。項目名付きで抽出したデータが表示されます。これは、次のSQLを実行したのと同じ結果です。

> SELECT name,age FROM SAMPLE; ↵
+--------+------+
| name | age |
+--------+------+
| 山田 | 24 |
| 佐藤 | 39 |
| 吉田 | 28 |
| 鈴木 | 27 |
| 小林 | 36 |
| 高橋 | 42 |
+--------+------+
6 rows in set (0.001 sec)

条件付きでデータを抽出

 SQLのSELECT文には、条件を指定してデータを抽出する「WHERE」句があります。例えば、SQLでSAMPLEデータベースから30歳よりも年齢の高い社員の名前、年齢を抽出するなら、

> SELECT name,age FROM SAMPLE WHERE age > 30; ↵
+--------+------+
| name | age |
+--------+------+
| 佐藤 | 39 |
| 小林 | 36 |
| 高橋 | 42 |
+--------+------+
3 rows in set (0.001 sec)

のように実行します。ユニケージで同じような処理を実行するには、次のようにtagawkコマンドで条件を指定します。

$ tagawk 'NR == 1{print %name,%age}NR > 1 && %age > 30{print %name,%age}' SAMPLE ↵
name age
佐藤 39
小林 36
高橋 42

 tagawkコマンドの基本構文は、

tagawk 'パターン{アクション}…' データファイル名

になります。パターンやアクションは、プログラミング言語「AWK」のパターンやアクションと同じような記述をします。
 パターンの「NR == 1」では1行目という条件、項目名が記されている行(1行目)を示しています。次の「NR > 1 && %age >30」は、2行目以降に対して、age項目が「30」より大きい行といった条件になります。これらの条件に一致したものに対して、それぞれのアクションに記した「print %name,%age」により「%項目名」で指定した名前と年齢のみが表示されます。なお、「%項目名」の指定は、AWKにはなく、tagawkコマンドの独自仕様です。
 tagawkコマンドでは、前述したようにAWKを知らないと、パターンやアクションの記述が難しいといえます。また、項目行も意識する必要があります。大小比較のような、複雑なパターンマッチングを必要としない条件であれば、tagcondコマンドが利用できます。
 tagcondコマンドの基本構文は、次のようになります。条件式には、表2のような比較演算子が使えます。

tagcond 条件式 データファイル名

 先ほどのSAMPLEファイルから30歳よりも年齢の高い社員の名前、年齢を抽出するなら、

$ tagcond '%age gt 30' SAMPLE | tagself name,age ↵

のように実行します。tagcondコマンドでは、1行目を項目行と判断して2行目以降に条件式を適用します。なお、tagcondコマンドには、項目を絞り込む機能がありません。よって、tagselfコマンドを併用します。
 tagcondコマンドは、複雑な条件を必要としない場合に用いると述べましたが、SQLのWHERE句の「AND」や「OR」のように「&&」や「||」を使って複数の条件を組み合わせられます。
 例えば、30代の社員の名前、年齢を抽出するなら、次のようにtagcondコマンドを実行します。

$ tagcond '%age ge 30 || %age lt 40' SAMPLE
| tagself name,age ↵

データを並べ替える

 SQLのSELECT文には、データを並べ替える「ORDER BY」句があります。例えば、SAMPLEデータベース内の社員名と出身地、社員の英語名を英語名の昇順に並べるなら、

> SELECT name,residence,english FROM SAMPLE ORDER BY english; ↵
+--------+-----------+-----------+
| name | residence | english |
+--------+-----------+-----------+
| 小林 | 東京 | kobayashi |
| 佐藤 | 東京 | sato |
| 鈴木 | 静岡 | suzuki |
| 高橋 | 名古屋 | takahashi |
| 山田 | 埼玉 | yamada |
| 吉田 | 沖縄 | yoshida |
+--------+-----------+-----------+
6 rows in set (0.001 sec)

のように実行します。ユニケージで同じ処理を実行するには、次のようにtagmsortコマンドとtagselfコマンドを使います。

$ tagmsort key=english SAMPLE | tagself name residence english ↵
name residence english
小林 東京 kobayashi
佐藤 東京 sato
鈴木 静岡 suzuki
高橋 名古屋 takahashi
山田 埼玉 yamada
吉田 沖縄 yoshida

 「key=項目名」には、並べ替える対象とする項目を記載します。データファイルの1行目は、項目行だと認識されて、2行目以降の並べ替えが実施されます。tagcondコマンドと同様に、tagmsortコマンドには、出力項目を絞り込む機能はないのでtagselfコマンドを併用します。
 tagmsortコマンドは、並べ替えのアルゴリズムとして「マージソート」を利用しています。また、ユニケージでは大量データの高速な並べ替え処理も必要とされているため、すべてのデータをメモリー上に読み込んでからソート処理を実施しています。
 tagmsortコマンドでは、文字列(文字コード)による昇順の並べ替えがデフォルトです。ただし、ORDER BY句の「ASC」(昇順)や「DESC」(降順)のような並べ替えも可能です。具体的には、文字列による降順なら項目名の後に「:r」を、数値による昇順なら「:n」を、数値による降順なら「:N」を付与します。
 例えば、入社年度の降順で社員名と出身地、入社年度を並べ替えるなら、SQLでは、

> SELECT name,residence,start FROM SAMPLE ORDER BY start DESC; ↵
+--------+-----------+-------+
| name | residence | start |
+--------+-----------+-------+
| 山田 | 埼玉 | 2021 |
| 鈴木 | 静岡 | 2018 |
| 吉田 | 沖縄 | 2016 |
| 佐藤 | 東京 | 2012 |
| 小林 | 東京 | 2011 |
| 高橋 | 名古屋 | 2009 |
+--------+-----------+-------+
6 rows in set (0.001 sec)

のように実行します。ユニケージなら次のようにtagmsortコマンドとtagselfコマンドを実行します。

$ tagmsort key=start:N SAMPLE | tagself name residence start ↵
name residence start
山田 埼玉 2021
鈴木 静岡 2018
吉田 沖縄 2016
佐藤 東京 2012
小林 東京 2011
高橋 名古屋 2009

 tagmsortコマンドでは、複数の条件での並べ替えも実施できます。「key=項目名」の後に「@」を付けて項目名を並べます。

文字列の一致によるデータ抽出

 SQLのSELECT文には、指定した文字列に一致したデータを抽出するための「LIKE」句があります。例えば、SAMPLEデータベース内の使用パソコンで「LIN」が末尾に付くLinux OSを使用している、社員名、年齢、使用パソコンを調べるには、

> SELECT name,age,os_name FROM SAMPLE WHERE os_name LIKE '%LIN'; ↵
+--------+------+------------+
| name | age | os_name |
+--------+------+------------+
| 佐藤 | 39 | UBUNTU_LIN |
+--------+------+------------+
| 高橋 | 42 | RHEL_LIN |
+--------+------+------------+
2 row in set (0.001 sec)

のように実行します。LIKE句では正規表現が使え、「%」が0文字以上の任意の文字列、「_」が任意の1文字に対応します。
 LIKE句と同様の処理を、ユニケージでは条件検索で登場したtagcondコマンドで実行できます。次のように条件式に正規表現を使って記述します。

tagcond '%項目名 ~ /正規表現/' データファイル名

 先ほどのSQLをtagcondコマンドで書き換えると、次のようになります。

$ tagcond '%{os_name} ~ /LIN$/' SAMPLE | tagself name age os_name ↵
name age os_name
佐藤 39 UBUNTU_LIN
高橋 42 RHEL_LIN

 「$」が行末を表す正規表現です。ちなみに、行頭は「^」、任意の1文字は「.」で表します。


 今回は、SQLのSELECT文をユニケージの専用コマンドで表してみました。次回は、今回紹介した以外の演算子、「IN」や「HAVING」、テーブル結合の「JOIN」などの句をユニケージ専用コマンドで表現する方法を解説します。

著者:田渕 智也、高橋 未来哉

※本記事は、シェルスクリプトマガジン Vol.83(2023年4月号)に掲載した記事からの転載です。

  • shell-mag ブログの 2025年7月 のアーカイブを表示しています。

  • -->