Re: シェルスクリプトを書くのをやめる

この記事はシェルスクリプトを書くのをやめる - blog.8-p.infoの個人的感想である。

なお元の記事を書いた人と私は全くの赤の他人であり、何の接点もない。つまりこの記事は単なる外野のたわ言である旨をご承知いただきたく。

Re: シェルは悪いプログラミング言語である

シェルスクリプトの文法やら何やらが、最近のモダンなプログラミング言語と比較すれば色々とアレなのは確かだ。

しかし冷静に考えてほしいのだが、現在使われている/bin/shのもとになったBourne Shellは1977年生まれで、44年前の代物だ。それぐらい大昔の、まだ言語設計もシステム開発技法も未成熟だった時代に、プログラミング言語ではなく「プログラマブルな対話型コマンドラインインタプリタ」の一種として開発された代物だということは押さえておくべきだろう。

コンパイラ本になるが『コンパイラ―原理・技法・ツール〈1〉 (Information & Computing)』の原書が出版されたのは1986年だ。プログラミング技法に関しては、『ソフトウェア作法』でさえも原書が出版されたのは1976年だ)

言語設計が未成熟だった故に言語文法とか色々とアレだし、未成熟だったプログラミング技法のあおりで微妙な振る舞いをするようになってしまった部分がPOSIXやら何やらで「仕様」と化してしまった点もあるかもしれない。知らんけど。

そもそもUnixシェルはプログラミング言語の処理系ではないプログラミング言語ではないのだから、REPLとは言えない。プログラミング言語と「対話型コマンドラインインタプリタ」の間には「野球とクリケット」ぐらいの違いがあって、「プログラマブルな対話型コマンドラインインタプリタ」であるUnixシェルは野球に近づいているものの、依然として「野球とソフトボール」ぐらいの違いがある。だから、プログラミング言語の視点では微妙だと感じられる妥協点がある文法となっている。

プログラミング言語ではないものをプログラミング言語として認識してしまう我々プログラマの業が、思わぬ認識の齟齬に繋がっている気がしないでもない。

その上で、良いか悪いか判断に迷うところだが、40年前にBourne Shell向けに書かれたシェルスクリプトは(使用しているコマンドの有無や、コマンドの仕様の変化などで躓かなければ)今でも動作する。例えば原書が1984年に出版された『UNIXプログラミング環境 (海外ブックス)』の第3章~第5章が、2021年になってもシェルプログラミングの入門文書としてほぼ修正なしで通用するのである。

見方を変えれば、シェルスクリプトのコアな部分は40年以上前から何も変わっていない、ということだ。

適切な例えではないかもしれないが、ある意味でシェルプログラミングには「21世紀のプログラマから見た『FORTRAN 77でプログラミング』や『COBOL-80でプログラミング』の世界」みたいな世代差による断絶があるのではないかと思う。知らんけど。*1

Re: シェルはあまりパワフルではないので、結果として複数の言語を混ぜることになる

……まあ、シェルスクリプトはグルー言語の王様だからなあ。

CLIの対話型コマンドラインインタプリタでは、どうしてもコマンドが主役となる。Unixシェルがそれ以前のコマンドラインインタプリタよりも優れていた点の1つは、パイプのような「コマンドとコマンドを容易に組み合わせられる機能」が組み込まれたことだ。

シェルは、例えるなら現場監督にすぎない。職人たち(各種コマンド群)を連れてくる(インストールする)か、もしくは職人を養成(コマンドを自作)したうえで、彼らに作業を割り振って工事を進める(コマンドを組み合わせて処理をする)のが、もっとも効率のよいシェルの使い方である。

言い換えれば、シェル自身に職人の役割が求められるようになった時点で非効率なのだ。他の、もうちょっと本格的なプログラミング言語に切り替えた方がよい。

Unixシェル環境に慣れている」という前提の下での話となるが、個人的な体感としては、単機能のプログラムなら小さなシェルスクリプトで実装してしまうのが手っ取り早い。シェル上でのワンライナーも含めて、小さなシェルスクリプトで済む範囲であるならば、シェルは現場監督として大活躍する。しかし少し複雑なプログラムをシェルスクリプトで実装しようとすると、シェルに配列のような「現場監督以外の役割」が欲しくなってくる――その時点で他のプログラミング言語を検討するべきだろう。

Re: シェルが存在しない環境のことを考えると、書いたものも、そこまでポータブルではない

そもそもシェルが存在する環境であっても、シェルスクリプトは移植性の問題を抱えやすい。この点はEric S. Raymondも『The Art of UNIX Programming』で書いている。以下、同書のP.365より:

複雑なシェルスクリプトは、移植性問題を抱えていることが多い。それはシェル自身に問題があるというよりも、コンポーネントとして他のプログラムが存在することが前提となっていることに起因するものだ。Bourne ShellKorn Shellのクローンは、Unix以外のオペレーティングシステムにも散発的に移植されているが、シェルプログラムは(現実的にいって)Unix以外のオペレーティングシステムには移植性がない。

以上をまとめると、シェルの長所は小さなスクリプトを書くためには非常に自然で手っ取り早いことだ。欠点は、特に大きなシェルスクリプトでは、すべてのターゲットマシンで同じように動作するとは限らず、ない場合もあるようなコマンドに大きく依存してしまうことだ。大規模なシェルスクリプトでは、依存関係を分析するのも容易ではない。

個人的には、シェルスクリプトの移植性は「C言語の移植性」と同程度に疑わしいと思っている。教科書の類には「C言語は移植性が高い」と書かれているけど、現実として、移植性の高いC言語ソースコードを書くためにプログラマは色々と苦労している訳で――Unix環境間の移植性であっても、それなりの苦労をしないとシェルスクリプトの移植性は得られないと考えるべきだろう。

先生誰にも言わないから、Ubuntu上で使っていたシェルスクリプトmacOSでうまく動かなかった子は手を上げなさい――先生は動かなかったことがあります。

Re: じゃあ代わりに何を使うの?

これな、これなあ、物凄く悩ましいの。

正直なところ、周囲の開発者の同質性が高めなら、そこまで悩まなくてもよいと思うの。だってみんなが慣れている言語から検討を始めればよいし、仮にちょうど良い言語が無かったとしても、モダンでメジャーでそれっぽい言語を採用して周囲に広めるのにあまり苦労しないはずだから。

でも皆が割と違うことをやっていて、作業環境もバラバラだと、使用する言語を統一しようという試みは概ね失敗する。どの言語を選んでも角が立つものだ。

個人的には、最近は色々と割り切って「プラットフォームごとに違う言語を使う」というようにしている。Windows向けの小ツールはPowerShell 5.1で実装する*2macOSなら頑張って「標準のコマンドを使用する前提のシェルスクリプト」を書くか、思い切ってSwiftでスクリプトを書いてしまうこともある*3

――Linux向けのツール? macOS以外のUnixユーザランド環境をまともに触っているの、自分だけだから……好き勝手にやらせてもらってる。小さなツールならシェルスクリプトで書いてしまうことが多いし、ちょっと複雑なツールは「スクリプト処理系が存在するプログラミング言語」の中からその時々の気分で選んで実装する。でもバイナリデータの処理を書くときはC言語C++を併用しがちだ――生のバイナリデータを直接操作することに慣れてしまった弊害だよなぁ。

なお状況次第ではコンパイル型言語を採用することもある。こういう時は、大抵は「各々のマシンにスクリプト処理系をインストールしたくない」という要求があるので、その影響で必要なライブラリ類を全て静的リンクしたFatな実行ファイルを配布することが多い――要するに「各々のマシンに追加の共有ライブラリ類をインストールすることも避けたい」ということだ。

Re: ShellCheck 使えばいいのでは?

よくある「シェルスクリプトの代替」はPerl/Python/Rubyあたりだと思うのだが、動的型付けのスクリプト言語を本格的に使うときは「lintツールによる静的解析 + 単体テストツール」は必須だと思っている。コンパイル型言語におけるコンパイル・チェックという名の静的テストに相当するテストを、lintによる静的テストと単体テストによる動的テストで補完するのが目的だ。

シェルスクリプトを「動的型付けのスクリプト言語」に含めるのは何か違うと思うのだが、しかし、コンパイル・チェックがなくて実行時でないと洗い出せないエラーが多々あるという点では、シェルスクリプトPerl/Python/Rubyと同じだ。だからシェルスクリプトへの「lintによる静的テスト」は効果的だと考えている。

――なんだけど、シェルスクリプトを書くときにShellCheckを使うのは、何というか大袈裟すぎる気がするというか、むしろ「ShellCheckが必要な規模のシェルスクリプト」を書くこと自体がアカンのではないか、というもにょもにょしたものを感じてしまう。

あれだな、「シェルスクリプトはシンプルで単純なもの」という思い込みがあるのだ。

Re: Python とか Rubyシェルスクリプトみたいなことするの面倒くさくないですか?

ひんしゅくを買う発言だと思うけど、そもそもPythonRubyで「シェルスクリプトみたいなこと」をする必要があるのだろうか……?

実現したい内容によるのだろうけど、私ならまず最初に「PythonRubyでコマンドを書いて、シェルスクリプトでラッピングしてコマンド同士を組み合わせる」というアプローチを検討すると思う。

……あれ? 結局シェルスクリプト自体は書いているような……しかも追加の依存関係が発生したぞ?

ま、まあ、アレだ、シェルが得意とする処理はシェルスクリプトで行えばよいし、シェルが不得意な処理は他のプログラミング言語で行えばよい、という考え方だ。そのアプローチの1つとして、私は「他のプログラミング言語でコマンドを実装して、シェルスクリプトで連結する」というシェルスクリプト・ファーストな方法を採用することが多い。

その結果として、結局はシェルスクリプトを書くことになるのだが、シェルスクリプト自体は小さくシンプルなコードで済む。まあ、単なる分割統治ですな、複数の言語を使っているだけの。

Re: Python とか Ruby で書いたときに、依存パッケージとかどうしてますか?

標準ライブラリ縛りは割と鉄板ですな。

もう少し真面目に書くと、例えばチーム開発をしていて、みんなPythonRubyを使っていて、自分と周囲の開発者の開発環境の同質性が高い――とかならpipやgemを使う前提でも問題ないと思うの。類似案件として「macOSでHomebrew」みたいなケースもそう。

