2026/07/13(月)令和にPerlでCGIを作る

更新日:
投稿日:

令和の時代にPerlでCGI…?という感じだが、PHP-FPMの上で動いてるLaravelとかもCGIの親戚みたいなものなので、そこまで違和感はないと思う。何せFPMはFastCGI Process Managerの略だからね。

でも令和の時代にPerl…?というのは、ちょっと面白いと思っている。

確認環境

Env Ver
Perl v5.38.2
fcgiwrap 1.1.0-14build1
nginx 1.24.0

実装方法

nginxの設定例

この設定ではドメイン直下のどこにアクセスされてもファイルがなければindex.cgiに飛ばしてCGIにルーティングを処理させるようにしている。

パスごとにCGIファイルを配置する方式ならtry_filesは不要だ。

server {
  listen [::]:443 ssl;
  server_name example.com;

  ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

  root /var/www;

  location / {
    index index.cgi;
    try_files $uri $uri/ /index.cgi;
  }

  location ~ ^.+\.cgi$ {
    fastcgi_pass unix:/var/run/fcgiwrap.socket;
    fastcgi_index index.cgi;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param HTTPS on;
    fastcgi_param SERVER_PORT 443;
  }
}

最低限の実装

Hello worldを出すだけの奴。レスポンスヘッダを書かないとnginxではエラーになった。Apacheでは試していない。

コード
#!/bin/env perl

# HTTPメッセージに近い構造になっている
# レスポンスヘッダを書いている
print "Content-Type: text/html; charset=UTF-8\r\n\r\n";
# レスポンスボディ
print 'Hello world';

これをindex.cgiとかで置いてやって所有者をwww-data:www-dataにして実行権限を付与したら動く。

sudo chown www-data:www-data index.cgi
sudo chmod u+x index.cgi

実行時エラーを出したりする

ファイルの先頭に以下の二行を加えると、実行時エラーをサーバーログに吐き出すようになる。

use strict;
use warnings;
コード
#!/bin/env perl
use strict;
use warnings;

# HTTPメッセージに近い構造になっている
# レスポンスヘッダを書いている
print "Content-Type: text/html; charset=UTF-8\r\n\r\n";
# レスポンスボディ
print 'Hello world';

環境変数を出す

コード
#!/bin/env perl
use strict;
use warnings;

print "Content-Type: text/html; charset=UTF-8\r\n\r\n";
print "QUERY_STRING => $ENV{QUERY_STRING}<br>";
print "REQUEST_METHOD => $ENV{REQUEST_METHOD}<br>";
print "CONTENT_TYPE => $ENV{CONTENT_TYPE}<br>";
print "CONTENT_LENGTH => $ENV{CONTENT_LENGTH}<br>";
print "SCRIPT_NAME => $ENV{SCRIPT_NAME}<br>";
print "REQUEST_URI => $ENV{REQUEST_URI}<br>";
print "DOCUMENT_URI => $ENV{DOCUMENT_URI}<br>";
print "DOCUMENT_ROOT => $ENV{DOCUMENT_ROOT}<br>";
print "SERVER_PROTOCOL => $ENV{SERVER_PROTOCOL}<br>";
print "REQUEST_SCHEME => $ENV{REQUEST_SCHEME}<br>";
print "HTTPS => $ENV{HTTPS}<br>";
print "GATEWAY_INTERFACE => $ENV{GATEWAY_INTERFACE}<br>";
print "SERVER_SOFTWARE => $ENV{SERVER_SOFTWARE}<br>";
print "REMOTE_ADDR => $ENV{REMOTE_ADDR}<br>";
print "REMOTE_PORT => $ENV{REMOTE_PORT}<br>";
print "REMOTE_USER => $ENV{REMOTE_USER}<br>";
print "SERVER_ADDR => $ENV{SERVER_ADDR}<br>";
print "SERVER_PORT => $ENV{SERVER_PORT}<br>";
print "SERVER_NAME => $ENV{SERVER_NAME}<br>";
実行結果

ライブラリを呼び出す

