Using Python for Scripting

2025/12/08 17:52

Using Python for Scripting

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

要約

この記事では、

readlink
find
xargs
date
sed
などの GNU 固有ツールに依存するシェルスクリプトが、BSD バリアントでオプションが欠落または変更されているため macOS 上でしばしば動作しなくなる理由を説明しています。例として Bash スクリプトを挙げ、macOS で実行すると典型的なビルドヘルパーが失敗する様子を示し、著者はクロスプラットフォームの信頼性を確保するために Python への移行を推奨しています。

Python 3 はほぼすべての現代マシンに標準で搭載されており、CPython の標準ライブラリ(JSON、XML、HTTP クライアント、時間型、データ構造、sqlite3 など)がビルド自動化タスクに必要な機能をすべて備えています。記事では PEP 387 が五年間の後方互換性を保証していると指摘し、今日書かれたコードは将来もそのまま実行できることを示しています。また

warnings.simplefilter("default", DeprecationWarning)
を使って
datetime.utcnow()
から
datetime.now(timezone.utc)
への置き換えなどの非推奨警告を表示する方法も紹介しています。

投稿は、配列構文(

@
)の欠如、誤った引用符、
${s^^}
のようなケース変換といった Bash 特有のクォリティをハイライトし、Python の読みやすいメソッド(例:
s.upper()
、リスト内包表記)がこれらの落とし穴を回避できることを説明しています。10〜20 行程度の小さなスクリプトは Bash で残せますが、大規模または理解しにくいスクリプトは Python に変換すると、チーム全体で一貫したプラットフォーム非依存コードになり、ビルドパイプライン上のバグを減らすことができます。

この記事はノルウェー語で書かれ、Kodemaker のブログに掲載されました。

本文

私たちはいつも問題を解決できる最もシンプルなツールを好みますが、長期的に見ると必ずしも最適とは限りません。さらに、複数のマシンで実行したい場合は特にそうです。

例えば、プロジェクトをビルドするためのシェルスクリプトがあるとします。
そのスクリプトを別のフォルダから呼び出せるようにしたいので、まず最初にやりたいことはプロジェクトのルートディレクトリを特定し、それを作業ディレクトリに設定することです。

SCRIPT_PATH="$(readlink -f "$0")"
PROJECT_ROOT="$(dirname "${SCRIPT_PATH}")"
cd "${PROJECT_ROOT}"

その後、ビルドアーティファクトが古くてキャッシュされているというバグがあるため、以前に作成したすべてのアーティファクトを削除したいと考えます。これで、前回のビルドから残った古いデータを残さないようにできます。

find build gen -type f \( -name '*.o' -o -name '*.a' \) -print0 | xargs -0 -r rm

もちろん、リリース用のビルド署名を作成するために、ビルド日付と git‑commit をバージョンファイルに書き込んでいます。

BUILD =  ……

We must translate the entire article.私たちはいつも「問題を解決できる最もシンプルなツール」を選びがちですが、長期的には必ずしも最適とは限りません。さらに、複数のマシンで実行したい場合はその傾向がより顕著になります。

たとえば、プロジェクトをビルドするためのシェルスクリプトがあるとします。  
別のフォルダからそのスクリプトを呼び出せるようにしたいので、最初にやりたいことは「プロジェクトのルートディレクトリを探して作業ディレクトリに移動する」ことです。

