どうも、PowerShellと出会ってからというもの、オブジェクト指向型でないコマンドラインシェルが時代遅れに見えて仕方ない天井冴太です。
Zshだと、ターミナルを開き直しても以前やったコマンドの履歴が参照出来て地味に便利。確かBashもそうだった気がする。PowerShellでは出来ないのか?とググって、以下の記事に辿り着いた。
PowerShell - このコマンドレット、この前実行した、ような・・・・・・ - Qiita
ふむふむ成る程。prompt
関数内でGet-History
した結果をExport-Csv
して、それをImport-Csv
するコードを自分のプロファイルのファイルに書き込めば可能と。
で、上記記事中のコードには、記事自体でも明記されているが、幾つかの問題点が残されている。
- 何も入力していない状態でEnterを押すと、一つ前の入力が保存される。
- ダブルクォーテーションマークのエスケープ処理が抜けている
- バージョン3.0未満だと
Export-Csv
に-Append
オプションが無い為、コードが少々複雑になる exit
以外の方法でPowerShellを終了させると、直前の履歴が保存されない。
上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
の存在も謎だ。)
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 に、うまく対応するように
がんばる意味を見いだせないというのがホンネです。
> exit以外の方法でPowerShellを終了させると、直前の履歴が保存されない。
> これは、どういう意味かよく分かってない。
function prompt が実行されるタイミング的に最後に実行した履歴は残らないということだったと思います。
プロンプトが描画されるタイミングでしか走らないはずなので。
> 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巻き添えにして死んだ」場合とかでしょうか。
おそらく初期はImport-Exportの対でコマンドレットをまとめたかったのだと思います。
(動詞の種類を制限するため)
終了時の保存方法については、Register-EngineEventで
終了時(Exiting)にスクリプトを走らせれば問題ないと思います。
http://technet.microsoft.com/en-us/library/hh849967.aspx
通常時でも、プロンプトが描画されるタイミングだと、最後の履歴が新しくなっておらず、最後に実行したコマンドの履歴を、プロンプトの関数では拾えなかったように記憶しています。
なので、S.K.さんが、おっしゃるように、イベントとかで別途処理を走らせないと残らないと思います。
個人的には、そこまでするのは面倒だと思うので、手軽にやると元記事のようになりました。
> Export-CsvはV1から、ConvertTo-CsvはV2からのコマンドレットです。
ああ、そうなんですね。情報有り難う御座います。
# PowerShellは2しか触ったことない……
> 終了時の保存方法については、Register-EngineEventで
> 終了時(Exiting)にスクリプトを走らせれば問題ないと思います。
イベント在ったんですね(調べたら2にも在った)。今度イベント版を作ってみよう。
> 通常時でも、プロンプトが描画されるタイミングだと、最後の履歴が新しくなっておらず、最後に実行したコマンドの履歴を、プロンプトの関数では拾えなかったように記憶しています。
そうなんですか。 .NET Framework に用意されている各種メソッドの内、直ぐに処理が終了しない可能性が在る物は非同期処理としてしか提供されていないという話を聞いた記憶があります(すみませんうろ覚えです)。ファイルIOなんて正にその一種ですし、 .NET Framework 上で動作するPowerShellで「タイミングのズレ」は在りそうな話ですね……