技術メモ、Tips、忘備録

技術系のメモ、試してみたこと、その他

【Python】「間違えようのないやり方が、一つだけある」という考え方

先日、 言語設計者たちが考えること (THEORY/IN/PRACTICE) | Federico Biancuzzi, Shane Warden, 伊藤 真浩, 頃末 和義, 佐藤 嘉一, 鈴木 幸敏, 村上 雅章 |本 | 通販 | Amazonという本を読みました。

数多く存在するプログラミング言語、その言語の設計者たちが何を考え、どんな目標をもって取り組んでいるのかということがインタビュー形式で読める書籍となっています。
この本の中で、Pythonの生みの親であるGuido van Rossumu氏の「間違えようのないやり方が、一つだけある」という考え方が非常に良いものだと思ったので記事に残しておきたいと思いました。

私自身Pythonはやったことがないのですが、Pythonの設計として上記の考え方は非常に重要視されています。
プログラムって書ければいいというだけにはいきません。むしろ他人が書いたものを読むことの方が圧倒的に多いです。
そこで目的を達成するための方法が一つに絞られる、というこの考え方は、読む側にとって非常にありがたいものです。

言語レベルで手法が標準化されているのはありがたいですね~
そのうちPython触ってみたいですね

【WSL2】wsl2のインストールで0x80370102 エラーが出たときの対処方法【HP】

Windows 10でwsl2をインストールするときに一発でうまくいかなかった部分の対応をメモしときます。
以下の手順で進めました。
Install Windows Subsystem for Linux (WSL) on Windows 10 | Microsoft Docs

MS StoreでUbuntuをインストールし起動すると、以下のエラーが起こりました。

WslRegisterDistribution failed with error: 0x80370102 

手順にもある通り、コンピューターのBIOS内部で仮想化が有効化されていないことが原因のようです。
私の場合はHPのPCなので、F10でBiosを起動し、System ConfigurationVirtualization TechnologyEnableにすればうまくいきました。

Powershellで引数をMandatory(必須)にするときに地味にハマったこと

事象

Powershellでスペースありの文字列型の引数を受け取るとき、受け取った値にダブルクォーテーションがついてたりついてなかったりするので、それについて。

以下のようにparamを使って引数を受け取る自作関数があったとします。

function Get-Hoge{
    param(
        [Parameter(Mandatory)]
        [string]$arg
    )

Write-Host $arg
}

で、関数を呼ぶ時は以下の2つの呼び出し方があります。 渡すのはスペースを含む文字列とします。
パターン1

PS C:\Users\nogam> Get-Hoge -arg "has space"
has space

これは問題なさそうですね。
パターン2(Mandatoryにより入力待ちから入力する場合)

PS C:\Users\nogam> Get-Hoge
コマンド パイプライン位置 1 のコマンドレット Get-Hoge
次のパラメーターに値を指定してください:
arg: "has space"
"has space"

のほうは、ダブルクォーテーション付きで渡されます。
"has space"ではなくhas spaceとだけ入力すれば、ふつうにhas spaceが渡されます。

これを回避するためには、以下のようにTrimすればよいです。