lib/Lyndbaum/Core.pmに次のライブラリを作成したとする。

パス名とpackage名の大文字小文字が一致してないと動かないので注意。

use strict;
use warnings;

package Lyndbaum::Core;

sub getEnv() {
    return (
        QUERY_STRING => $ENV{QUERY_STRING},
        REQUEST_METHOD => $ENV{REQUEST_METHOD},
        CONTENT_TYPE => $ENV{CONTENT_TYPE},
        CONTENT_LENGTH => $ENV{CONTENT_LENGTH},
        SCRIPT_NAME => $ENV{SCRIPT_NAME},
        REQUEST_URI => $ENV{REQUEST_URI},
        DOCUMENT_URI => $ENV{DOCUMENT_URI},
        DOCUMENT_ROOT => $ENV{DOCUMENT_ROOT},
        SERVER_PROTOCOL => $ENV{SERVER_PROTOCOL},
        REQUEST_SCHEME => $ENV{REQUEST_SCHEME},
        HTTPS => $ENV{HTTPS},
        GATEWAY_INTERFACE => $ENV{GATEWAY_INTERFACE},
        SERVER_SOFTWARE => $ENV{SERVER_SOFTWARE},
        REMOTE_ADDR => $ENV{REMOTE_ADDR},
        REMOTE_PORT => $ENV{REMOTE_PORT},
        REMOTE_USER => $ENV{REMOTE_USER},
        SERVER_ADDR => $ENV{SERVER_ADDR},
        SERVER_PORT => $ENV{SERVER_PORT},
        SERVER_NAME => $ENV{SERVER_NAME}
    );
}

1;
コード
#!/bin/env perl
use strict;
use warnings;

# @INCはライブラリをインクルードするパスを指定するための組み込み変数で
# unshiftは配列に要素を追加する関数
# つまりインクルードパスに./libを追加している
unshift(@INC, './lib');
# 読み込んだライブラリの使用
use Lyndbaum::Core;
# %で始まる変数は連想配列(ハッシュ)として機能する
my %e = Lyndbaum::Core::getEnv();

print "Content-Type: text/html; charset=UTF-8\r\n\r\n";
# ハッシュは$変数名{キー名}で参照できる
print "REQUEST_SCHEME: $e{REQUEST_SCHEME}<br>";
print "HTTPS: $e{HTTPS}<br>";
print "HOST: $e{HOST}<br>";
print "SERVER_NAME: $e{SERVER_NAME}<br>";
print "REQUEST_URI: $e{REQUEST_URI}<br>";
print "DOCUMENT_URI: $e{DOCUMENT_URI}<br>";
print "SCRIPT_NAME: $e{SCRIPT_NAME}<br>";

unshift(@INC, './lib');use lib './lib';元の構文みたいなものらしい。adiaryの実装がこうだったので真似してみたが、バッドプラクティスの可能性がある。

ステータスコードなどを返す

コード
#!/bin/env perl
use strict;
use warnings;

unshift(@INC, './lib');

use Lyndbaum::Core;

my %e = Lyndbaum::Core::getEnv();

# Status: <コード>でレスポンスステータスコードを返せる
# 指定しないと200になった
print "Status: 200\r\n";
# 好きなサーバー名を書いて君だけのサーバーを名乗ろう!
print "Server: Lyndbaum\r\n";
print "Content-Type: text/html; charset=UTF-8\r\n\r\n";
# レスポンスボディ
print "REQUEST_SCHEME: $e{REQUEST_SCHEME}<br>";
print "HTTPS: $e{HTTPS}<br>";
print "HOST: $e{HOST}<br>";
print "SERVER_NAME: $e{SERVER_NAME}<br>";
print "REQUEST_URI: $e{REQUEST_URI}<br>";
print "DOCUMENT_URI: $e{DOCUMENT_URI}<br>";
print "SCRIPT_NAME: $e{SCRIPT_NAME}<br>";

ルーティングする

パスが取れるのでもう朝飯前。

コード
#!/bin/env perl
use strict;
use warnings;

unshift(@INC, './lib');

use Lyndbaum::Core;