でも世の中そんな環境ばかりじゃない訳で、開発環境の同質性が低い環境にて「他人も使うツール」を書くとなると、スクリプト言語なら標準ライブラリ縛りにせざるを得ないことが多い。

例えばscipy必須のPythonスクリプトを社内に配布すると、絶対に何人かはscipyの導入やらpipenvの問題やら「そもそも吾輩は中身を理解しないまま手順書通りにvirtualenvを使っているでござる」やらでトラブルを報告してくる。実装に要するコスト次第だが、配布後のユーザ・サポートのコストを抑える目的で、あえてサードパーティのライブラリを使わないケースは少なくない。

別のアプローチとして、コンパイル型言語を使って、必要なライブラリ類を全て静的リンクした実行ファイルを配布することもある。

人月の神話【新装版】』の第1章「タールの沼」に曰く、単なるプログラムをプログラミング製品に磨き上げるには最低3倍のコストがかかる。自分が使うだけのツールと「他人も使うツール」とでも、やはり相応のコスト差があると考えた方がよいだろう。

おわりに:2021年にシェルスクリプトについて学ぶべきか否か?

そもそもUnixシェルが本質的に「Unixマシンを操作するための対話型コマンドラインインタプリタ」である以上、まず肝となるのは「Unixマシン上でシェルというCLIを多用するか否か」という点だろう。

というのも「Unixシェルでの操作に慣れて、パイプなどでコマンドを組み合わせることを覚えたユーザが、『コマンドの組み合わせ』を再利用するためにスクリプト化する」というのが、伝統的な「シェルスクリプトに手を出す契機」の1つだと思うからだ。

もしくは「Unixシェルでの操作に慣れて、既存のコマンドに不満を感じたユーザが、そのコマンドを拡張するためにスクリプトでラッピングする」というケースもあるだろう。というか『UNIXプログラミング環境』の第5章の導入部分がもろにそれである。

つまりUnixシェルの住人ならば、住民歴が長くなるにつれてシェルスクリプトに手を出すようになるのは自然であるし、その恩恵を享受しやすいのだ。でもUnixシェルの住人になる気が無いのならば、シェルスクリプトに手を出しても得られる恩恵は少ない。

だから、コンテナではなくUnixマシンそのもの(VirtualBox上の仮想マシンを含む)をCLIで管理したいとか、Unixシェル上の柔軟で効率的な「テキストファイル処理環境」で日常の作業を行いたいとか、そういう動機があるならば、2021年の今でもシェルスクリプトについて学ぶ意味はあるだろう(ただしUnixシェルにある程度親しんだ後に、だが)。

でも主眼が「ツールを自作する」というプログラミングにあるのならば、プログラミング環境としてUnixシェルを選択する意義は今でもあるかもしれないが*4、言語処理系としてUnixシェルを選択する意義は薄いだろう――もちろん作ろうとしているツールに課せられた制約次第だが。

*1:FORTRAN 77やCOBOL-80でのプログラミング経験が無いので、本当のところは分からない。

*2:PowerShell 5.1なら大抵のWindows 10マシンで動作する。

*3:swift(1)の引数にSwiftで書いたソースファイルを指定することで、ビルドしないで実行することができる。shebangにだって対応している。周囲のmacOSユーザはiOS/macOSアプリの開発者だけなので、大抵はXcodeがインストール済みであり、swift(1)を使用できる環境が整っている。

*4:とはいえGUIの便利なエディタを使ってしまうものである。

なぜGitは難しいのか(あるいは「難しいツール」だと感じるのか)?

ユーザとして2~3のバージョン管理システムを扱った後、4つ目に出会い今も使い続けているGitだが、使い始めてから7年以上経った今でも「難しいツールだな」という感想を抱く瞬間がある。

私は元々、集中型のバージョン管理システムを長らく使用してきた。Gitは初めて経験した(そして現時点では唯一経験したことのある)分散型バージョン管理システムだ。そういった個人的経緯より、Gitを難しく感じることがあるのは「古い経験則に囚われた、老害の個人的感想」ではないかと自分自身を疑ってきた。

疑ってきたのだが……新入社員にGitについて教える機会に直面するようになってから、どうもGitというツールには、今まで触ってきたバージョン管理システムにはないような「本質的に難しい何か」があるのではないかと感じるようになってきた。

というのも、例えば過去に使用した経験があるCVSSubversionと比較すると、Gitは明らかに「新入社員への教育コスト」が高いのである。教育に割かれる時間は増えていて、それでいて業務アサイン後の操作トラブル件数は微増している。

振り返れば、CVSSubversionを使用していた頃は、バージョン管理システムの扱い方について「独立した研修」のような機会は皆無だった。大抵はOJTの場にて先輩からちょっと説明を受けただけだったし、それだけでもしばらく使っていくうちに「大雑把な使い方の全体像」を理解することができたように思う。

一方で、Gitを使い始めた当初の感想は「どう使えば良いのか分からない」だった。個々の操作(fetchやpullやmergeやpushなど)が分からないのではなく、全体像がよく分からなかった。そのため、1つ1つの操作を実行する際に「全体的にみて、この操作は今ここで実行してよいのか?」という疑問を解消できず、迷いを抱えたまま恐る恐る触っていた。

7年以上使っていることもあり、普段使いの範囲では、迷いを抱えたまま操作することは無くなった。しかしそれでも、普段使いの範囲を逸脱した操作や、他の利用者のヘルプ・コールに応じて問題解決を試みる時などには、今でも迷いを抱くことがある。

Gitは15年以上も開発が続けられているツールではあるが、それでもどちらかと言えば後発のバージョン管理システムだ。しかしオープンソースの超有名どころバージョン管理システムを歴史順に「CVSSubversion・Git」と並べた時、Gitは最後発なのにも関わらず、使いこなせないというユーザは最も多いように見える。

より便利になるように後発のツールが作られたはず(仮説)なのに、使いこなせないユーザの割合が増えているように見える――というのは少々不思議な構図である。

ポリシーとメカニズムの分離と、メカニズムの制約に基づく「暗黙のポリシー」

Gitを難しいと感じる理由について考えたとき、UNIX思想における「ポリシーとメカニズムの分離」に突き当たるように思う。

これはエリック・S・レイモンドが『The Art of UNIX Programming』で述べた「ポリシーをメカニズムから分離せよ」という思想である。ポリシー(物事を行うときの原則)は時代と場所で変化する不安定なものであるから、ツールを作る際にはメカニズム(機能)だけ提供するようにしておいて、ユーザには自身のポリシーに沿った形でメカニズムを利用してもらう――という考え方だ。

実のところ、CVSSubversion・Gitのいずれも、基本的にはメカニズムのみを提供する。問題は、CVSSubversionはメカニズムの制約より「大まかな利用スタイル」が定まっているのにたいして、Gitは自由度が高い(メカニズムによる制約が緩い)、という点にある。

CVSSubversionには「集中型バージョン管理システム」という制約がある。この制約により、使い方のスタイルは概ね定まっている。メンバー全員がアクセスできるサーバ上にリポジトリを置き、各メンバーの端末にファイルをチェックアウトし、編集し、リポジトリにチェックインする。大抵はこのようなスタイルに固定される。

UNIX思想に基づいてメカニズムのみを提供しているにも関わらず、ツールの性質より、大まかな利用スタイルが決ってしまう。私はこれを「暗黙のポリシー」と呼んでいる。

つまりCVSSubversionを使う際には、誰しもが暗黙のポリシーに従っているし、ひとまず暗黙のポリシーに従っておけば概ね上手くいくのだ。

一方でGitは、使い方のスタイルが定まっているとは言い難い。リモートリポジトリを1つ利用する従来のSubversionっぽいスタイル、リモートリポジトリを持たないスタイル、複数のリモートリポジトリを参照するスタイルなど、様々な使い方ができるように設計・実装されている。だからGitでバージョン管理したいなら、まずは自分の環境に合ったスタイルを模索しなくてはならず、模索するためには「Gitで何ができるか?」ということを知る必要があり、Gitの可能性と限界を判断するためにもGitの全体像(表面的な使い方ではなく、操作の背景や内部メカニズム)に目を向けなくてはならない。

つまりGitには、CVSSubversionのような暗黙のポリシーが無いに等しい。

ポリシーがない状態でメカニズムを渡された時、メカニズムの制約の緩さ(≒ツールの自由度の高さ)は、時として凶器となる。明示的でも暗黙的でも、ポリシーさえあるなら、ひとまずそれに従ってツールを使えばよい。使っていくうちに、段々と分かってくるようになるものだ。しかしポリシーが無いと、自由過ぎて「そもそも、どう使えばよいのか?」と困惑してしまうものだ。

仮にポリシーを定めたとしても、今度は「ポリシーに沿うようにメカニズムを利用するには、どうすれば良いか?」という問題が生じる。うまい具合にポリシーに合わせるために、メカニズムそのものへの理解を深める必要がある。この点は「ツールの学習コストの高さ」として表面化する。

そして重要なことだが、「自身のポリシーに沿った形で『制約の緩いメカニズム』を利用する」ということは、「運用でカバー」と同義だ。「ユーザの使い込み度」という、ある種の技術力を前提としている訳だ。だから、ツール利用者の熟練度が低い間は、運用トラブルは必至だと言える。誰しも慣れないうちは、commit前にpullしたり、commit後のpushを忘れたりしてしまう――ということが起きる遠因だ。

熟練者の罠

ポリシーがない状態でメカニズムを渡すことには暴力性が伴うものだが、しかし一方で「使いこなすことさえできれば、利用者に大きな力を与える」という利点もある。

その分かりやすい例はUnix環境のCLIだ。自由度の高い、メカニズムのみを提供するツールであり、初心者がトラブルを引き起こす例は枚挙にいとまがない。それなのに、Unix環境は依然として一部のソフトウェア・エンジニアを惹きつけて止まない。なぜだろうか?

要は、自由度の高さは諸刃の剣なのだ。扱い方をしくじれば大怪我するが、うまくコントロールできれば強力な武器となる。ツールの自由度の高さは、現実の開発においてソフトウェア・エンジニアの武器となりうる(偶にしくじって怪我をするが)。だから今でもメカニズムのみを提供するツールが作られ続けているし、熱心なユーザはツール利用の熟練度を上げようと訓練し続けている。

