2014年02月10日

PowerShell でコマンドレットの履歴を保存する (TB: このコマンドレット、この前実行した、ような・・・・・・)

どうも、PowerShellと出会ってからというもの、オブジェクト指向型でないコマンドラインシェルが時代遅れに見えて仕方ない天井冴太です。

Zshだと、ターミナルを開き直しても以前やったコマンドの履歴が参照出来て地味に便利。確かBashもそうだった気がする。PowerShellでは出来ないのか?とググって、以下の記事に辿り着いた。

PowerShell - このコマンドレット、この前実行した、ような・・・・・・ - Qiita

ふむふむ成る程。prompt関数内でGet-Historyした結果をExport-Csvして、それをImport-Csvするコードを自分のプロファイルのファイルに書き込めば可能と。

で、上記記事中のコードには、記事自体でも明記されているが、幾つかの問題点が残されている。

上2つは解決出来そうだ。3つ目もある程度は緩和出来そう。という訳でチャレンジ……出来た!

$historyFilePath = "~/.posh_history.csv"

Import-Csv $historyFilePath | Add-History

function prompt {
	$latestHistory = Get-History -Count 1
	if($script:lastHistory -ne $latestHistory) {
		$csv = ConvertTo-Csv $latestHistory

		if( -not(Test-Path $historyFilePath)) {
			Out-File $historyFilePath -InputObject $csv[0] -Encoding UTF8
			Out-File $historyFilePath -InputObject $csv[1] -Encoding UTF8 -Append
		}
		Out-File $historyFilePath -InputObject $csv[-1] -Encoding UTF8 -Append

		$script:lastHistory = $latestHistory
	}

	return "$pwd >"
}
  • 入力無しEnterで重複する履歴が保存されるのは、その時には履歴情報の更新が行われないから。毎回、直前の履歴を$script:lastHistoryに格納しておき、それと現在の最新の履歴を比較する事で、履歴の更新の有無を判断している。
  • 元のコードでは、追記するレコードをマニュアルで構築してる。故にエスケープ処理の抜けが発生しているのだが、CVSへの変換だけであればConvertTo-Csvが使える。ConvertTo-Csvの結果の最終行を取り出せば、それが求めるレコードの内容となる。
  • 同様に、元のコードではマニュアルで構築しているCSVファイルのヘッダ(シリアライズした型の名前、各フィールドに対応するメンバの名前)2行も、ConvertTo-Csvの結果の先頭2行が使える。

後は、現状prompt関数に直書きしているのを別の関数に独立させれば、なおシンプルになるだろう。

しかし、そもそも履歴のファイルへの保存が追記でないといけないのかは疑問。PowerShellで記憶出来る履歴の最大件数は$MaximumHistoryCountに設定された値(デフォルトは64)で、それを超えると古いものから捨てられる。ファイルに保存されている履歴の件数が$MaximumHistoryCount以下であれば問題ないが、それを超えると、古い情報は無意味になり、しかもファイルは際限なく太っていく。情報を追加するだけのAdd-Historyではなく、実行も行うInvoke-Historyを使うというならば話は別だが、毎回上書きした方が良いのではないかと思う。エクスポートの為のコードも、「Get-History | Export-Csv -Path $historyFilePath」だけで済むしね。

オブジェクト指向型でないコマンドラインシェルが時代遅れに見えて仕方ない
えーマジテキスト出力型コマンドラインシェルー? テキスト出力型コマンドラインシェルが許されるのは2009年までだよねー!
Bashもそうだった気がする
Bash使ってたの昔過ぎて憶えてない。
Export-Csv
勿論Export-Clixmlでも良い筈である。以降のCVS読み書き周りについても同じ事が言えるが、特に断りは入れない。
exit以外の方法でPowerShellを終了させると、直前の履歴が保存されない。
これは、どういう意味かよく分かってない。
ConvertTo-Csv

CSVに限った話ではないが、ConvertTo-Csvが在るのに何故Export-Csvなんて物があるんだろう。要はExport-Csvの内容をファイルにリダイレクトすればいい訳で、専用のコマンドレットが必要とは思えない。

(そういう意味では、Out-Fileの存在も謎だ。)

ラベル:PowerShell CLI CUI
posted by 天井冴太 at 20:47| Comment(6) | TrackBack(0) | Other | 更新情報をチェックする
この記事へのコメント
> ConvertTo-Csv

ConvertTo-Csv って、2.0 になかったような気がしたのですがありましたっけ?
今、ググってみても情報が見つからない上に、手持ちのものはバージョンが上がってしまっていて、調べられないです。


> CSVに限った話ではないが、ConvertTo-Csvが在るのに何故Export-Csvなんて物があるんだろう。
> 要はExport-Csvの内容をファイルにリダイレクトすればいい訳で、
> 専用のコマンドレットが必要とは思えない。
> (そういう意味では、Out-Fileの存在も謎だ。)

このあたりは、あまり詳しく把握できていませんが、歴史的経緯で結構しょぼいコマンドレットが残っていたりしますよね。
Out-File は文字コードとかもあるので、必要な気がします。
それと、PowerShell のコマンドレット名とかもそうですが、Linux とかでよくあるような
黒魔術的なワンライナーみたいなのにならないように、コマンドレットで統一して、
英語っぽくすることで、可読性を上げようとしているような気がします。


> PowerShellで記憶出来る履歴の最大件数は$MaximumHistoryCount
> に設定された値(デフォルトは64)で、それを超えると古いものから捨てられる。
> ファイルに保存されている履歴の件数が$MaximumHistoryCount以下であれば問題ないが、
> それを超えると、古い情報は無意味