Write-Host $arg.Trim("`"")

何がハマったのか

ここからはPowershellの仕様とは関係ない運用的な話になります。
例えば、上記の関数が以下のようにファイルパスを受け取るような関数だった場合を考えます。

function Open-HogeFile{
    param(
        [Parameter(Mandatory)]
        [string]$file_path
    )
    $file = [System.IO.File]::Open($file_path, [System.IO.FileMode]::Open)
    $file
    $file.Close()
}

パターン1の場合

PS C:\Users\nogam> Open-HogeFile -file_path "C:\Users\nogam\Downloads\hoge.txt"

CanRead        : True
CanWrite       : True
CanSeek        : True
IsAsync        : False
Length         : 11
Name           : C:\Users\nogam\Downloads\hoge.txt
Position       : 0
Handle         : 4228
SafeFileHandle : Microsoft.Win32.SafeHandles.SafeFileHandle
CanTimeout     : False
ReadTimeout    : 
WriteTimeout   : 

問題なさそうです。しかしパターン2だと

PS C:\Users\nogam> Open-HogeFile
コマンド パイプライン位置 1 のコマンドレット Open-HogeFile
次のパラメーターに値を指定してください:
file_path: "C:\Users\nogam\Downloads\hoge.txt"
"2" 個の引数を指定して "Open" を呼び出し中に例外が発生しました: "パスに無効な文字が含まれています。"
発生場所 C:\Users\nogam\Downloads\test.ps1:6 文字:5
+     $file = [System.IO.File]::Open($file_path, [System.IO.FileMode]:: ...
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ArgumentException
 
null 値の式ではメソッドを呼び出せません。
発生場所 C:\Users\nogam\Downloads\test.ps1:8 文字:5
+     $file.Close()
+     ~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) []、RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull
 

当然ですが失敗します。

ところで、「この引数の渡し方は失敗するのがわかっているのに、なぜそんな渡し方をするの?」と思いますよね。
それは、この関数の運用を考えると見えてきます。この関数の利用者は、
1.エクスプローラーからファイルを右クリック⇒パスをコピー
2.関数を実行するときに、コピーしたパスをペースト
という手順を踏むことが多くなるはずです。
上記の1.でコピーしたパスはダブルクォーテーション付きでコピーされます。
また、この関数を利用するのがPowershellをあまり利用しない人だった場合、関数実行時に指定する引数と対話型プロンプトから指定する引数で渡し方が違うとは想像しにくいです
なので、引数はTrimして受けるのがいい気がしますね。

ここまでお読みいただきありがとうございます。

【java, C#】windowsの環境変数を取得する。

はじめに

自分がよく使う言語でWindows環境変数UserProfileを取得してみる。

java

String userProfile = System.getenv("UserProfile");

c sharp

string userProfile = Environment.GetEnvironmentVariable("UserProfile");

【雑記】javaで定数を比較する時の違和感

javaで定数を比較する時によく、

if(CONSTANT.equals(value)){
    System.out.print("hoge");
}

という書き方をします。
CONSTANTは定数でvalueはチェック対象の変数だと思ってください。

これあんまり好きじゃないんですよね。
理由はやりたいこととやっている事(前から読んだ時の意味)が逆になるからです。
上記の例だと、
□やりたいこと: valueCONSTANTと等しい時、hogeを出力

□やっている事: CONSTANTvalueと等しい時、hogeを出力

いやまぁやっていることはどちらも同じなんですが、やりたいことをそのまま表現するには

if(value.equals(CONSTANT)){
    System.out.print("hoge");
}

となっていた方が単純にいいですよね。。
最近は読みやすいプログラムを書くことが重要になってきているので、正直この辺は気持ち悪いですね。

ちなみに定数を前に持ってくるのは、valueがnullだったときのNullPointerExceptionを避けるためです。

【失敗談】Gitのコマンドとオプションを補完するコンソールアプリケーションを作ろうとした話

前置き

gitのコマンドとオプションを補完するコンソールアプリケーションを作ろうとしていましたが、失敗しました。
何がだめだったか、どうすべきだったかを自戒も込めて記事にします。

何がしたかったか

作りたかったものはタイトルの通りです。
手順としては、
1. gitの公式サイトのコマンド一覧からコマンドとオプションをスクレイピングする
2. 1でスクレイピングしたものをもとに、補完するための仕組みを作成する。
です。

コマンド、オプション、synopsisのスクレイプ

まず、スクレイプするにあたり以下のことを考えなければいけません。
1. 対象のコマンドは?
2. スクレイプのルールは?

1については、command_list.txt公式サイトの一覧をベースに、いらないもの(コマンドではないもの等)を除外してスクレイプ対象のコマンドを決めました。

2について、それぞれ以下のように決めました。
■ URL & command
https://git-scm.com/docs/git-{command}」という形式でコマンドごとのリファレンスがあります。
例えばaddコマンドだとhttps://git-scm.com/docs/git-addといった感じです。

■ synopsis
ざっくりいうと、コマンドごとのリファレンスのHTMLを見ると、シノプシス_synopsisというidに属しています。

■ options
ざっくりいうと、コマンドごとのリファレンスのHTMLを見ると、オプションはすべて_optionsというidとhdlist1というクラスに属していることがわかります。

ここまでそろえば簡単ですね。...と思っていましたが実は少しめんどくさかったです。

スクレイプで躓いたこと

結論から言うと、スクレイプのルールの前提が甘かったです。甘かったといってもほとんどはルール通りスクレイプできましたが、一部のドキュメントがルールに沿わない構造となっていたため、個別ルールを追加してスクレイプしました。
そもそもの目標の1つとして「gitのアップデートに柔軟に対応する」というのがありました。
しかし、上記のような「例外(ルールを外れたスクレイプ)」を許してしまうと、gitのアップデートのたびにスクレイプ用のアプリを改修しなければいけない可能性があるので、あまりよくない状況になりました。

ともあれスクレイプはひとまず完了

自分のプロジェクトに実際にスクレイプしたものがあります。ご参考までに

スクレイプしたものを解析する。

上のセクションで、synopsisもスクレイプしました。このセクションで使うためです。
まずはそれぞれのオプションがどのように使われるかを知る必要があります。そこで、synopsisを解析し、オプション一つ一つにメタデータ(値を持つか、他のオプションと同時に指定できるか、等)を付けようと考えました。

sinopsis解析のパターン

まずはsynopsisの記法についてです。
調べてみましたが、あまり参考になるものを見つけられませんでした。正規表現の概念に依存するというようなことはあったのですが.....

まぁでも、何となくsynopsisの記法は直感的に理解していたので、適当にやってみるか!となりました。

...が、これが思ったより難しい。当たり前ですが、人が見れば普通に分かることでも、機械的に解析しようと思うと大変です。解析するにあたり、いろいろ考えなければいけませんでした。最も頭を悩まされたことは「そもそもコマンドによってsynopsisの書き方のクセが異なる」ということでした。
またスクレイプした時のように個別ルールが必要になるのでは…
まさにそうでした。1つの共通ルールでの解析は(私の能力では)無理そうでした。

だんだんと当初の考えとずれてきたので、1度原点回帰することに。

そもそも前提が間違っていた

当時(今もですが)、Powershellの補完機能に魅了されてました。従来のshellは「ヘルプを見て、すべてタイピングする」というのが基本ですが、Powershellは「途中までタイプすれば保管してくれる」ので初めてでも割と感覚だけで使えました。また特性としてコマンドが「動詞-名詞」の形式かつ、オプションも正式な英語(従来のshellのように略語ではない)なので、コマンド名、オプション名から何がしたいかを予測可能なのです。
例えば、PowershellGet-ChildItem - Fileとcmd.exeのdir /a-dは同じ挙動をします。Powershell、cmd.exeを知らない人(例えば開発者ではない事務の人とか)が上記を見た場合、前者は「あぁ、なんか子アイテムのファイル取ろうとしてるわ」と容易に予想できますが、後者は予測不可能でしょう。

従来のshellの場合、その特性から「コマンド名、オプション名は短く、タイピングしやすいこと」が重要な要素だったと考えます。現にgitも主要なオプションに短い形式が用意されていました。 一方でPowershellは「わかりやすさ」に重きを置いている気がします。なので、逆に普通にシェルとしてプロンプトから利用するのは少し面倒くささもあります。(エイリアス使えば別ですが)

上記を踏まえて、「もともとタイピングを減らすために設計されているので初見では分かりにくいものを補完したところで、補完されたものの意味を予測できないので結局ヘルプ見るしかない」ことに考えが及ばなかったことが敗因だと思いました。

また、「GitのHelpページはひな形こそあれど、別に厳密なルールで構造化されているわけではない」、「synopsisも別に明確なルールがあるわけではなく、人にわかりやすい記法(≒機械にはわかりにくい)である」ということに気づけなかったのも良くなかったと思います。たしかにgit開発のチュートリアルとか見てもそんな感じですね。。

結局、ここで考えるのをやめて、アプリ作りも終わりました。 

結論

今回のことで、
■ 公式ドキュメントを読め。
■ 根拠のない決めつけで物事を進めるな

ということを痛感させられました。ただ、経験としては良かったです。

ここまで読んでいただきありがとうございました。

Powershell Coreをビルドしてデバッグする。

前置き

最近Powershellを使うことが多かったのですが、なかなか使いこなすのが難しいので、内部仕様を見て理解を深めようと思ったのでやってみます。
Powershell CoreはWindows Powershellコードベースにフォークされたプロジェクトです。
ビルドはwindows-core.mdにそってやります。

環境

Windows 10 (1909)
Visual Studio Community 2019 (16.7.2)

ビルド

visual studioの設定

[ツール] -> [ツールと機能を取得] -> [ワークロード]
[.NET デスクトップ開発]と[.NET Coreクロスプラットフォームの開発]にチェックしてインストールする。

リポジトリのクローン

今回は現在の最新のタグ(v7.1.0-preview.6)でやろうと思います。
(各種タグについての説明はここで確認できます)

$git clone https://github.com/PowerShell/PowerShell.git
$git checkout v7.1.0-preview.6
■.NET Core SDKのインストール(またはglobal.jsonの書き換え)

現在.NET SDK 5.0以降をインストールしていない人はこのセクションの手順は実施不要です。
後述する「Powershell Coreのビルド」で自動的に.NET SDKがインストールされます。
.NET Core SDKをすでにインストール済みの人は、この手順が必要です。

  • インストールされている.NET SDKのバージョン確認
$dotnet --list-sdks
3.1.301 [C:\Program Files\dotnet\sdk]
3.1.401 [C:\Program Files\dotnet\sdk]
5.0.100-preview.7.20366.6 [C:\Program Files\dotnet\sdk]

自分の場合は5.0.100-preview.7.20366.6ですね。

  • \global.jsonを書き換え
    cloneしたPowershell直下の.\global.jsonを下記のように書き換えます。
$type global.json
{
  "sdk": {
    "version": "5.0.100-preview.7.20366.6"
  }
}
Powershell Coreのビルド

cloneしたPowershell直下で実行してください。 Powershellまたはpwshで実行してください。

Powershellのプロンプトってどう表現すればいいか分からなかったので、ここではPS>としています 。

以下のコマンドで.NET SDKをインストールします。すでにインストール済みの場合はdotnet is already installed. Skipping installation.のようなメッセージが出ます。

PS>Import-Module .\build.psm1
PS>Start-PSBootstrap

以下のコマンドでPowershell Coreをビルドします。

Start-PSBuild

正常終了されればOKです。
実行ファイルへのパスは下記のコマンドで確認できます。

PS>Get-PSOutput
C:\develop\global_reps\PowerShell\src\powershell-win-core\bin\Debug\net5.0\win7-x64\publish\pwsh.exe

Visul Studioでデバッグ

cloneしたPowershell直下の.\PowerShell.slnを開きます。 f5キーで実行します。
プロンプトが表示されるので、試しにGet-ChildItemコマンドレットをたたいてみます。

PS C:\develop\global_reps\PowerShell\src\powershell-win-core\bin\Debug\net5.0> Get-ChildItem

    Directory: C:\develop\global_reps\PowerShell\src\powershell-win-core\bin\Debug\net5.0

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----          2020/08/22    10:07                cs
d----          2020/08/22    10:07                de
d----          2020/08/22    10:07                en-US
d----          2020/08/22    10:07                es
d----          2020/08/22    10:07                fr
d----          2020/08/22    10:07                it
d----          2020/08/22    10:07                ja
d----          2020/08/22    10:07                ko
d----          2020/08/22    10:07                Modules
d----          2020/08/22    10:07                pl
d----          2020/08/22    10:07                preview
d----          2020/08/22    10:07                pt-BR
d----          2020/08/22    10:07                ru
d----          2020/08/22    10:07                runtimes
d----          2020/08/22    10:07                Schemas
d----          2020/08/22    10:07                tr
d----          2020/08/21    16:19                win7-x64
d----          2020/08/22    10:07                zh-Hans
d----          2020/08/22    10:07                zh-Hant
-a---          2020/08/21    16:13           8933 Install-PowerShellRemoting.ps1
-a---          2020/08/21    16:13           2908 InstallPSCorePolicyDefinitions.ps1
-a---          2020/08/21    16:13           1095 LICENSE.txt
-a---          2020/04/18    16:34         403456 Markdig.Signed.dll
-a---          2020/04/22    16:37         350072 Microsoft.ApplicationInsights.dll
-a---          2020/07/25     4:15        5437320 Microsoft.CodeAnalysis.CSharp.dll
.......
PS C:\develop\global_reps\PowerShell\src\powershell-win-core\bin\Debug\net5.0>

大丈夫そうですね。

コマンドの実行エントリ

.\src\Microsoft.PowerShell.ConsoleHost\host\msh\ConsoleHost.csの2583行目当たりの
_exec.ExecuteCommand(line, out e, Executor.ExecutionOptions.AddOutputter | Executor.ExecutionOptions.AddToHistory);
からコマンドを実行します。引数のlineには(よく確認してないけどたぶん、、)プロンプトからの入力がそのまま入る感じですね。

ブレイクポイント

プロンプトの実行、コマンドの実行等の主要なポイントでブレイクポイントを用意しました。
ここからダウンロードしてVisual Studioのブレイクポイントウィンドウからインポートして下さい。

読んでいただきありがとうございました。

・免責事項

当方は、当記事にコンテンツを掲載するにあたって、その内容、機能等について細心の注意を払っておりますが、コンテンツの内容が正確であるかどうか、最新のものであるかどうか、安全なものであるか等について保証をするものではなく、何らの責任を負うものではありません。また、当方は通知することなく当記事に掲載した情報の訂正、修正、追加、中断、削除等をいつでも行うことができるものとします。また、当記事、またはコンテンツのご利用により、万一、ご利用者様に何らかの不都合や損害が発生したとしても、当方は何らの責任を負うものではありません。