Unix環境のCLI信者の大半は、「熟練度が高くなるにつれて、メカニズムを自在にコントロールできるようになり、飛躍的に生産性が向上する」という体験の虜になっているように思う。

Gitも同じで、使い慣れてくるとその便利さが分かるようになるし、熟練するにつれてGUIのツールだけでは物足りなくなってgit(1)コマンドを併用するようになってくる。

問題は、Gitのパワーを感じ取れるようになる前に、学習曲線が低い状態がしばらく続く、という点にある。Gitはバージョン管理システムという重要なツールではあるが、プログラミングそのものでない。あくまでも副次的なツールだ。本筋からやや外れたツールであるために、「習熟する」というレベルまで使いこもうとする人は少ない。そのため、大抵のGitユーザは学習曲線が低い期間が長いか、もしくは低い状態から抜け出すことがないだろう。

シミュレーション用のREPL環境としてPowerShellを使う

プログラマにとって、使い慣れたプログラミング言語のREPLは非常に便利なものだ。電卓代わりの計算機としても、コーディング時にちょっとした機能を探求的に実装していく実験環境としても、REPLは非常に重宝する。

ここ2~3年は、ちょっとしたシミュレーションをする際に、PowerShellのシェル環境をREPLとして使っている。シミュレーションと言っても、予め何らかのアルゴリズムを関数ないしクラス化しておいて、色々とデータを流し込んで結果の数値を眺めてアレコレ考える程度だ。グラフ化のような高度なことはしていない。やってもせいぜいOut-GridViewに流し込むぐらいだ。

普段はSchemeRubyのREPLを使うのだが、私の周囲にはSchemeRubyの利用者がいない。これらの言語でシミュレーション用の環境を構築しても、引き継ぎや業務委託(≒同僚に作業を横流し)する時に、REPLの環境構築から始めてもらわなくてはならない。

しかし幸いなことに、私の周囲はWindowsユーザばかりだ。PowerShell 5.1なら高確率で同僚のPCにも入っている。PowerShellを前提にシミュレーション環境を用意しておけば、同僚に引き渡す時にちょっとだけ手間が減ることになる。

REPLとしてのPowerShell

PowerShellは、その出自からコマンドプロンプトUnix環境のシェルと対比されがちだが、実のところ全くの別物である。どちらかといえば「手軽にシステム管理用APIや外部の実行ファイル類を叩くことができる、Microsoft独自のプログラミング言語」と考えた方がよい。その言語のREPLと簡易IDEWindowsに付属しているのである。

PowerShellのシェル環境では、以下のようにREPL的に四則演算などが可能である。

PS C:\PS> 1 + 1
2
PS C:\PS> "foo"
foo
PS C:\PS> 1..5
1
2
3
4
5
PS C:\PS> 1..5 | %{ $_ * 2 }
2
4
6
8
10
PS C:\PS> _

コマンドプロンプトUnix環境のシェルは、その出自より、ほぼ全てが「コマンドを実行する」という思想に基づいた形式の構文となっている。

一方でPowerShellにおける構文解析の振る舞いは、式モードとコマンドモードに大別される。その詳細は割愛するとして、コマンドモードではなく式モードとして解釈されるステートメントを記述することで、他の一般的なプログラミング言語のREPLで式を評価するのと同じような使い方ができる。

PowerShellをシミュレーション環境として使う

このエントリを書いている数週間前に、ネットでは3n+1問題(コラッツ予想)の懸賞金の話題が出た。ということで、試しに3n+1問題の計算をしてみよう。

まず3n+1問題の定義を関数化してみる。REPL上ではなく、独立したスクリプト・ファイル collatz-problem.ps1 に記述する。

<#
 # collatz-problem.ps1
 #>

Set-StrictMode -Version Latest

<#
.SYNOPSIS
3n+1問題(コラッツ予想)を実際に計算してみる。

.PARAMETER n
計算開始の値。

.INPUTS
なし。

.OUTPUTS
計算過程と最終結果。
 #>
function Calc-Collatz(
    [parameter(mandatory)]
    [uint64] $n)
{
    if ($n -le 1) {
        $n
    } else {
        $m = $(if (($n % 2) -eq 0) {$n / 2} else {$n * 3 + 1})
        "$n --> $m"
        Calc-Collatz $m
    }
}

厳密に定義に従って実装すると「1 → 4 → 2 → 1」と無限ループしてしまうので、1になったら停止するようにしている。お試し実装であるため、深く考えずに再帰を用いているし、演算時のオーバーフローも考慮していないが、ちょっと試す分には問題ないはずだ。

collatz-problem.ps1 が用意できたら、PowerShellのシェル環境にて当該ファイルを読み込んで、関数Calc-Collatzを使って計算してみる。

PS C:\PS> . .\collatz-problem.ps1
PS C:\PS> Calc-Collatz 1
1
PS C:\PS> Calc-Collatz 2
2 --> 1
1
PS C:\PS> Calc-Collatz 3
3 --> 10
10 --> 5
5 --> 16
16 --> 8
8 --> 4
4 --> 2
2 --> 1
1
PS C:\PS> Calc-Collatz 27 | Measure-Object -Line

Lines Words Characters Property
----- ----- ---------- --------
  112


PS C:\PS> _

――こんな感じで、スクリプト・ファイルにシミュレーション用のAPIを定義しておき、PowerShellのシェル環境にてドットソーシング演算子を用いて中身をロードして、REPL的にパラメータをアレコレ弄りながらAPIを実行して、結果を眺めるのである。

引き継ぎの際には、スクリプト・ファイルと簡単な手引書を渡せばよい。

もうほんの少しだけ本格的にシミュレーションしてみる

JUAS 日本情報システム・ユーザー協会がほぼ毎年実施しているソフトウェアメトリックス調査にて、COCOMOモデルを用いて人月から最適工期を計算するために必要なパラメータを、統計データより導きだしている。

そこで、JUASの計算式を用いて最適工期と開発者数を計算する workperiod.ps1 を書いたことがある。

前項の3n+1問題と同様に、workperiod.ps1 を使用してPowerShellのシェル環境でアレコレ計算することが可能だ。

PS C:\PS> . .\workperiod.ps1
PS C:\PS> $wp = [WorkPeriod]::new()
PS C:\PS> $wp.Period(12)
5.72357121276666
PS C:\PS> $wp.MinPeriod(12)
4.29267840957499
PS C:\PS> $wp.HeadCount(12)
2.09659311536712
PS C:\PS> $wp.MaxHeadCount(12)
2.79545748715616
PS C:\PS> $wp = [WorkPeriod]::new([Factor]::JUAS_2016)
PS C:\PS> 12, 24, 36 | %{ $wp.Period($_) }
5.95251406127733
7.49969776559852
8.58501084712603
PS C:\PS> _

ところで、Windows上のPowerShellならば、頑張ればWPF/XAMLを使用してGUI化できなくもない。ということで、workperiod.ps1 とセットで利用する workperiodgui.ps1 も存在する。

PS C:\PS> Get-ChildItem *.ps1 | Select-Object Name

Name
----
workperiod.ps1
workperiodgui.ps1


PS C:\PS> .\workperiodgui.ps1

GUI化が適切かどうかについては「課題の内容次第」だろう。実際には、GUI化が必要となる機会は少なく、それよりも表やグラフによる描画が求められる方が多いと思う。

表についてはOut-GridViewに流し込むのが手っ取り早い。グラフ化については未経験だが、手元のツールで何とかするならば、おそらくExcelを使用するだろう(PowerShellからCOM経由でExcelを操作するスクリプトを何度か書いたことがあるので)。

今までどのくらいプログラミング言語を触ってきたか(3秒で挫折したものものも含む) Ver.13

2021-06-20現在のステータス。
https://eel3.hatenablog.com/entry/2020/06/30/201935 から1年経て、こうなっている。

なおCSS、HTML、XMLはひとまず除外する。人工言語ではあるけれども「プログラミング言語」という括りに含められるか否かは議論が分かれる気がする。*1

よく使っている