```sh
SCRIPT_PATH="$(readlink -f "$0")"
PROJECT_ROOT="$(dirname "${SCRIPT_PATH}")"
cd "${PROJECT_ROOT}"

その後、ビルドアーティファクトが古くてキャッシュされているというバグを修正したいので、以前に作ったすべてのアーティファクトを削除します。これで前回のビルドから残っていた古いデータを残さないようにできます。

find build gen -type f \( -name '*.o' -o -name '*.a' \) -print0 | xargs -0 -r rm

もちろん、リリース用にビルド署名を作るために、ビルド日付と git‑commit をバージョンファイルに書き込みます。

BUILD_DATE="$(date -d 'now' +%F)"
cp version.template build/version.txt
sed -i "s/@VERSION@/${COMMIT_TAG:-dev}/" build/version.txt
sed -i "s/@BUILD_DATE@/${BUILD_DATE}/" build/version.txt

最後に、プロジェクトが依存しているビルドコマンドを実行してプロジェクトをビルドします。

ここにはいくつか問題があります。私にとってはこのスクリプトはうまく動きますが、Mac ユーザーにとってはすべての手順が失敗します!

readlink
find
xargs
date
sed
などの呼び出しは、これらのプログラムの GNU(Linux)版に依存しており、BSD(Mac)のバージョンでは存在しないか同じように動作しません。同様に、Mac 用で Linux 用で動く引数があるものもあります。

こうした問題を回避する方法はいくつかありますが、いずれも洗練された解決策とは言えません。しかも、Mac でも Linux でもどちらかにしか慣れていないと、ツールが別の OS で動かないことに驚きます。これは、CI マシン上で実行されるスクリプトを修正したい Mac ユーザーにとっては特に苛立たしいでしょうし、新規プロジェクトに取り掛かった瞬間に「Linux で動くように開発スクリプトを直さないと」なるのは楽しくありません。

Python が救世主になる

タイトルが示す通り、Python 3(以下、Python)は複雑なシェルスクリプトの優れた代替手段です。理由は次の 4 点に集約されます。

  1. ほぼすべてのマシンに Python がインストール済み
  2. 多くの開発者が Python に慣れている(ある程度)
  3. Python は大規模で標準化された標準ライブラリを持つ
  4. Python コードは後から読んだときに読みやすい

既知・既装備

私の主眼として最も重要なのは、Python 3 がほぼすべてのマシンにインストールされている点です。

さらに、私たちは「Python を知っている」― 大規模プロジェクトで使ったことがなくても、一度は何かしら Python っぽいものを触った経験があります。これだけでも導入へのハードルが大幅に下がります。

標準化された標準ライブラリ

標準ライブラリに沿って書けば、スクリプトを実行するすべてのマシンで同じ動作になります。参照実装である CPython は、開発者の 99.8 % がインストールして使っています。残りの 0.2 % は別実装(Jython, IronPython など)を使用していますが、私が知る限りすべての実装は参照実装と意味的に同等になるよう努力しています。

注意すべきなのは「どのバージョンの Python を前提にしているか」だけです。実際には、新しいパッケージほど問題が起こりやすいですが、古い Python で動くスクリプトなら大抵は問題ありません。
Python は後方互換性を真剣に扱っています ― すべての大規模言語と同様です。PEP 387 は破壊的変更が導入される際の指針を示しており、頻繁に起こることはほぼありません。もし何かが非推奨になった場合でも、コードは少なくとも 5 年間はそのまま動き続けます。

たとえば

datetime.utcnow()
は 3.12 で非推奨となりました。
DeprecationWarning
を有効にすると「
datetime.now(timezone.utc)
に置き換えるべき」と警告が出てきます。これは標準ライブラリから消える前に soft‑deprecation(軽いデプリケーション)であることを意味します。メンテナーが削除するなら、PEP 387 は「最短 2028 年頃」になると示しています。

大規模な標準ライブラリ

標準ライブラリが存在し、消える心配もない点が Python を選ぶ最大の理由です。上記で述べたように、これらは すべて 事前に決められた構成要素で、同じ作業をシェルではなく Python で行うことができます。

JSON → JSON‐?‑…(何でも―)
他の JSON‑ …?
…
「… …

(???)


## 思考ツールとして

We must finish translation of remaining text.```text

JSON を扱う? それとも XML?


というように、標準ライブラリだけで何でも出来ます。  
HTTP クライアントが必要なら組み込みの `http.client` や `urllib.request` が用意されています。  
時間型やデータ構造も充実しており、必要なら `sqlite3` パッケージまで利用できます。

「プロトコル生成ツール(`protoc` など)やオープンAPIのクライアント・コンパイラ」を除けば、シェルスクリプトで簡単にできないことはほぼありません。  
Python なら同じ作業を容易に行えます。

### 読みやすさ

「言語 X は Y より読みやすい」と主張する人は多いですが、両者のセマンティクスが似ている場合は特にそうではありません。こうした議論はしばしば「自分がよく使う方を好む」だけで済むケースです。

しかし Python がシェルより読みやすいと主張する根拠には、以下のような点があります。まず第一に、頻繁に使わない言語だと、データ型や文字列操作が頭に残りにくいです。たとえば Bash でリストを大文字化するときの書き方は次の通りです。