my %e = Lyndbaum::Core::getEnv();

print "Status: 200\r\n";
print "Server: Lyndbaum\r\n";
print "Content-Type: text/html; charset=UTF-8\r\n\r\n";

if ($e{REQUEST_URI} eq '/hoge') {
    # 文字列比較はeqなので注意、シェルスクリプトと同じ!!
    print "hoge!!";
} else {
    print "REQUEST_SCHEME: $e{REQUEST_SCHEME}<br>";
    print "HTTPS: $e{HTTPS}<br>";
    print "HOST: $e{HOST}<br>";
    print "SERVER_NAME: $e{SERVER_NAME}<br>";
    print "REQUEST_URI: $e{REQUEST_URI}<br>";
    print "DOCUMENT_URI: $e{DOCUMENT_URI}<br>";
    print "SCRIPT_NAME: $e{SCRIPT_NAME}<br>";
}

あとがき

何故Perlかというところだが、単にCGIとして実装したかったからだ。PHPやRuby, Pythonなどの選択肢もあると思うが、私はPythonが好きではなく、Rubyには興味がなく、最近のPHPもあまり好きではない。そこでPerlにすることにした。

PerlはPHPと比べたときに破壊的変更が少なく、枯れており、構文を覚えればワンライナーや簡単なスクリプトを書くのも便利だ。他にも構文が柔軟な部分も素晴らしい。

例えばPHPであれば以下のようにエスケープが必要なコードは

$hoge = '"/hoge/\'fuga\'/piyo/"';
if (preg_match("/(\/'fuga'\/)/", $hoge, $mat) === 1) {
    echo $mat[1];
}

Perlではこう書ける。

$hoge = qq("/hoge/'fuga'/piyo/");
if ($hoge =~ m|(/'fuga'/)|) {
    print $1;
}