AWK (Gawk)
単純なテキストレコードの処理はAWKで十分間に合う。今の時代、自作ツールをnawkやGNU awk単体で実装するのは苦行すぎて*2皆無なものの、シェルスクリプトMakefileAWKのコードを埋め込むなどして他のコマンドと組み合わせて使う機会は依然として多い。シェル上でワンライナーでテキスト処理する時にも重宝している。これはこれで十分AWKらしい使い方ではないだろうか?
C++
ちょくちょくお仕事で使うが、未だに本職のC++使いではない。C++11やC++14は非常に便利で、better Cでも使う価値がある。C言語使いからすると、C++03時代よりも充実度が進んだ標準ライブラリ、ラムダ式、autoによる型推論――などなど、便利で羨ましい限りだ*3。あと、Swift時代のクロスプラットフォームC++ライブラリ作者は、どうあがいてもARCから逃れられないので、C++11以降のスマートポインタは必須だ*4正規表現とスレッドも標準ライブラリに加わったので、あとはソケット(低水準ネットワークAPI)をサポートしてくれないだろうか。C++17のstd::optionalも、KotlinやSwiftの経験者としては好ましい。低水準の処理を行いつつも便利な機能で実装時間を短縮できる点は便利で、少なくともシステムプログラム言語としての利点だと思う。だけど機能多すぎ/複雑すぎなところはなんとかならないものか。強力な反面、使い手を選ぶ言語だ。
C言語
お仕事での主力言語だった――ここ最近は仕事以外でしか使ってないけど。シンプルかつ低水準の世界が垣間見れるところが割と好き。とはいえ最近の他の言語と比較すると、シンプルすぎて安全機構が欠けていたり標準の便利機能が少なかったりするので、入門用の言語としては薦められない。にもかかわらず、プログラミング未経験者向けのC言語の本は今でも出版されている――謎だ。クロスプラットフォームなモジュール屋としては、今までC89を採用してきたものの、いい加減そろそろC99とかC11とか次世代の言語とか使いたい。でも古いVisual Studioもサポートしたいから、C99すら2023年まで使えない*5。悲しいなぁ。
DOSバッチファイル
プログラミング言語に含まれるかどうか不明だが、含めてしまう。ちょっとした自動化や、複数ツールを組み合わせて使うときのラッパーとしてよく使う。コマンドプロンプトはシバン(shebang)に対応していないので、スクリプト言語で書いたツールを起動するラッパーとしても多用している。意外と色々なコマンドが用意されているので、単純にそれらを叩く分には十分だが――言語機能がショボいので、バッチファイルでifやforのような制御構文系コマンドが必要になってきたら、何か決定的に間違えていないか、考え直すようにしている。
Kotlin
本格的にAndroidアプリ開発に関わるようになったのがGoogle I/O 2017直後の過渡期なので、JavaよりもKotlinでの経験値の方が多い。モダンな「強い静的型付け」の、割とええ感じの言語やね。ただ、使い始めが「Swift 3をつまみ食いして半年以上経ってからKotlinをつまみ食いした」みたいな経緯だったこともあり、未だに両者の概念・機能が頭の中でごった煮になっている。それと、NDK絡みの作業が多いので、C++11/14・Java 7/8・Kotlinを行ったり来たり。泣けるぜ。Swiftもそうだが、最近のメジャーな「強い静的型付け」の言語は「開発環境込み」で高い生産性とコードの安全性を両立させる方向に進んでいる気がする。
make (Makefile)
プログラミング言語に含まれるかどうか不明だが、DSL扱いで……いやGNU Makeはそこそこプログラミング言語的か。GNU Make 4.0はさらにプログラミング言語的だな、特にGNU Guileなところが。GNU MakeとNMAKEが主力で、稀にNetBSD Make(pmake)を使うが、いずれも独自拡張アリアリだ。もう素のmakeではMakefileを書けない :)
Objective-C, Objective-C++
時代はSwiftだと言われて久しいけど、どっこいObjective-CObjective-C++は生きている。というかSwiftのコードにC++で書かれたライブラリを直接組み込むことができない以上、両者を繋げるグルー言語として生き残ることになるよね。一定以上のリアルタイム性が求められるアプリをSwiftだけで書くのは厳しくて、どうしても部分的にC言語C++を使うことになり、グルー言語としてObjective-Cが召喚されることになる。最近流行の言語と比べると良くも悪くも80年代的だが、アプリケーションプログラミング用としてはC言語よりマシだし、C++ほど複雑怪奇*6ではない。そしてC言語C++で書かれた既存のライブラリをそのまま使える。Objective-Cのハイブリッドな所は好きだが、Objective-C++はハイブリッドすぎて――C++のクラスとObjective-Cのクラスを、C++ラムダ式Objective-Cのブロック構文を同時に使うのは大変だ。便利ではあるんだけどね。
Python
Python 2.xで実装した小ツール群をPython 3.xに移植した。Pythonではlazyなスタイルでのコーディングが許されず、整然とコードを記述する必要がある。その辺は、Perl 5やRubyとは随分と雰囲気が異なる。気になるのは、インデントが必須な言語仕様であるために、シェルスクリプトに埋め込んで使うのが苦痛な点だ。Pythonだけでコードを書く分には気にならないのだけど。
sed
プログラミング言語に含まれるかどうか不明だが、DSL扱いで*7。テキスト処理用。シェルスクリプトMakefileにて他のコマンドと組み合わせて使う。というか正規表現でのテキスト置換以外の機能を使った記憶が……あったな、dとiとpと=とブレースによるグループ化ぐらいだが。私の技術レベルではsedFizzBuzzを書けないので、sedで難しい処理を記述しないようにしている。
Swift
コンパイラによる強力な型推論と型安全性のチェック」がお仕事用のメジャーな言語にまで降りてきたという点で、Swiftは静的型付け言語界のJavaScript*8だと思っている。でもユーザ数的には、Kotlinが「静的型付け言語界のJavaScript」ポジションなのかもしれない。割と好感が持てる言語だが、知識が中途半端にKotlinとごった煮になっているので、ついうっかりif式を書こうとしてコンパイルエラーになったり、「varval」と「varlet」の振る舞いの差異につまづいたりしてしまう*9
シェルスクリプト (/bin/sh)
プログラミング言語に含まれるかどうか不明だが……いや、私的にはシェルスクリプトは立派なプログラミング言語だ。基本的な用途は、バッチファイルと同じくちょっとした自動化や複数コマンドを組み合わせて使うときのラッパーだが、実現できる内容は遥かに多い。言語本体(?)がバッチファイルよりも高機能だし、Unixユーザランドはコマンドが充実している。その意味では、WindowsではMSYSよりもCygwinで――いやむしろWSL(Windows Subsystem for Linux)で環境構築すべきだろう。Cygwinでは、主要な処理をシェルスクリプトで記述しておき、bashからはシェルスクリプトを利用し、コマンドプロンプトではラッパーのバッチファイル経由でシェルスクリプトを叩く使い方をしている。ただWindows上では処理速度が妙に遅くなる点が不満だ。まあしかし、Unixのシェルは言語設計もシステム開発技法も未成熟だった大昔に「プアな環境でも問題なく動作する、プログラマブルな対話型コマンドインタプリタ」として開発された代物なので、言語設計の研究が進んでから作られたプログラミング言語と比較してはならない。

あまり使っていない

Perl 5
時々、やむをえない事情で触ることがある。だが基本的によく分からない。何というか、あの記号の羅列っぽさに中々慣れないというか、自分は余りに自由度が高すぎる言語は苦手だと気づいたというか。(言語仕様に慣れているなら)半ば使い捨てなテキストフィルタとかをさっと書くには、悪くない言語だと思うのだが。
Ruby
自作ツール実装にて、AWKの代替言語の最有力候補だった(でも最近はPythonが多いんだな、これが)。テキスト処理でも割と重宝するが、バイナリデータへの変換が絡んでくるとAWKよりもRubyを使った方が効果的だ*10。そろそろirbを電卓代わりに使うスタイルも板に付いてきた気がする。to_s(16)やto_s(2)で基数変換して表示できるところが好き。
SQL
生まれて初めて触れたプログラミング言語その3ぐらいに位置する。組み込みの人なのでSQLとは無縁だと思っていたが、まさかTransact-SQLを少しだけ触ることになるとは。最近はAndroidアプリ絡みでSQLiteに触れることもあるが、AndroidXのRoom経由だったり、ContentResolverのqueryだったりと、フルセットのSQL文ではなく局所局所でDSL的に使う感じである。
Windows PowerShell
時代はPowerShell Coreらしいが、現行のWindows 10でデフォルトで利用できるv5.1に留まったままである。スクリプト言語としてのPowerShellは、オブジェクト指向.NET Frameworkを叩けてダイナミックスコープでスクリプトブロック(という名の無名関数)と、無茶でピーキーで完全にプログラマ向けな代物だ。Microsoftもよくもこんなエライ代物を出したものだ。残念なことに、コマンドプロンプトの代替という観点では、外部ツールとの親和性が微妙にイマイチだ(特に文字コードとか)。でもPowerShell内で閉じている分には問題ないので、私の手元では「Windows専用のGUI付き小ツールを作るためのスクリプト言語」か「Excel COMとか叩く用のスクリプト言語」か「Windows Serverの管理スクリプトを書くためのスクリプト言語」扱いしている。ところで、いい加減『Windows PowerShell イン アクション』並みの言語解説書の最新バージョン対応版を出版してくれないだろうか。

最近使ってないが、縁は切れてない

bash
最近はデフォルトシェルがbashな環境も多いので、自分用のツールぐらいは素の/bin/shではなくbashで書いても大丈夫な気がしてきた。shよりbashの方が遥かに便利だからなあ――PerlRuby等には負けるけど。bashスクリプトを書くときの唯一の欠点は、メジャーバージョンごとの差異や各ディストリでのビルドオプションの違いにより、同じbashという名前でも実は千差万別なところだと思う。PerlRubyのバージョンは気にするけど、これがシェルになると意外とバージョンに無頓着になってしまう。なんでだろう?
C#
かつて、勉強を兼ねてC# 2.0を少し触ろうとするも未完に終わり、数年後にあらためてVisual Studio 2013をインストールして少しだけ触った*11けどほんの少しだけで終わった過去をもつ私。変数の型推論ラムダ式LINQ・デフォルト引数は便利だなあと思っていたら、いつの間にかC# 8.0になってKotlinやSwiftに見られる流行を取り入れてますな。おっちゃん、付いてくのが大変だよ。.NET Frameworkの機能数は反則ものだが、所々に微妙に抽象化が行き過ぎたAPIが見られるのは気のせいだろうか? それにしても、クラスが必須ではないC言語C++に慣れてしまった弊害か、アプリケーション・メインエントリすらclass内に定義しなくてはならないC#には、なかなか慣れない。
Free Pascal
お試しで触っているのだが、微妙にDelphi/Free Pascal初心者(ただし他言語の経験者)向けの良い資料が少なくて難儀している。玉石混交なのだ。いっそのこと『OBJECT PASCAL HANDBOOK―マルチデバイス開発ツールDelphiのためのプログラミング言語完全ガイド』を買ってしまおうかしら……と思っていたら絶版っぽい。
Go
寡作ながらもいくつか小ツールを書いてみたが、標準ライブラリが充実しているコンパイラ型言語っていいっすね。C言語に比べればC++の標準ライブラリも充実しているが、どちらかといえばプリミティブな機能が中心だ。PythonRubyばりの標準ライブラリを抱えているGoには及ばない。その辺は、やはりCプログラマ(特にCでフィルタやデーモンの類を書く層)には受けそうな言語だと思う。並列処理周り(goroutines)とかARM対応とかが気になる。ソフトリアルタイム限定だが「組み込みLinux + Goで書いたデーモン」とかどうだろう? ただメモリを食うらしいという噂がどうなったか気になる――64bit環境では解消されるという話だったようだが、32bit環境でも解消されるようになったのだろうか? 組み込みでは現時点では逆立ちしたって64bit CPUはありえないからなあ、スマホタブレット以外では。
Java
生まれて初めて触れたプログラミング言語その2。実のところ、職業プログラマとして本格的に使用することは一生ないと思っていた。Androidアプリ開発も、Kotlin採用後に本腰入れて関わるようになったので、Kotlinメインだ。だが、なぜかぬるい感じに時々Javaのコードを触っている。先にコレクションの操作方法が充実した他の言語を学んでからJavaを本格的に触るようになったので、Java 8以降のStream APIが使えないと身体が拒否反応を示す。少なくとも、構文の見た目こそ保守的なオブジェクト指向プログラミング・スタイルで書かれたC++に似ているけど、中身はC++とは似ても似つかない代物だということは体感している。
Lua
Wiresharkのパケット解析スクリプトを書いたことも、C言語で書かれたUnixデーモンの設定ファイル用に処理系を組み込んだこともあった*12。あれから数年経ったが、今はどんな感じなんだろう?
QML
宣伝文句のとおり、QMLはGUIの記述に非常に向いている。それも、単に標準のUI部品(エレメント)を使うだけでなく、少し改造して使うとか、オリジナルのUI部品を作って使うとか、それらを別のアプリケーションに使いまわすとか、そういう時に威力を発揮する。あと、プロパティバインディングやレイアウトのアンカー指定など、画面サイズの変更に追随するUIを作りやすい機能も揃っている。JavaScriptでちょっとした処理も記述できる――とはいえ、やりすぎるとパフォーマンスの罠が……。少なくとも、JavaScriptでゴリゴリコードを書くのはQML的ではない。QMLは宣言的に、シンプルに書くものだ。力技でロジックでゴリ押しすると、色々と罠に嵌る言語だ。
Scheme
GaucheWindowsネイティブ環境用バイナリは実験版だが、私が触る分には何の支障もない*13ことに気づいて久しい今日この頃。『Scheme手習い』と『Scheme修行』を購入したので、とりあえずCommon LispではなくGaucheScheme)の勉強をする方向に転換しようか検討*14しているうちに何年たったのやら。Gaucheはフィルタ・ライクな小ツールの実装用としても良い感じだ。しかし最も多い利用方法はREPLを電卓代わりにすることだ*15。うーん、作業環境がmacOSLinuxに移ったなら、大手を振ってGaucheでフィルタを書くのだが。
Tcl/Tk
Tclは書き方を忘れた頃にテキスト処理ツールを書いている気がする。Tclは結構独特な言語だ。構文がシェルスクリプトばりに全てコマンドだったり、値が全て文字列だったり、実はリスト構造だったり、意外とTCPソケット通信が得意だったり……。それでも慣れれば結構使いやすい。意外とプロトタイピングに向いている気がする。8.6以降ではオブジェクト指向プログラミングもOKだが、それよりも例外処理用のtry末尾呼び出しの最適化用のtailcallの方が興味深い。しかし、これからメジャーになる可能性は低そうだ。Tkは……小規模なGUIツールをさくっと構築できるところは便利だが、Webアプリ全盛の時代にどれだけ訴求力があるのやら。
Visual Basic .NET
Visual Basic .NET 2003で書かれたコードを時々メンテ中。流石に開発環境はVisual Studio 2013移行したけど。
XSLT
よく考えてみたら生まれて初めて触れたプログラミング言語その4ぐらいに位置する言語だった。縁が切れたと思いきや、仕事でXHTMLから特定要素を抜き出す作業に使うことがあったり……。XMLからテキストレコードに変換してしまえば、後はUnix流テキストフィルタの世界が待っている。餅は餅屋というもので、定型的なXMLの変換はXSLTで記述するべきか。唯一気に入らないのは、xsl:sortでアルファベットの大文字と小文字を区別してソートすることができないこと。ぐぬぬぬ。

