【雑記】javaで定数を比較する時の違和感
javaで定数を比較する時によく、
if(CONSTANT.equals(value)){ System.out.print("hoge"); }
という書き方をします。
※CONSTANT
は定数でvalue
はチェック対象の変数だと思ってください。
これあんまり好きじゃないんですよね。
理由はやりたいこととやっている事(前から読んだ時の意味)が逆になるからです。
上記の例だと、
□やりたいこと: value
がCONSTANT
と等しい時、hogeを出力
□やっている事: CONSTANT
がvalue
と等しい時、hogeを出力
いやまぁやっていることはどちらも同じなんですが、やりたいことをそのまま表現するには
if(value.equals(CONSTANT)){ System.out.print("hoge"); }
となっていた方が単純にいいですよね。。
最近は読みやすいプログラムを書くことが重要になってきているので、正直この辺は気持ち悪いですね。
ちなみに定数を前に持ってくるのは、value
がnullだったときのNullPointerExceptionを避けるためです。
【失敗談】Gitのコマンドとオプションを補完するコンソールアプリケーションを作ろうとした話
- 前置き
- 何がしたかったか
- コマンド、オプション、synopsisのスクレイプ
- スクレイプで躓いたこと
- ともあれスクレイプはひとまず完了
- スクレイプしたものを解析する。
- sinopsis解析のパターン
- そもそも前提が間違っていた
- 結論
前置き
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のように略語ではない)なので、コマンド名、オプション名から何がしたいかを予測可能なのです。
例えば、PowershellのGet-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のブレイクポイントウィンドウからインポートして下さい。
読んでいただきありがとうございました。
階層が深いディレクトリから同名ファイルを探し出して上書きする
前置き
仕事中にタイトルのようなことがしたいことがありました。
例えば、普段の作業はローカルで行い、ファイルサーバにコピーするときに、エクスプローラでいちいちファイルの場所まで開く必要があります。
別に大した手間ではないのですが、よく考えると面倒だし、なんでファイルのパスまで覚えとかないといけないんだろう。。。と思ってしまったので、それ用の関数を用意することにしました。
スクリプト記述
注意 ここではローカルパスからローカルパスのコピーを行います。前置きでファイルサーバへのコピーと書きましたが、一般的にはそういう使い方の方が多いのかな?と思ってそう書きました。私の実際の用途としてはデプロイ資材の差し替えとかです。
早速Powershellで書いていきます、
function Global:Copy-FileToSameName { param( [Parameter(Mandatory, HelpMessage="送り側")] [string]$source, [Parameter(Mandatory,HelpMessage="受け側")] [string]$destination ) # is Exist ? $_source = [System.IO.FileInfo]::new("$(Convert-Path -Path $source)") $_destination = [System.IO.DirectoryInfo]::new("$(Convert-Path -Path $destination)") $file_list = [System.IO.Directory]::GetFiles( $_destination.FullName.Split("`""), $_source.Name.Split("`""), [System.IO.SearchOption]::AllDirectories) foreach ($item in $file_list) { if ($item -ne $_source.FullName.Split("`"")) { Copy-Item -Path $_source.FullName.Split("`"") -Destination $item } } }
こんな感じでしょうか。ちなみに引数でFileInfoやDirectoyrInfo型ではなくstring型で受けたのは理由があります。
それはまた今度記事にしようと思います。
動作確認
- コピー元ファイルが存在するディレクトリで、そのディレクトリの配下にある別の同名ファイルに上書き
- コピー元ファイルが存在しないディレクトリで、そのディレクトリの配下にある別の同名ファイルに上書き
- コピー先が複数存在する場合
こんな感じでしょうか。
ではまず準備です。以下のような構造でディレクトリを用意します。
C:\DEVELOP\TMP_SCRIPT\SCRIPT\1\TEST ├─1 │ │ file1.txt │ │ │ └─parent │ ├─child │ │ file1.txt │ │ file2.txt │ │ │ └─child2 │ file3.txt │ file4.txt │ ├─2 │ └─parent │ ├─child │ │ file1.txt │ │ file2.txt │ │ │ └─child2 │ file3.txt │ file4.txt │ └─3 └─parent ├─child │ file1.txt │ file2.txt │ └─child2 file1.txt file4.txt
ファイルの更新確認は、gitで差分確認しましょう。
現在の状態です。差分はないです。
実行してみます。
Copy-FileToSameName -source .\test\1\file1.txt -destination .\test\1\ Copy-FileToSameName -source .\file1.txt -destination .\test\2\ Copy-FileToSameName -source .\file1.txt -destination .\test\3\
大丈夫そうですね。 ファイルの中身も確認して正常にコピーされていました。(画像だらけになるので張りません、、)
今回のスクリプト等は、tmp_scriptに置いてあります。
読んでいただきありがとうございました。
【Powershell】任意のアプリのウィンドウサイズ等を指定して起動する
前置き
GUIアプリケーションを実行すると、基本的には最後に閉じた状態で開きます。
ニッチな需要だと思いますが、例えばGUIアプリを利用したシェルスクリプトではウィンドウを隠したり、特定の条件のときだけアプリをフルサイズで開きたい、ということがあるかもしれません。
(ちなみに私は仕事中にそういうことがありました。)
そこで、その方法について記載します。
スクリプト記述
※注意
コンソールのコマンド実行を説明するときは、習慣的に$
がつけられることが多いです。(例えば$dir
と書かれていたらコンソールでdir
と打つ)
powershellでは変数を定義するときに、$var
のように書くとvar
という名前の変数を定義できます。
なので、ここで紹介するスクリプトは$
を含めて実行してください。
それでは、いきなりですがシェルはこちら
$p =New-Object -TypeName System.Diagnostics.ProcessStartInfo $p.FileName ="{プログラム名}" $p.WindowStyle =[System.Diagnostics.ProcessWindowStyle]::{設定値} [System.Diagnostics.Process]::Start($p)
シェル自体の説明は割愛します。
{プログラム名}には任意のファイルを指定できます。
例えば$env:LOCALAPPDATA\Programs\Microsoft VS Code\bin\code
や、パスが通っていればnotepad
のように名前だけの指定もできます。
また、hoge.txt
のように実行ファイル以外を指定した場合は、その拡張子に紐づいたアプリが実行されます。(.txt
だとnotepad.exe等){設定値}には
System.Diagnostics.ProcessWindowStyle
型の値を指定できます。
具体的には以下のいずれかを指定できます。
Hidden | Maximized | Minimized | Normal |
---|---|---|---|
非表示 | 最大化 | 最小化 | 通常(最後に閉じたときの状態) |
動作確認
試しにメモ帳をサイズ最大で起動してみましょう
PS C:\> $p =New-Object -TypeName System.Diagnostics.ProcessStartInfo >> $p.FileName ="notepad" >> $p.WindowStyle =[System.Diagnostics.ProcessWindowStyle]::Maximized >> [System.Diagnostics.Process]::Start($p) Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName ------- ------ ----- ----- ------ -- -- ----------- 28 5 1032 2188 0.02 1132 1 notepad
大丈夫そうですね。
読んでいただきありがとうございました。
Windowsの環境変数「PATH」
初めに
PATHについての説明はいくらでも転がっているのですが、(いいことですが)丁寧な説明で長い記事になっているものが多く、読むのが面倒!という人のために「何ができるか」「なぜ必要なのか」を簡潔にまとめます。
それではどうぞ
PATHを通すことでできること
- プログラムを名前だけでどこからでも実行することができる
例:$notepad
-> メモ帳()が起動します
Pathが通ったプログラムを実行するときの内部的な動き
説明
ファイル名だけをコンソールから実行した場合、
登録してあるPATHの直下にある、拡張子にPATHEXTを持つファイルを実行する
という動きになります。 また、PATHやPATHEXTは登録されている順にファイル検索が行われ、最初に一致したものを実行します。(※後で補足します)
確認
試しに、メモ帳(notepad.exe)を起動します。
$notepad
はい、起動しますね。
ちなみにnotepad.exeは二種類の実行ファイルがあります。(私の環境だけ。。?)
$where notepad C:\Windows\System32\notepad.exe C:\Windows\notepad.exe
$where
は検索パターンに一致するファイルの場所を表示するコマンドです。
既定では、現在のディレクトリおよび PATH 環境変数によって指定されたパス内を検索します。
先ほど実行したnotepad.exeはC:\Windows\System32\notepad.exe
の方ですね。
C:\Windows\notepad.exe
を実行するには、フルパスで指定してやるか、C:\Windows\
の直下で$notepad
を実行する必要があります。
そもそもなぜPathが必要なのか?
一つは、冒頭でも書いた通り「ファイル名だけでどこからでも実行できると便利」ということが挙げられます。
そのほかの理由として、考えられるのは、アプリケーションの開発者側が、ユーザのコンピュータのアプリケーションを実行する際にアプリケーションがどこにあるのかを意識しなくてよい、というのもあるような気がしますね。
余談
「コマンドプロンプトから実行できるコマンドは何があるんだろう?」
そんな時に自分の環境でファイル名のみで利用可能なコマンドの一覧を確認したければ、powershellで
foreach ($a in ($env:PATHEXT).Split(";")) {Start-Process -FilePath "cmd" -ArgumentList " /c where *$a >> %userprofile%\Downloads\command.txt" -Wait }
を実行すると、%userprofile%\Downloads\command.txt
に使用可能なコマンドの一覧が出力されます。
※正確には、上記で出力できるのは外部コマンドと呼ばれる類のものです。cmd.exeには組み込みコマンドというものもあり、組み込みコマンドはコマンドプロンプトでhelp
と打てば全量が表示されます。
Gitでコマンドにalias(=別名)を設定し、爆速でコマンドをたたく
前提
gitについてある程度知っている前提です。 gitとはそもそも何か?ということについてはこちらをご覧ください
なぜalias(=別名)が必要か
例えば、 コンソールでgitを使用している人が一番使うであろうコマンドのこちら
$git status
リポジトリの状態を確認するコマンドですが、ほんとによく使います、、
$git add
から$git push
までの間に3,4回使ったりすることも稀によくあると思います。
使用頻度が高いくせして、'status'と6文字もタイピングする必要があるのは、なかなかめんどくさい。
そこで、別名を付けることで、タイピング量を減らしてやろう!というのが目的です。
それでは見ていきましょう。
aliasの設定
※ 前提(補足)
gitの設定にはスコープ(=設定の有効範囲)があり、次の3つに分かれます。
スコープについての詳しい説明はこちらが大変参考になります。
system | global | local |
---|---|---|
システム単位 | ユーザ単位 | リポジトリ単位 |
今回はglobalスコープにaliasを設定します。
alias設定
ここでは私も使用している設定を例として挙げます。
ちなみにタイトルでは「コマンドにaliasをつける」と言っていますが、実際はコマンド以外にもつけられます。
$git config --global alias.a "add" $git config --global alias.b "branch" $git config --global alias.c "checkout" $git config --global alias.ch "cherry-pick" $git config --global alias.d "diff" $git config --global alias.l "log --graph --all --format='%%x09%%C(cyan bold)%%an%%Creset%%x09%%C(yellow)%%h%%Creset %%C(magenta reverse)%%d%%Creset%%s' --branches" $git config --global alias.rmh "reset --mixed head" $git config --global alias.rhh "reset --hard head" $git config --global alias.s "status"
例だと、status
にはs
というaliasを付けています。
設定は%userprofile%\.gitconfig
に下記のように反映されます。
[alias] a = add b = branch c = checkout ch = cherry-pick d = diff l = log --graph --all --format='%x09%C(cyan bold)%an%Creset%x09%C(yellow)%h%Creset %C(magenta reverse)%d%Creset %s' --branches rmh = reset --mixed head rhh = reset --hard head s = status
動作確認
gitリポジトリで、$git status
と$git s
を試してみます。
同じ結果が得られればOKです。
$git status On branch temp Your branch is up to date with 'origin/temp'. nothing to commit, working tree clean
$git s On branch temp Your branch is up to date with 'origin/temp'. nothing to commit, working tree clean
大丈夫そうですね。
お読みいただきありがとうございました。