まず書いていませんでしたが、 $MaximumHistoryCount = 2000 しているので、最大件数は、
あんまり気にならないです。zsh でも 10000件保存できるようにしてる人とか結構いますよね。

また、プロンプトで毎回実行して、1行ずつ保存すれば、最大件数を超えても保存できます。
それを残しておくと、#foobar とやると補完できて便利だったりします。
# でうまく補完できなかったとしても、長いワンライナーが、どこかに残っているというのは
結構便利だったりします。(というか、こっちがメイン?

Enterの連打で重複が発生する件との重なりますが、履歴に重複があると、
そもそも # での補完にも重複があると、かなり使えない感じになってしまうので、
Enter の連打ではなくても、重複は削除するべきかなと思っていました。

いろいろ書きましたが、ガッツリとコードを書くなら PSReadLine とかもあるので、
既にあるものを使ったほうがいい気がするし、そもそも PowerShell は、3.0 以降じゃないと、
まともに使えるレベルではないと思っているので、2.0 はオマケ程度にしか考えておらず、
さっさと、2.0 はなくなってくれないかなと思っているので、2.0 に、うまく対応するように
がんばる意味を見いだせないというのがホンネです。
Posted by cd01 at 2014年02月10日 22:54
書き忘れていたので追記です。

> exit以外の方法でPowerShellを終了させると、直前の履歴が保存されない。
> これは、どういう意味かよく分かってない。

function prompt が実行されるタイミング的に最後に実行した履歴は残らないということだったと思います。
プロンプトが描画されるタイミングでしか走らないはずなので。
Posted by cd01 at 2014年02月10日 23:05
cd01さん、コメント有り難う御座います。

> ConvertTo-Csv って、2.0 になかったような気がしたのですがありましたっけ?
はい、在ります。手元のWinXP(PowerShellの3.0以上が入れられない)で確認したので間違いないです :)

> Out-File は文字コードとかもあるので、必要な気がします。
あー、確かに!

> 英語っぽくすることで、可読性を上げようとしているような気がします。
それはありそうですね……Unix shellの"ls"とか"cd"みたく記号的でないですし。デフォルトで潤沢に用意されているaliasの存在も、それ故にでしょうね。

> また、プロンプトで毎回実行して、1行ずつ保存すれば、最大件数を超えても保存できます。
> それを残しておくと、#foobar とやると補完できて便利だったりします。
いえ、どのみち $MaximumHistoryCountを超えた分は「#」でも出てこなくなります(3.0以上では仕様が変わってるんでしょうか)。
とはいえ、
> # でうまく補完できなかったとしても、長いワンライナーが、どこかに残っているというのは
> 結構便利だったりします。(というか、こっちがメイン?
というのは納得です。

> > exit以外の方法でPowerShellを終了させると、直前の履歴が保存されない。
> > これは、どういう意味かよく分かってない。
> function prompt が実行されるタイミング的に最後に実行した履歴は残らないということだったと思います。
> プロンプトが描画されるタイミングでしか走らないはずなので。
ええと、例えば「実行した.exeなりコマンドレットなり関数なりが、powershell巻き添えにして死んだ」場合とかでしょうか。
Posted by 天井冴太 at 2014年02月11日 01:17
Export-CsvはV1から、ConvertTo-CsvはV2からのコマンドレットです。
おそらく初期はImport-Exportの対でコマンドレットをまとめたかったのだと思います。
(動詞の種類を制限するため)

終了時の保存方法については、Register-EngineEventで
終了時(Exiting)にスクリプトを走らせれば問題ないと思います。
http://technet.microsoft.com/en-us/library/hh849967.aspx
Posted by S.K. at 2014年02月11日 15:23
> ええと、例えば「実行した.exeなりコマンドレットなり関数なりが、powershell巻き添えにして死んだ」場合とかでしょうか。

通常時でも、プロンプトが描画されるタイミングだと、最後の履歴が新しくなっておらず、最後に実行したコマンドの履歴を、プロンプトの関数では拾えなかったように記憶しています。
なので、S.K.さんが、おっしゃるように、イベントとかで別途処理を走らせないと残らないと思います。

個人的には、そこまでするのは面倒だと思うので、手軽にやると元記事のようになりました。
Posted by cd01 at 2014年02月11日 18:18
S.K.さん、cd01さん、コメント有り難う御座います。

> Export-CsvはV1から、ConvertTo-CsvはV2からのコマンドレットです。
ああ、そうなんですね。情報有り難う御座います。
# PowerShellは2しか触ったことない……

> 終了時の保存方法については、Register-EngineEventで
> 終了時(Exiting)にスクリプトを走らせれば問題ないと思います。
イベント在ったんですね(調べたら2にも在った)。今度イベント版を作ってみよう。

> 通常時でも、プロンプトが描画されるタイミングだと、最後の履歴が新しくなっておらず、最後に実行したコマンドの履歴を、プロンプトの関数では拾えなかったように記憶しています。
そうなんですか。 .NET Framework に用意されている各種メソッドの内、直ぐに処理が終了しない可能性が在る物は非同期処理としてしか提供されていないという話を聞いた記憶があります(すみませんうろ覚えです)。ファイルIOなんて正にその一種ですし、 .NET Framework 上で動作するPowerShellで「タイミングのズレ」は在りそうな話ですね……
Posted by 天井冴太 at 2014年02月11日 21:37
コメントを書く
コチラをクリックしてください

この記事へのトラックバック