これから(また)使うかもしれない

Alloy
形式手法の中では比較的カジュアルに使えそうなので期待中。入門書も処理系も入手した。私の場合、先に何か論理型の言語をかじった方がよいのかも。
bison (yacc)
プログラミング言語に含まれるかどうか不明だが、DSL扱いで。やっぱり構文解析系統のコードを自作するのは割に合わない――だなんてうそぶきつつ、LALR法とか全く知らないままに、既存のyaccのコードを切り貼りして遊んでみた。簡易電卓レベルだが便利さを体感しつつ、さっそくtypo 1文字で痛い目(shift/reduce)に遭った。とりあえず、flexと組み合わせた上でのエラー処理(エラーメッセージの改善)が課題だ。
Common Lisp
2009年に勉強しようと思い立ったものの、未だに進んでいない。階乗とかハノイの塔とかiotaぐらいは書いたが、目標は「ちょっとしたツールを自作する」だ。まだ道は遠い。最近は時々CLISPを簡易電卓代わりにしている。
Coq
ソフトウェアの基礎が気になるので、処理系だけ入手。
F#
OCamlは「Windows上で日本語を扱う」という視点では処理系がちょっと微妙なので、いっそのことF#に乗り換えようかと……。『実践F#』が積読状態になっている。
flex (lex)
プログラミング言語に含まれるかどうか不明だが、DSL扱いで。字句解析用のツールという印象が強かったのだが、よく考えてみたら、flexは「sed(1)のよくある使い方」と同様に「正規表現でパターンマッチング --> 何らかのアクション」という内容を記述するためのツールだった。ただ単に、「何らかのアクション」をC言語で書けることと、flex自体ではパターンマッチングをせずに「パターンマッチングするC言語のコード」を生成することが少々風変わりなだけ。grep(1)やsed(1)その他で小ツールを実装して運用しつつ、性能が求められたらflexで専用ツール化する――とか考えたけど、普通にgrep(1)やsed(1)を使う方が高速だった。
Forth
pForthをMinGWでビルドしたので処理系は手元にある。スタック指向の言語はいつか勉強したい。
Io
プロトタイプベースである点を除けば、何となくSmalltalk的であるような――公式ドキュメントらしきIo Programming Guideでも影響を受けた言語として真っ先にSmalltalkが挙げられているから、あながち思い違いでもないだろう。今更ながら『7つの言語 7つの世界』のIoの章を読み終えたので、ちょっとしたコード片を書いているが……Windows版のバイナリが古いためか、リファレンス通りなのに動作しないコードに直面している。
JavaScript(クライアントサイド)
クライアントサイドJavaScriptのコードを書いたこともあったけど、スキル的にはもう何もかも賞味期限切れだよなあ。ECMAScript 2015以降を使うなら、それ以前とはコードのスタイルから変えないと不味そう。
JavaScript(サーバサイド?)
Node.jsやPhantomJSが出てきた頃は、クライアントサイド以外でもJavaScriptが使われる文化が広まる可能性があるのか気になったものだが……結局どうなっているの? 何か処理系もNode.jsに収束してきた印象が。
LOGO
そういえばLOGOを触ったことがない。とりあえずUCBLogo(Berkeley Logo)だろうか? Windows上でUCBLogoばりにGUI無しで動作する処理系はないだろうか?
Object REXX
思うところがあって処理系とIBM謹製のドキュメントを入手したものの、そこから先の進展は無いまま。ReginaでClassic REXXっぽい感じで触っているからなあ。
OCaml
Common Lispを勉強するはずが、いつの間にか触っていた言語。一応、階乗ぐらいは書いた。時間が取れたらもうちょっとしっかりと勉強したいが、面倒なのでF#に移行しようか検討中。
Oz
ふと思い立ってUbuntuにMozartを入れた。『Scheme手習い』の次はCTMCP片手にOzで勉強かなあ。道は遠いな……。
PostScript
これかForthか、どちらに手を出すべきか? 悩ましい。
Processing
入門書も処理系も入手して、あとは弄る時間をつくるだけ。
Prolog
『7つの言語、7つの世界』の地図の色分けプログラムには衝撃を受けた。何というか「正しい問い」を見つけられるか否かが肝なのか。この辺は、根底の部分でAlloyに通じる何かがあるように思う。ひとまず、Prologで論理プログラミングと宣言的なスタイルに慣れておけば、形式手法にて「論理で宣言的に」記述するときに戸惑いが減るのではないかと期待している。
Rust
仕事柄「C/C++の次のシステムプログラミング言語」はそれなりに興味の対象で、Go言語やD言語ほどではないが、Rustも……まあ、気にならなくはない。ちなみに、これら3言語と同列にObjective-CやSwiftが挙げられることもあるようだが、個人的見解としては、システムプログラミング言語としてのこの2言語には全く期待していない。あれは、Appleというしがらみからは逃れられないでしょうな。
VBA (Visual Basic for Applications)
今までVBAから逃げ回っていたのだが、ついに使うことになりそうな予感。たぶん、Access VBA 8割にExcel VBA 2割ぐらい。

今は全く使っていない