qq()のqはquoteを指し、quoteが二つあるのでダブルクォートを指し示す。この演算子を使うことでPerlでは文字のエスケープを減らすことができ、書きやすさと可読性を保ちやすくなる。qq<>とかqq{}みたいに任意の記号を使うことができるので、文字列の中に(出たら終わりじゃんみたいなこともない。大変柔軟性がある。

$hoge =~ m|(/'fuga'/)|=~は正規表現の比較に使う演算子だが、ついでにキャプチャもしてくれる。更にm||のように書くことで正規表現内に/が含まれていてもエスケープ不要になる。qq同様に使う記号は好きに変えられる。

反面でPerl特有の演算子でもあるため、Perlが分からない人には暗号にしか見えないのは欠点と言えるだろう。

またPerlにはNode.jsの面倒さのようなものもない。例えばNode.jsならパッケージマネージャーをnpm, yarn, pnpmなどのどれにするかとか、実行環境をDeno, Bun, Node.jsなどのどれにするかとか、バージョン管理をVolta, nodenv, nvmなどのどれにするかとか、様々な争いがあると思うが、Perlにはそんなものはないと思う。

それに、このブログで利用しているadiaryはPerl CGIで実装されているし、lycolia.info配下のページも実は全部Perl CGIとして実装している[1]。つまり最近私の中でPerlブームが訪れているわけだ。ソフトウェア配布に置いてるのも現状Perl実装だしね。


  1. しかも恐ろしいことにルーティングなしでページ単位にcgiファイルがあるぞ!

2026/07/10(金)nginxでディレクトリ単位にPHPの設定をする方法

投稿日:

Apacheでは.htaccessに以下の形式で書けばphp.iniの内容をサーバーの再起動なしに設定できるが、それと同等のことをnginxでもやる方法。

php_value key value

確認環境

Env Ver
PHP 8.3.31
PHP-FPM 8.3.31
nginx 1.24.0

やり方

  1. PHPの設定を変えたいフォルダに.user.iniというファイルを作成する
  2. php.iniと同じ形式で設定を書く
    例:
    upload_max_filesize = 20M
    post_max_size = 20M
    memory_limit = 256M
    display_errors = Off
    
  3. 該当のPHPスクリプトにアクセスすると反映されている

2026/06/16(火)WSL2の中からPowershellでOSへ通知トーストを投げる

投稿日:

PowerShellはWSL2の中から叩けるため、WSL2の中から通知を出したいときに使える。

PowerShellなのでホスト側のWindowsからも使える。

確認環境

PowerShell 7では動かないので注意。

Env Ver
OS Windows 11 Pro 25H2 (OSビルド 26200.8655)
PowerShell 5.1.26100.8655

やり方

  1. この内容をtoast.ps1など適当な名前で保存する。文字コードはUTF-8 BOM, 改行コードはLFにしておく

    param (
        [String]$subject = "WSL Notice",
        [String]$title   = "たいとるですー",
        [String]$message = "お知らせでーす",
        [String]$icon    = "C:/env/1e1b9fdf82a6a544.png",
        [switch]$keep
    )
    
    [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null
    $template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastImageAndText04)
    $xml = New-Object Windows.Data.Xml.Dom.XmlDocument
    
    $xml.LoadXml($template.GetXml())
    $toastTextElements = $xml.GetElementsByTagName("text")
    $toastTextElements.Item(0).AppendChild($xml.CreateTextNode($title)) > $null
    $toastTextElements.Item(1).AppendChild($xml.CreateTextNode($message)) > $null
    $toastImageElements = $xml.GetElementsByTagName("image")
    $toastImageElements.Item(0).SetAttribute("src", $icon) > $null
    
    if ($keep) {
        $xml.DocumentElement.SetAttribute("scenario", "reminder") > $null
        $actions = $xml.CreateElement("actions")
        $action = $xml.CreateElement("action")
        $action.SetAttribute("content", "閉じる") > $null
        $action.SetAttribute("arguments", "dismiss") > $null
        $action.SetAttribute("activationType", "background") > $null
        $actions.AppendChild($action) > $null
        $xml.DocumentElement.AppendChild($actions) > $null
    }
    
    $toast = [Windows.UI.Notifications.ToastNotification]::new($xml)
    $appId = $subject
    $notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($appId)
    
    $notifier.Show($toast)
    
  2. 次の書式で叩くと通知トーストが出る
    powershell.exe -File 'C:/path/to/toast.ps1' -subject hoge -title fuga -message piyo -icon C:/path/to/icon.png -keep
    

このスクリプトのオプション

オプション 役割
-subject 通知の表題。ここを変えると別の通知として扱われる
-title 通知のタイトル
-message メッセージ本文。一行のみ
-icon アイコン。Windowsホストの絶対パスで指定
-keep このフラグを立てると通知が消えなくなる

-subjectを変えると別の通知扱いになるため、通知を別々に貯めることができるようになる。

Windows側から叩く方法

powershell 'C:/path/to/toast.ps1' -subject hoge -title fuga -message piyo -icon C:/path/to/icon.png -keep

Windows側から叩く場合、-Fileは省略できる。

参考情報

あとがき

Claude Codeの確認と終了のイベントで通知が出ると便利かなと思って作ってみたが、まだ試せていない。

取りあえずWSL2の中と、Windowsホスト側から叩いたときに通知が出ることは確認している。

PowerShell 7で動かそうと思ったらエラーまみれで動かず、PowerShell 7の場合、NuGetパッケージを取得するか、DLLのパスを直に指定するか、サードパーティの通知ライブラリを使うかということで、諦めた。

2026/05/15(金)Windows 11でPowerShellを使って画面をオフにする方法

投稿日:

Win32APIをPowerShellから叩くことで実現できる。

確認環境

  • Windows 11 25H2 (OSビルド 26200.8457)

コード

(Add-Type -MemberDefinition @"
[DllImport("user32.dll")]
public static extern int SendMessage(int hWnd, int hMsg, int wParam, int lParam);
"@ -Name "Win32API" -PassThru)::SendMessage(0xFFFF, 0x0112, 0xF170, 2)

パラメーターを解説すると0xFFFFHWND_BROADCAST0x0112WM_SYSCOMMAND0xF170SC_MONITORPOWERに相当し、2はモニタ電源を落とす固定値である。

つまり内容としては第一引数のパラメーターがHWND_BROADCASTである場合、最上位のウィンドウにWM_SYSCOMMAND指令が出され、そのパラメーターはSC_MONITORPOWER2であるということで、つまりモニタ電源をオフにするシステムコマンドを最上位のウィンドウに送っている内容になっている。

-Name "Win32API" -PassThruを外したくなるが、外すとエラーで動かなくなるので付ける必要がある。

-PassThruがあることで戻り値が生まれ、-Name "Win32API"をつけることでクラスが生まれるらしい。

参考情報

  • SendMessage function (winuser.h) - Win32 apps | Microsoft Learn
    • > HWND_BROADCAST ((HWND)0xffff)
    • LRESULT SendMessage(
      [in] HWND hWnd,
      [in] UINT Msg,
      [in] WPARAM wParam,
      [in] LPARAM lParam
      );
  • WM_SYSCOMMAND message (Winuser.h) - Win32 apps | Microsoft Learn
    • > #define WM_SYSCOMMAND 0x0112
    • > SC_MONITORPOWER 0xF170
  • GET MONITOR STATE POWER ON OR OFF | Microsoft Learn
    • •-1 (the display is powering on)
      • 2 (the display is being shut off)
  • Add-Type (Microsoft.PowerShell.Utility) - PowerShell | Microsoft Learn
    • -Name

      作成するクラスの名前を指定します。 このパラメーターは、メンバー定義から型を生成するときに必要です。

      型名と名前空間は、セッション内で一意である必要があります。 型をアンロードしたり、変更したりすることはできません。 型のコードを変更するには、名前を変更するか、新しい PowerShell セッションを開始する必要があります。 それ以外の場合、コマンドは失敗します。

    • -PassThru
      追加された型を表す System.Runtime オブジェクトを返します。 既定では、このコマンドレットは出力を生成しません。 OUTPUTAssembly 使用して DLL ファイルを作成し、新しく作成したアセンブリから型を返す場合は、このパラメーターを使用します。

あとがき

これは私物PCと業務PCでモニタを共有しており、排他制御で利用してる時に便利な技だ。

例えばリモートワークで昼休みに私物PCにスイッチする時で、業務PCをスリープや休止にするとWSLの中身が死んだりして面倒だが、画面オフならそういうことが起きないので便利に切り替えできるという訳。

2026/03/05(木)HTMLの標準機能でカラーパレットを使ったカラーコード入力UIを作る方法

投稿日:

なんかこんな感じのやつの作り方。

内容

左側にあるカラーパレットを出すボタンはinput type="color"で実現できるので、単純なカラー入力だけならこれでも構わない。現時点ではAndroid版Firefoxを除き、主要ブラウザは

ただ、コードも欲しいことが往々だと思うので、その場合は横にinput type="text"を生やして、次に示すサンプルコードのように双方の入力値をバインディングしてやればよい。

サンプルコード

動きはデモページで確認できる。

<input id="palette" type="color">
<input id="code" type="text">
<script>
  const getInput = () => {
    return {
      palette: document.getElementById('palette'),
      code: document.getElementById('code')
    };
  };

  const registerHandlers = (paletteEl, codeEl) => {
    paletteEl.addEventListener('input', (e) => {
      codeEl.value = e.target.value;
    });
    codeEl.addEventListener('input', (e) => {
      paletteEl.value = e.target.value;
    });
  };

  window.addEventListener('DOMContentLoaded', () => {
    const input = getInput();
    registerHandlers(input.palette, input.code);
  });
</script>

Safariでも表示できるが見た目がだいぶ異なる

PC Safari 18.2とiOS 26.3のSafariで確認した見た目。

一行目が初期表示、二行目がカラーパレット展開表示。iOS Safariの方は素の状態だとUIが小さすぎて見えないので、かなり拡大して表示している。

PC Safari iOS Safari
PC Safari初期表示
iOS Safari初期表示
PC Safariカラーパレット展開表示
iOS Safariカラーパレット展開表示