```bash
morning_greetings=('hi' 'hello' 'good morning')
energetic_morning_greetings=()

for s in "${morning_greetings[@]}"; do
  energetic_morning_greetings+=( "${s^^}!" )
done

Bash を頻繁に使わない人なら、

"${morning_greetings[@]}"
が何を意味するのかすぐに分からず、書くときに正しい構文を忘れやすいです。
[ @ ]
を抜けてしまうと最初の要素だけが返ってきますし、ダブルクォートを外すと「good morning」が 2 要素に分割されます。そして
${s^^}
が実際に何をしているかも分かりづらいです。

ちなみにリスト内でカンマを付けてしまうと

morning_greetings=('hi', 'hello', 'good morning')

というリストになり、

hi,
hello,
good morning
という要素が作られます。

さらに Zsh では動きません。Zsh を使っている人は端末愛好家かもしれませんし、その場合でも自分の好きなシェルでこの書式を使えないと、スクリプトに組み込む可能性が低くなるでしょう。

上記のように、Bash を頻繁に使わないと多くのミスを犯すことになります。

Python 版は次のようになります。

morning_greetings = ['hi', 'hello', 'good morning']
energetic_morning_greetings = [s.upper() + '!' for s in morning_greetings]

最初に見るとリスト内包表記

[x for y in z]
がすぐに理解できるとは限りませんが、使い方を覚えてしまえば
s.upper()
のほうが
${s^^}
より直感的です。

また Python にはより豊富な語彙があります。したがって「

morning_greetings.pop(1)
」のように要素を削除する操作は、最初に見るとわかりづらいですが、後で見たときに「インデックス 1 の要素を取り除く」という意味になるので理解しやすいです。対して Bash には同等のメソッドが無いため、次のような手作業が必要になります。

unset 'morning_greetings[1]'
arr=( "${morning_greetings[@]}" )

これらは分かりにくく、さらにドキュメントも参照できません。

「子を洗い流さないでおく」ことの危険性

スクリプトがある程度大きくなると、Python を使う理由が増えてきます。逆に Bash で書いていると、次に何をすべきかが分からなくなります。
そのため、既に Bash で書かれている場合でも、リライトする余裕がないときは「先ほどの…」という形でいろいろ検討しながりつくらずに作業してしまいます。

シェルでは全体を見えやすくすることが難しいので、実際には スクリプトに最初から前置付けがなくても綺麗な面目が形づくる という点でも長い労働作業が落ち込んです。

「全ての自動化で‐自動化…」
と…

(ここでは抜粋させてしまいます)


## Pythone ?‑

先ほども説明したように、Python ― 前後‐全般‐実際に実装にくい ……

何味? 先行‐点‐下

We need finish translation.## 子どもを水に流さないで

スクリプトがある程度大きくなると、Python を使うべき理由が増えていきます。逆に Bash で書いていると、次に何をすべきかが分からなくなることがあります。
そのため、既に Bash で書かれているスクリプトでも、リライトの余裕がない場合は「先ほどの…」という形で途中まで進めてしまい、結局は手作業で修正する羽目になります。

シェルでは全体を把握しづらく、実際には 「最初から前置付けをせずにスクリプトを書いても見通しが良くなる」 という点でも長い労力がかかります。

「すべての自動化は…」という考え方は…

(ここでは抜粋させていただきます)


Python が選ばれる理由

先ほど述べたように、Python は既知・標準化・大規模であるため、スクリプトを書き直す価値があります。特に次の点が重要です。

  • 移植性:Linux だけでなく macOS でも同じコードで動作します。
  • 保守性:バージョンアップや非推奨警告を一目で確認でき、長期的に安全に使えます。
  • 機能の充実:JSON・XML・HTTP クライアント・タイムゾーン・データ構造など、標準ライブラリだけでほぼ全てが実装済みです。
  • 読みやすさ:Python のメソッド名は人間にとって直感的で、複雑な Bash スクリプトよりもエラーを起こしにくいです。

まとめ

  • シェルスクリプトは短くても可読性が低いことが多い
  • Python はほぼすべてのマシンで動作し、標準ライブラリだけで完結できる
  • 長期的に見たときの保守コストが低減される

したがって、スクリプトを 10〜20 行程度から始めても、将来拡張する可能性があるなら Python に移行しておく価値があります。
新しいプロジェクトに取り掛かったときに「Linux 用に開発スクリプトを直さなければならない」ことは楽ではありません。Python を使えば、その負担を大幅に軽減できます。

この記事はオリジナルでノルウェー語で書かれ、Kodemaker のブログで公開されています。
(英訳版を読む場合は下記リンクをご参照ください)


同じ日のほかのニュース

一覧に戻る →

2025/12/14 7:58

Linux Sandboxes and Fil-C

## Japanese Translation: メモリ安全性とサンドボックスはプログラムの異なる部分を保護するため、両方が強力なセキュリティに必要です。純粋な Java プログラムはメモリ安全であってもファイルシステムの syscalls を通じて任意のファイルを書き込むことができるし、逆にすべての能力を取り消したアセンブリプログラムでもメモリバグがある場合がありますが、カーネルが特権 syscalls を殺すためサンドボックスから逃げられません。サンドボックスは意図的に許容範囲を広く設計しているため、攻撃者は残されたメモリ安全性のバグを利用してブローカー・プロセスへ到達することができるので、両方の防御を組み合わせるとより強固な保護が得られます。 本書では、C/C++ 用に設計され、システムコールまで安全性を保証し、init や udevd などの低レベルコンポーネントで使用できるメモリ安全ランタイム「Fil‑C」への OpenSSH の seccomp ベース Linux サンドボックス移植方法について説明します。OpenSSH は既に chroot を採用し、`sshd` ユーザー/グループとして特権なしで実行し、`setrlimit` を使用し、非許可 syscalls を `SECCOMP_RET_KILL_PROCESS` で殺す seccomp‑BPF フィルタを適用しています。Fil‑C はその runtime 内で自動的にこれらの syscalls を許可することで簡素化します。背景スレッドは存続させつつスレッド生成を防ぐため、Fil‑C は API `void zlock_runtime_threads(void)` を追加し、必要なスレッドを事前確保してシャットダウンを無効にします。 OpenSSH の seccomp フィルタは強化されています。失敗時の挙動が `SECCOMP_RET_KILL` から `SECCOMP_RET_KILL_PROCESS` に変更され、mmap 許可リストに新たに `MAP_NORESERVE` フラグが追加され、`sched_yield` が許可されています。サンドボックスは二つの `prctl` コール(`PR_SET_NO_NEW_PRIVS` と `PR_SET_SECCOMP`)で構築され、エラー検出も行われます。Fil‑C のランタイムは `filc_runtime_threads_handshake` で全スレッドとハンドシェイクし、各スレッドが no_new_privs ビットと seccomp フィルタを持つことを保証します。複数のユーザー スレッドが検出された場合、安全エラーが発生します。 メモリ安全性とサンドボックスを組み合わせることで、OpenSSH はより厳格な隔離を実現し、メモリバグによる権限昇格リスクを低減します。このアプローチは他のセキュリティクリティカルプロジェクトにも採用を促す可能性があります。

2025/12/14 9:34

An Implementation of J

## Japanese Translation: ## 改訂版要約 本書は、技術仕様の構造化された目次であり、以下のように整理されています。 1. **第0章 – はじめに** 2. **第1章 – 文を解釈する** - 1.1 単語生成 - 1.2 構文解析 - 1.3 トレイン(列車) - 1.4 名前解決 3. **第2章 – 名詞** - 2.1 配列 - 2.2 型 - 2.3 メモリ管理 - 2.4 グローバル変数 4. **第3章 – 動詞** - 3.1 動詞の構造 - 3.2 ランク - 3.3 原子(スカラー)動詞 - 3.4 オブヴァース、同一性、および変種 - 3.5 エラー処理 5. **第4章 – 副詞と接続詞** 6. **第5章 – 表現** - 5.1 原子表現 - 5.2 ボックス化された表現 - 5.3 木構造表現 - 5.4 線形表現 7. **第6章 – ディスプレイ** - 6.1 数値表示 - 6.2 ボックス化表示 - 6.3 フォーマット済み表示 主要セクションの後に、付録A〜F(インキュナブルム、スペシャルコード、テストスクリプト、プログラムファイル、外国接続詞、およびシステム概要)が補足資料として提供されます。書末には参考文献・用語集・索引が付されています。 この構成(目次 → 詳細セクション → 付録 → 参照資料)は、読者に全体枠組みを最初に把握させたうえで、必要に応じて詳細へ掘り下げたり補足資料を参照したりできる明確かつ階層的な道筋を提供します。

2025/12/14 8:39

Closures as Win32 Window Procedures

## Japanese Translation: **改訂版要約:** この記事では、Win32 のウィンドウプロシージャに追加のコンテキストポインタを渡す方法を示しています。これは、WndProc が通常 4 つしか引数を取らないため、ネイティブ API には備わっていない機能です。著者は x64 アセンブラで小さなトランスペイル(trampoline)を作成し、実行時に JIT コンパイルして 5 番目の引数スロットを挿入し、呼び出し前に必要なコンテキストを格納します。これにより、各ウィンドウがグローバル変数や `GWLP_USERDATA` を使わずに独自の状態を保持できるようになります。トランスペイルは GNU アセンブラで書かれ、`.exebuf` セクション(`bwx` フラグ付き)から 2 MiB の実行可能バッファが確保されます。C ヘルパー関数 `make_wndproc(Arena *, Wndproc5, void *arg)` は 2 つのバイトオフセットプレースホルダーを修正してトランスペイルを生成します。作成後は `set_wndproc_arg(WNDPROC p, void *arg)` を使ってコンテキストを変更できます。アロケータ例では、異なる状態オブジェクト用に複数のトランスペイルを生成したり、動的に切り替えたりする方法を示しています。この手法は、トランスペイルがアンウインドテーブルを持たないため Windows Control Flow Guard 下でも安全に機能し、グローバル変数を使わずにウィンドウごとのデータを付与する低レベルの手段を示しています。