Active Basic
VBScripを触りだした影響で、時々思い出しては弄くっていた。ほんの少しだけ触って放置して、すっかり忘れてからまた触る――これを繰り返していた気がする。なので毎度初めて触るのと同じ状態だった。String型をバシバシ使用 :)
bc
その昔、Windows標準の電卓アプリの代わりに使おうとして色々あって挫折した。今はirbclisp/goshで計算しているからなあ。
CASL II
生まれて初めて触れたプログラミング言語その1。何だかんだで、後でCプログラマになってからも低水準での思考ツールとして微妙に役に立っている。まあ考えるための言語であって実用言語ではない。仮に実用的な処理系*16があったとしても余りに命令がシンプル過ぎて悶絶するなあ、なんてFizzBuzzしてみて思った。
Clojure, Scala
JDKがなくてもJava APIを叩くスクリプトを書けるので非常に便利。Scala型推論とか、便利っすね。言語仕様はJavaよりも好みだ。とはいえ、IoT時代にJava VMベースでどこまでメインストリームに居残ることができるのか? ちょっと興味深い。サーバサイドに活路を見出すのだろうか?
COBOL
FizzBuzzするためだけにOpenCOBOL 1.0をWindows上に用意して触ってみた。なんというか、COBOLの名前と生まれた時代が示すように基幹業務(というかお金や帳簿が絡んでくるところ)向けの言語だよなあ、といった感じ。COBOL 2002のフリーフォーマットを採用するだけでも使い勝手が変わる気がしたが、世の中にはまだ広まらないのだろうか。
CoffeeScript
仕事で使う予定はない。RubyPythonその他の影響を受けているだけあり、その手のスクリプト言語っぽい感じでコードを書けるので、慣れれば素のJavaScriptで直接コーディングするよりは楽だ。しかし標準ライブラリ回りや処理系絡みの機能やサードパーティのライブラリなど、結局はJavaScriptを知らないとCoffeeScriptでコードを書けないと思う。それに生成されたJavaScriptのコードを見て「うわぁ、これあまり効率的でないなあ」と感じる時もあって、高速化が必要な部分では生成されるコードを気にしながら記述したりCoffeeScriptを諦めてJavaScriptで書くことになるので、やはりJavaScriptを知らないとマズイ。とはいえ便利なのは確かだ。CoffeeScriptのコードは即Node.jsで実行できるので、その辺りから「CoffeeScriptでテキストフィルタ」的な文化が生まれると面白いかも。気になるのはECMAScript 6の存在で、今までCoffeeScript独自の機能だった部分の多くがES6で取り込まれるので、今後ES6対応が進むにつれてCoffeeScriptの立場がどうなっていくのか、少々興味深い。
D言語 2.x
仕事柄「C/C++の次のシステムプログラミング言語」はそれなりに興味の対象で、Go言語ほどではないが、D言語も気になる存在だ。D言語シンタックスがC・C++に近いだけでなく、コーディングしている時のアプローチ・判断についても、CやC++での流儀がそこそこ通用しやすい気がする。少なくとも、Go言語でコーディングするよりは、文化的背景の違いによるモヤモヤは感じにくい。あと、標準ライブラリを使ってテキストフィルタを書いたところ、エラー処理を1~2ヶ所のtry - catchにスッキリまとめることができて、ちょっと驚いた。throwされる例外のメッセージ文字列が、ちょうどよい塩梅の内容だったため、メッセージを変更する(いったんcatchして、再throwする)必要がなかった。ちょっと残念なのは、マルチバイト対応だが……。
Emacs Lisp
.emacsにコピペ」限定で。Common LispSchemeを触ったためか、何となく内容を追えるようになってきた気がしていたが、勘違いだった。
Fortran
Fortran 90やFortran 95あたりは結構近代的な言語だと思う。用途次第ではC言語よりもFortranの方が遥かにマシな選択だろう。配列がらみの処理はFortranの方が得意だし、言語機能としてのモジュール化の方法はC言語には存在しない。可変長な文字列の扱いに微妙な制限がある点はマイナスな気もするが、まあ基本的に数値計算プログラム用の言語だからなあ。
GDB (GNU Debugger)
……いやGDBはデバッガとして使っているが、GDBスクリプトを書く機会は(FizzBuzz以外に)ない。勉強不足なだけかもしれない。
Groovy
JDKがなくてもJava APIを叩くスクリプトを書けるので非常に便利。動的型付け言語っぽくいくもよし、@CompileStaticや@TypeCheckedで型推論するもよし。言語仕様はJavaよりも好みだ。コンソールアプリを書く人としては、オプション引数解析用の機能を標準で持っている点で、GroovyはClojureScalaよりもポイントが高い*17。個人的には、IoT時代に「Java VMベース」の言語としてどこに活路を見出すのが、興味深く見守りたいところ。やはりサーバサイドだろうか?
HSP (Hot Soup Processor)
FizzBuzzで楽しんでみたが、何というか他言語経験者には受けが悪そうな命令体系だと思う。もっとも初心者がプログラミングという行為に深入りせずにWindows用のGUIな何かを作る分には、あの命令体系でも十分な気がしないでもない。ところで元々は「HSPで職業プログラマ的な良いコードを書くと、どんな感じになるか?」というネタを思いついて処理系を用意したのだけど、そちらは全く進展がないまま。
JScript on WSH
他人が使うテキスト処理ツールの実装に使って以来、時々触ってきた。Windows用の配布可能な小ツールを実装する時の定番言語だった。でもそろそろ潮時だろう。HTAと組み合わせてクライアントサイドJavaScriptなノリで簡易なGUIツールを実装できる点も、PowerShell + WPF + XAMLで代替できそうだ。他のメリットは「JavaScriptECMAScript)でフィルタを書ける」だったが、WSHのなかなか目的にたどり着けないオブジェクト階層にイライラするよりも、Node.jsやPhantomJSを使ったほうが精神衛生的にマシだ。
m4
その昔テキスト処理用に触ろうとして、Windows用のどの処理系も日本語の置換に何かしらの問題を抱えていたので泣く泣く諦めた。思うところがあって改めて少し触ってみたが――なるほど、確かに中毒性のある言語*18だ。
Smalltalk (Squeak, Pharo)
Smalltalkは有名な古典的プログラミング言語だというのに、触ったことがない。ということでSqueakとPharoの処理系のみ準備完了。うーん、「環境」付きなのが気になる――言語を弄くる基準が「コンソール上でテキストフィルタ」という変な人種な私だからなあ。
Smalltalk (GNU Smalltalk)
個人の思想信条による理由よりSqueakとPharoにわだかまりを感じてしまう変人なので、邪道だと思いつつもコンソールでテキスト処理もOKなGNU Smalltalkも用意してみた。これで言語としてのSmalltalkの勉強に集中できる……か?
REXX
Open Object REXXの処理系を入手したのに、何故かReginaを入れてClassic REXXっぽい方向に走っていた。何というか、COMコンポーネント.NET Frameworkと無関係でいられるのなら、バッチファイルの代替としてはREXXあたりがほどよい塩梅だと感じる。しかし最近流行の言語とは随分と勝手が違うし、日本語の情報も少ない。メインフレーム以外の世界で流行る可能性は少ないだろう。
T4 Text Template
「へえ、こんなものがVisual Studioに入っていたのか。機能多すぎで色々と便利なツールを見逃しているんだな、やっぱり」と思いつつ触ってみた。テンプレート変換の用途ではピカ一だと思う。ただ処理系を手に入れる方法が「Visual Studioをインストールする」or「MonoDevelopをインストールする」なので、何となく「単体で手軽に使えるツール」ではないというイメージが……。まあC#VBで処理を記述するので、それらの処理系が必要だという面での制約なのだろう。
VBScript on WSH
JScriptほどではないが「Windows上で他人も使えるツールを書くためのLL」扱いしていた言語。Windows Server管理の関係で触っていた。というかWebで入手可能なWSHのサンプルの大半がVBScriptで書かれていたり、ADSI関連のコレクションをJScriptで舐めれなかったりして、結局は必要に駆られて使用することに。明快に記述できる文法は評価に値するが、スクリプト言語としては少々冗長だ。配列は自動拡張しないし、組み込み関数はプリミティブ気味だし、冗長気味な文法との合わせ技でコードがさらに冗長になっていく……。文法や言語仕様の詳細なドキュメントが見つからないのだが、どこにあるのだろうか?*19
Vim script
少し触ってみた分には、exコマンドの拡張(=コマンドの羅列)とは気づかない程度にはプログラミング言語らしいと思う。とはいえ妙なところで嵌ったり微妙に一貫性のない部分があったりするので、その辺りで好き嫌いが別れる気がする。
秀丸マクロ
7年ほど秀丸エディタを使っていたが、マクロを書く機会はなかった。一念発起してFizzBuzzしてみて感じたのは、最近の便利な言語に慣れた身としては色々とモヤモヤ感がある言語仕様だということ(歴史的経緯的に仕方ないのだが)。とはいえちょっとした拡張ツール的なものを手軽に作れそうではあった。

*1:HTML5 + CSS」の組み合わせなら、チューリング完全の疑惑があったり、JavaScript使わずにCSSでWebチャットを作った猛者がいたりと、色々と怪しいのだけど。

*2:「独立性の高い単体のツールを実装する」という視点では、現代ではAWKよりも便利な言語が山ほどある。

*3:しかし標準ライブラリの充実度をJavaC#.NET Framework含む)と比較したり、型推論まわりをKotlinやSwiftと比較してはいけない。以前よりも随分と便利になったのだけど、だけど、隣の芝生を見てしまうと、うーん……。

*4:SwiftではC++の機能を直接呼び出すことができないので、Objective-Cでラッピングして利用することになる(インタフェースはObjective-Cで、内部実装はObjective-C++)。この時、Objective-Cクラスのインスタンス変数として「C++クラスのインスタンスを保持するスマートポインタ」を持つ構成にしておくと、Objective-Cクラスのインスタンスがdeallocされる時に、スマートポインタ経由でC++クラスのインスタンスもdeleteされる。

*5:本格的にC99がサポートされ始めたのはVisual Studio 2013以降だ。それよりも前のバージョンのEOLだが、Visual Studio 2012が2023年1月となっている。

*6:少なくともC++の言語仕様は私の手には余る。自分が把握している範囲の機能でコードを書くのは好きなのだけど。

*7:これでもsedチューリング完全な言語だと証明されているらしい。

*8:私の認識では、JavaScriptは、第一級関数やクロージャがお仕事用のメジャーな言語に組み込まれて、少なくない人が使う契機となった言語だ。

*9:Kotlinのvalは「再代入不可の変数」だ(定数はconstで定義する)。Kotlinのプリミティブ型以外のデータ型はclass(つまり参照型)なので、valで定義した変数を破壊的操作する行為は割と普通だと思う。一方でSwiftのletは定数であるし、値型が中心の言語仕様である影響かstructやenum(つまり値型として振る舞うデータ型)が多用されるので、letで定義した変数を破壊的操作できるケースとできないケースが生じる。

*10:とはいえ、ついついC++を使ってしまうのだよなあ。

*11:言語仕様的にはC# 5.0の環境だが、ライブラビまわりはC# 4.0相当だったはず。

*12:Windowsのことを考えなければ、自前でライブラリをビルドしてアプリに組み込むのは結構簡単だった。

*13:支障がある部分を触るほど深入りするには、あと20年ぐらい掛かるのではないか?

*14:Schemeの勉強というよりも、再帰の勉強なのか?

*15:現状はirbclispかgoshの3択だ。

*16:――といってもシミュレータだけど。

*17:ClojureScalaに関しては、同様の機能を私が見逃している可能性も高いのだが。

*18:m4はマクロプロセッサなのでプログラミング言語ではないはずだけど……。

*19:MSDNの資料では物足りない。もうちょっと掘り下げた内容のものが欲しい。

Windows 10 Version 2004でのネットワーク名の扱いについてのメモ

Windows 10にてネットワーク名を変更する方法について、ネット上の情報は若干の混乱というか両論併記的な記述が多いようなので、少し整理してみる。

要は、Windows 10 ProやEnterpriseの場合:

  • ローカルセキュリティポリシーでネットワーク名を設定する方法
    • Windows 管理ツール → ローカル セキュリティ ポリシー → ネットワーク リスト マネージャー ポリシー → すべてのネットワークを表示
  • レジストリでプロファイル名を変更する方法
    • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles

――という2つの方法があるのだが、それぞれどう影響しあうのか、という話だ。

Windows 10 Homeではレジストリでプロファイル名を変更するしかないので割愛)

  1. ローカルセキュリティポリシーでネットワーク名を設定した場合には、その名前が表示される。
  2. ローカルセキュリティポリシーでネットワーク名を「未構成」に場合には、レジストリに設定されているプロファイル名が表示される。

ローカルセキュリティポリシーにて「すべてのネットワークを表示」とした際に一覧表示されるネットワーク名は、レジストリに設定されているプロファイル名だ。

興味深いことに、ローカルセキュリティポリシーで設定したネットワーク名は、レジストリのProfileNameとは別のどこかに保存されるようだ。

つまり、Windows 10 Pro / Enterpriseにおいては、GUIで表示される「ネットワーク名」には「ローカルセキュリティポリシーで設定した名前」と「レジストリ上のプロファイル名」の2種類があり、「ローカルセキュリティポリシーで設定した名前」の方が優先的に使用される。そして「ローカルセキュリティポリシーで設定した名前」が未構成の場合は「レジストリ上のプロファイル名」が使用される。

どうしてネットワーク名が内部的に2種類あるのかは不明だ。おそらく「歴史的経緯による継ぎ足し」によって、この二重化が発生したのだと思うのだが……プロファイル名には使用できない文字がある(なので「プロファイル名≒ネットワーク名」とすると、使用可能な文字種の制約が厳しい)とか、そんな理由かしらん。プロファイル名って「辞書型におけるキー値」っぽい役割で利用されていそうだから、色々と制約がありそうな気がする。

コードリーディング対象としての標準ライブラリのすゝめ

年頭において、初心を忘れないように書いておく。

新しいプログラミング言語の使い方を学ぶ時、個人的に、公式のチュートリアル/コーディングスタイル/リファレンスを参照するのと並行して、当該言語の標準ライブラリのソースコードを読むことが多い。

全くのプログラミング初心者には推奨できない手法だ。しかし、学習者が単なる「当該言語の初心者」でしかなく、関数やクラスなどのモジュール化機構による機能分割に慣れ親しんだプログラミング経験者ならば、話は違う。

大抵において、標準ライブラリの中身は、不要な部分が削られて小さくまとめられた単機能であり、手のひらサイズのコードで実現されている――だから読んで理解しやすい。昨今の言語ならば、REPLで振る舞いを確認することも容易だろう。

最近の主流言語の多くでは「言語仕様の設計者≒当該言語の最もメジャーな処理系の開発者≒『最もメジャーな処理系』に付属する標準ライブラリの設計・実装者」の図式が比較的成立しやすい。そのような環境においては、標準ライブラリの実装者は当該言語のグルだ。傾向として「言語設計者の思想」を強く受け着いたコードを書くはずである。

結果として、十分にレビューとテスト済みの、無駄が取り除かれた、その言語らしさが凝縮された短いコードを読むことができる。

学習の目的が「当該言語でのコーディング時にフラストレーションが溜まらない程度に、言語の流儀に慣れたい」であれば、標準ライブラリのコードを読んで真似することにより、足切りをクリアできる水準に比較的短時間で到達できる。

この方法を伝授された時の言語はC言語で、BSDユーザだった当時の先輩が構築したFreeBSDマシンにリモート・ログインして、BSD libcの文字列関数*1のコードを読むよう勧められた。まだGitなんて影も形もなかったころで、Webベースでのコードリーディングが堪らなく遅かった時代だ。System source tree込みでFreeBSDをインストールして、EmacsないしVim/nviでタグジャンプを駆使しつつBSD libcのコードを読む、というのは割とベターな方法ではあった。

最近ガッツリ学んだ言語はKotlinだが、GitHub公式のリポジトリがあり、その中に標準ライブラリのコード一式も含まれている。ブラウザ・ベースでの閲覧で十分ならば、ちょっと検索するだけでコード自体にたどり着いてしまう――という事実には、隔世の感を禁じ得ない。ただ、興味深いことに、言語は変われども「読みやすいのは文字列とコレクション操作」という感想に変わりはない。

*1:一般にstring.hに定義されている関数群のこと。

シェルスクリプトを生成するシェルスクリプト

超小規模な社内サーバの管理に関わることが多いためか、管理用に「スクリプトを生成するスクリプト」を作成することがある。

スクリプトを生成する」と書くと、なにかしら大層なことをやっているように聞こえるが、何のことはない、単に可変のパラメータをテンプレートに当てはめて出力しているだけである。

4月の新卒社員の入社や、オンプレミスのサーバの入れ替えにともなうデータ移行などでは、似たような作業を何度も繰り返す場面がある。例えばアカウントやグループの追加などだ。こういった場面では、事前にアカウント名などの一覧を作成しておき、それらをテンプレートに当てはめて、「アカウントを一括追加するスクリプト」を生成しておく。

生成したスクリプトは、事前に内容をチェックしておき、後で*1スクリプトを実行してシステムに適用する。

そして使用済みのスクリプトは、システムの運用記録の一部として保存しておく。

この手法を、まだPowerShellが無かったころにWindows Serverで採用した時は、「RubyスクリプトWSHVBScript)のスクリプトを生成する」という、少々大げさな構成だった。システムをスクリプトで十全に管理するためにはWSHを使うしかなかったのだが*2スクリプト生成自体はテキスト処理が得意な別の言語*3を使わないと辛かった。

(最近のWindows Serverはまともに触れていないのだが、PowerShellを使えば「PowerShellPowerShellスクリプトを生成する」という感じになりそうだと思っている)

今はLinuxサーバを運用しているが、Unix系のファイル・ベースの世界なので、「シェルスクリプトシェルスクリプトを生成する」というシンプルな構成に落ち着いている。シェル環境はテキスト処理もそれなりにイケる口だし、システム管理自体もシェル・コマンドで済むので、こういう芸当が可能となる。

つい最近サーバを引っ越したのだが、ディレクトリ・サービスなんて存在しない環境なので、「引っ越し元のユーザとグループを、uidとgidが同じ値となるように、引っ越し先に登録する」という作業を行う必要があった。

全ユーザの情報は「getent passwd」で取得できるが、システム管理用のアカウントも含まれている。引っ越し元の環境では、一般ユーザのuidは1000以上なので、まずはuidでフィルタリングすればよい。ただし例外として、nobodyのuidは「1000以上」に該当するので、追加でフィルタリングする。

getent passwd |
awk -F : '$3 >= 1000 && $1 != "nobody"'

これで、例えば次のような出力が得られる。

foo:x:1000:1000::/home/foo:/bin/rbash
bar:x:1001:1001::/home/bar:/bin/rbash
baz:x:1002:1002::/home/baz:/bin/rbash

この出力を、例えばuseradd(8)のコマンドの羅列に変換してしまえば、後は引っ越し先で実行するだけで済む(パスワード・ログインが絡んでくると厄介だが、このサーバでは、一般ユーザはsshの公開鍵認証でのログインのみ許可している。また制限付きシェルを使用して、利用可能な機能を制限しており、su(1)やsudo(8)なんて夢のまた夢状態である。なので、この程度の変換で済む)。

getent passwd |
awk -F : '
$3 >= 1000 && $1 != "nobody" {
    print "useradd -b /home -g " $4 " -s " $7 " -u " $3 " " $1
}'

結果はこんな感じ。

useradd -b /home -g 1000 -s /bin/rbash -u 1000 foo
useradd -b /home -g 1001 -s /bin/rbash -u 1001 bar
useradd -b /home -g 1002 -s /bin/rbash -u 1002 baz

コマンドの生成はワンライナーでも可能だが、実際のサーバ運用では、引っ越し直前に急なアカウントの変更が発生することがある。なので、コマンドの生成処理自体をスクリプト化しておき、直前にアカウントの変更が発生した場合には再度スクリプトを実行するだけで済むようにしておく。

#!/bin/sh

getent passwd |
awk -F : '
BEGIN {
    print "#!/bin/sh"
}
$3 >= 1000 && $1 != "nobody" {
    print "useradd -b /home -g " $4 " -s " $7 " -u " $3 " " $1
}' >02-make-users.sh

実のところ、サーバ管理はほぼ独学でやっているので、このような手法が一般的なのかどうなのか分からない。でも、まあ、私のような素人が思いつくような方法なので、別段変わったやり方ではないと思う。

ただし、おそらく「大きな会社のシステム管理」や「大規模システムのインフラ管理」という「利用者の同質性が低い」環境では、このような手法は危険だろう。スクリプトを生成するとなると、本来はユーザ名などの可変パラメータのバリデーション(というかエスケープ処理)を真面目に考える必要があって、そこで七転八倒して苦しむぐらいなら最初から「スクリプトを生成する」ではなく「スクリプト内で処理までしてしまう」に舵を切ってしまった方が安全だ。

良くも悪くも、同質性の高い少人数が利用しているサーバだからこそ可能な手法だろう。

まあ、モダンなサーバ管理では、より安全で簡単な手法が採用されているに違いない。

*1:新卒社員の正式入社日や、サーバの入れ替え作業実施日など。

*2:システムの細かいところに手を加えようとした時、バッチファイルでは不十分だった。

*3:当時だとPerlPythonRuby

2020年Objective-Cの旅

まだObjective-Cで消耗してるけど、なんか質問ある?

いや、別にSwiftが嫌いな訳じゃない。「Xcode + Swift」とか「Android Studio + Kotlin」みたいな「開発環境込みでの『モダンな静的型付け言語』」の便利さは体験済みな訳で、私だって今からiOS/macOSアプリを書くならSwiftを第一候補とするだろう。

なのだけど、例えば既存のPC向けクロスプラットフォーム・アプリをスマホに移植する場合、私の周囲にはアプリのコア部分がC++で書かれたものが多い。

そのようなアプリは、GUIは各プラットフォームのフレームワークで実現していて、コア部分はC++で書かれていて、さらにコア部分の中の機種依存層は各プラットフォームのライブラリをC/C++ベースの言語で叩いていて――という構造を持っている。

Windows向けの実装だと、以下のような感じになっていたりする。

こんなアプリをiOSに移植しようとすると、Swiftからコア部分を利用しやすいようにObjective-Cでラッピングして*1、コア部分の中の機種依存層の中身はObjective-C++で書くかC/C++を使いつつCoreFoundationなどのC言語ベースのAPIを叩く――というアプローチをとることになる。

で、Swiftで書けるGUI側は若人に任せて、おっさんの私は泥臭い下回りを担当する……あれ? 結局Swiftのコードを書く機会が少ないぞ、と。

まあそんな訳で、2020年になってもObjective-Cのコードを書く機会があるのだけど、今となっては「モダンなObjective-C」を学ぶ情報源に乏しい。さてはて、どうしたものか?

Apple公式のObjective-Cのドキュメントの所在

Apple Developerのサイトを見た感じ、Objective-Cのドキュメントは軒並みDocumentation Archiveに置いてあるようだ。

ここに置いてあるということは `no longer updated' ということだ。まあ、そうだよね。

ところで、一部のドキュメントは日本語版が作成されていたはずなのだけど、それらへのリンクは無いのだろうか? Documentation Archiveのものは全部英語っぽいのだが……。

日本語の書籍は電子書籍が頼り

Apple公式の資料の扱いがあんな感じなので、日本語で書かれた書籍は絶滅寸前――と思いきや、意外とKindleなどの電子書籍なら購入できるようだ。

今時Objective-Cを触る人は、かつてのiOSアプリ開発のような「オールObjective-C」ではなくて、私みたいに「Swiftとの連携」とか「C/C++との連携」とか、少々マニアックな用途で使うことが多いと思う。

そういう「若干、言語のコアな部分に踏み込む」ような用途を考えると、『Effective Objective-C 2.0』や『詳解 Objective-C 2.0 第3版』あたりを推奨したいところである。

Effective Objective-C 2.0

Effective Objective-C 2.0

詳解 Objective-C 2.0 第3版

詳解 Objective-C 2.0 第3版

Swiftとの連携に関しては『Swift実践入門』や『詳解Swift』の古い版が良いと思う。古い版では、Swiftとの連携について章を設けて書かれている。

私は『Swift実践入門』の最初の版と『詳解Swift 第3版』を持っていて、この2冊ではSwiftとの連携について書かれていることを確認している。

詳解Swift 第3版

詳解Swift 第3版

ネット上で章立てを確認した感じでは『Swift実践入門 改訂新版』や『詳解Swift 第4版』でも記述があるように見えるが、実際のところは不明である。

詳解 Swift 第4版

詳解 Swift 第4版

一方で現在出版されている『Swift実践入門 増補改訂第3版』や『詳解Swift 第5版』では、その辺の記述が省かれている。なので、せっかく新しい版を買ったのに、旧版を知人に譲ったりネットで売ったりすることができない。本棚が圧迫されていく……。

実際のところ、2020年にObjective-Cってどうなのよ?

SwiftやKotlinは、型チェックまわりを理解すれば安全なコードが書けるし*2統合開発環境と組み合わせればコーディング時の生産性が高くなるし、言語や標準ライブラリ自体もモダンな機能が取り入れられていて使いやすい。便利な言語なことは間違いない。

最適化についても、Kotlinは分からないが、Swiftは色々と頑張ってくれているようだ。

なのだけど、一定以上のリアルタイム性が求められるケースでは、暗黙のうちにヒープ領域へのメモリアロケートや排他のためのロックが発生することがある、という振る舞いが仇となる。便利さは何かしらの犠牲の下に成り立っているものだ。

そういう時には「C言語におけるインライン・アセンブラ」のように、局所的にC/C++を併用せざるを得ないことがある。

このような場面にて、アプリケーションの大半はSwiftで書きつつ、一部モジュールのみインラインにC/C++のコードを書けるObjective-CObjective-C++で実装したり、C/C++でモジュールを書いた上でObjective-Cでラッピングする――というアプローチは悪くない選択であるし、実際のところそれなりに機能する。

少なくとも今しばらくは、タンスのいちばん下の引き出しにObjective-CObjective-C++がしまわれている日々が続くだろう。捨て去ってしまうのは、まだちょっと早いようだ。

*1:インタフェースはObjective-Cで、中身はObjective-C++で書く。

*2:ふわっと理解する程度なら短時間で済むし、その程度の理解でも効果が得られる。

EdgeRouter Xに組み込む自作ツールを作るなら、どの言語を使うか?

故あってEdgeRouter Xを触っている。

中身のEdgeOSがVyattaベースで、Vyattaの大本はDebianで、さらにコンソールがvbashで割と普通のシェル環境っぽく使えるので*1、自作のコンソールアプリを組み込めそうだと思ったのだ。

というかsshログインのホームディレクトリにbinという名前のディレクトリを作って、中にスクリプト等を置いておくと、次回のログイン以降に環境変数PATHに追加されるよね……。

タスクスケジューラ機能を使って自作スクリプトを定期実行することもできるようだ。

問題は、どの言語なら比較的容易にコンソールアプリを実装できるか、ということだ。

EdgeRouter X上のEdgeOSは32bit MIPS little-endianで動作している。CやC++で実装するとクロスコンパイルが面倒だ。一方でスクリプト言語の類は、限られた空きディスク領域に処理系を導入すると後々面倒なことになりそうだ。

一番手軽なのはシェルスクリプトだろう。POSIXでも定義されているような、一般的なテキスト/ファイル処理用のコマンドは一通り載っているようなので、凝ったことをしなければ大丈夫だろう。

スクリプト言語については、EdgeOS V1.x系にはデフォルトでPerl 5とPython2が入っている。CPANPyPIが組み込まれていないので標準ライブラリ縛りとなりそうだが、Perl 5やPython2でスクリプトを書くこともできそうだ。ただし、スクリプトが実行開始されるまでにほんの少し時間がかかる。普段使いのPCと比較すれば、EdgeRouter XはCPUパワーもディスクI/O速度も劣るだろうから、仕方がない。

ダークホースだったのはGo言語。普通にPC上で開発して「GOOS=linux GOARCH=mipsle」を付与してgo buildして、生成された実行ファイルをEdgeRouter Xに転送すればよい。簡単だ。ただし実行ファイルのバイナリサイズが大きくなるので、若干注意が必要かもしれない。まあそこまで気にすることは無い気もするが……。

起動速度を勘案すると、簡単な処理ならシェルスクリプトで、凝ったことをしたいならGo言語で開発するとよさそうだ。Perl 5やPython2も悪くないだろうが、果たして標準ライブラリ縛りに耐えられるだろうか? あと、Pythonは3系を使いたくなりそう。

*1:ただしUnixコマンドやファイルの入力補完は効かない。管理用コマンドの入力補完が組み込まれているためだろう。

Dockerのコンテナ停止時にバックグラウンド・プロセスとして動作しているデーモンを適切に終了させたい

続き。

前回、下記のような書き方だとコンテナを停止する際に問題を引き起こす可能性がある、と書いた。

FROM amazonlinux:latest

# 色々と準備(省略)

# コンテナ起動時に秘伝のデーモン magic-daemon を起動する。
# その後、コンテナを終了させないようにする。
CMD /etc/init.d/magic-daemon start; tail -f /dev/null

docker stopなどでコンテナを停止させる時、コンテナ内のPID=1のプロセスにSIGTERMが送信されるのだが、PID=1はCMDの内容を実行している/bin/sh -cだ。

つまり、バックグラウンド・プロセスとして動作しているデーモンそのものにはSIGTERMが届かない。デーモンは依然として動作したままとなり、一定時間後(既定では10秒後)にSIGKILLが送信されて、強制的に終了させられる。

待ち時間はdocker stopのオプション-tで変更できるものの、ついうっかりオプションを忘れて10秒待つのはイライラの元であるし、何より「本来意図されているだろう終了処理が行われない」というのも気持ち悪い。「気持ち悪い」だけで済めば良いのだが、例えば複数同時起動を回避するためにロック・ファイルを作成するシステムでは、強制終了によりロック・ファイルが残されたままとなり、次回以降のコンテナ起動時にデーモン起動に失敗する――といった具合に、何かしらの問題が引き起こされるものである。

この問題に対応するには、以下の内容を実行するラッパーのシェルスクリプトか何かを用意しておき、CMDにてexec形式で当該スクリプトを呼び出せば良い。

  1. デーモン起動のコマンドを実行する。
  2. SIGTERMを受信するまで無限待ちする。
  3. SIGTERMをトラップして、デーモン終了のコマンドを実行する。

例えば次のような内容のスクリプトdaemon-runner.shを用意しておく。

#!/bin/sh
# -*- coding: utf-8-unix -*-
# vim:fileencoding=utf-8:ff=unix
#
# daemon-runner.sh
# 秘伝のデーモンの起動/終了を制御するスクリプト。

trap '/etc/init.d/magic-daemon stop; exit 0' TERM

/etc/init.d/magic-daemon start

# SIGTERM が届くまで待ち続ける。
# trap(1p) で指定したコマンドが即座に実行されるようにするため、
# バックグラウンドで sleep(1) させてフォアグラウンドで wait(1p) する。
while : ; do sleep 86400 & wait $!; done

このスクリプトをコンテナにコピーして、CMDで実行するようにしておく。

FROM amazonlinux:latest

# 色々と準備(省略)

# ラッパースクリプトをコンテナにコピーして、実行権限を付与する。
# いずれ COPY のオプションで --chmod=0755 みたく書けるようになるはず。
COPY ./daemon-runner.sh /usr/local/sbin/
RUN chmod 755 /usr/local/sbin/daemon-runner.sh

# コンテナ起動時に秘伝のデーモンを起動する。
CMD ["/usr/local/sbin/daemon-runner.sh"]

こうしておけば、コンテナ起動時にデーモンを起動した後は無限待ちとなるのでプロセスが生存し続ける。またexec形式で記述したことでPID=1のプロセスとして動作するので、コンテナ停止時にSIGTERMが届くから、trap(1p)でシグナルをフックしてデーモン終了のコマンドを実行することが可能となる。

蛇足

なお、秘伝のデーモンがシグナル受信だけで適切に終了する(つまり本稿で言うなら「/etc/init.dスクリプトで停止」しなくても問題ない)場合、もしかしたら冒頭に挙げたCMD /etc/init.d/magic-daemon start; tail -f /dev/nullのような書き方をした上で、docker runの時にオプション--initを付与するだけで事足りるかもしれないが、あまり試していないので真偽は不明である。

バックグラウンド・プロセスが/bin/sh -cの子プロセスの場合には効果があるようだが、deamon(8)などを使ってバックグラウンド化したプロセスの場合にどうなるのだろうか?