のえら

技術備忘とかメモとか.間違いはつっこんでいただきたい所存.アフィリエイトはやっていません.

PowerShellのあれやこれ

概要

PowerShellでファイル操作とかする中で、調べたりコマンドヘルプ見たり試したりして溜まった小ネタとかメモとか。パイプとか基本的な部分は書いてない。
ざっくばらんに調べた順になっているのでごちゃごちゃしてる。

小ネタ

入力

タブキー押下で入力補完してくれる。
カーソルがコマンドの間にあるときにタブキーを押下すると、カーソル以降の入力内容は消える。

UNIXコマンドの使用

大体のUNIXコマンドはPowerShellエイリアスがつけられているので使える。
たとえば「cat」「ls」「pwd」とか。

後述の「別名(エイリアス)の取得」も参照。

テキストファイルの行数計測

データベースのレコードから作成したcsvファイルに対して、各何行あるのか調べる。
ぱっと思いついたのはこんな感じ。

Get-Item * -Include *.csv | foreach -process { (Get-Content $_).length }

カレントディレクトリ内のcsvファイルを取得して、foreachでぐるぐるして行数取得してみたけど、一定のファイルサイズになると「'System.OutOfMemoryException' の例外がスローされました。」で落ちる。マジか。

PowerShell使わないでWindowsコマンドでやれば落ちなかったけど、これはこれでなんとなく悔しい。
※1行目はbatで保存したときに、実行したディレクトリに移動するコマンド

cd %~dp0
forfiles /m *.csv /c "cmd /c find /v /c """""" @path"

で、PowerShellで他に行数取得できないかコマンド調査。
計測関数「Measure-Object」コマンドというのがあった。パイプでファイルオブジェクト渡すとLineオプションで行数出力できる。
早速使ってみる。行数だけ欲しいのでFormat-Listでcountのみ出力。

Get-Content hoge.csv | Measure-Object | Format-List count

時間はかかるけどメモリエラーは発生しない。やったね。

他に、System.IO.FileStreamを使用してファイルを読み込み、1行読んだら変数増加、末行まで読んだら変数出力、というやり方がある。省略。

別名で保存

カレントディレクトリ内の特定のファイルに対して何か処理をして、元のファイル名に文字列を付加して保存するやり方。

例1)カレントディレクトリのcsvファイルに対して先頭から2行ずつ取得してそれぞれ別名で保存する(UNIXのhead相当)

Get-Item * -Include *.csv | foreach -Process { gc $_ -TotalCount 2 > $($_.BaseName + "_head_2.csv") }

例2)カレントディレクトリのcsvファイルに対して末尾から10行ずつ取得してそれぞれ別名で保存する(UNIXのtail相当)

Get-Item * -Include *.csv | foreach -Process { gc $_ | Select-Object $_.Path -last 10 > $($_.BaseName + "_last_10.csv") }

拡張子を含まないファイル名の取得はBaseNameプロパティを用いる。
文字列の結合は$()内で行う。これ知らなくて、最初括弧外して記述して、実行時エラーになるわ元のcsvファイル上書きしちゃうわでえらいことになった。。

foreachの話

foreachはForEach-Objectの別名だけど、%も別名なので、上記のコマンドはさらに短く書ける。

Get-Item * -Include *.csv | %{ gc $_ -TotalCount 2 > $($_.BaseName + "_head_2.csv") }

余談で、foreachの改行ってどうするのかと調べてみた。{}をPowerShellが認識するから特殊なコマンド入力とかは不要だよ!って見つけたけど、}を入力した後も「>>」が表示されるからあれ?と思ったら、空入力でエンターキー押せばいいだけだった。。

PS C:\> 30000,56798,12432 | foreach-object -process {
>> $_ / 1024
>> }
>>

↑ここで;とか入れるからだめだった、エンターキー押せば実行される。

.NET Frameworkメソッド呼び出し

たまに「 [System.io.XXX]::HogeHoge」のような記述をみかけるので試してみた。

[System.io.Path]::GetFileNameWithoutExtension("C:\Users\hoge\foobar.txt")

拡張子を除いたファイル名が返却される(この場合「foobar」)

ファイル一覧取得時の小ネタ

ディレクトリ内の一覧表示は ls とか Get-ChildItem とかでできるけど、この一覧をソートしたい場合はパイプで Sort-Object に渡してソート項目を指定する。
以下のコマンドは「最終更新日」でソートする。

Get-ChildItem | Sort-Object lastwritetime -Descending

何も指定しない場合は名前昇順でソートされる。
項目は最終更新日とかサイズとかを指定可能。
降順はDescendingオプションを指定する。

ディレクトリだけ抽出したい場合は PSIsContainer プロパティが使える。

Get-ChildItem -Recurse | ?{ $_.PSIsContainer } | Select-Object fullname

これでもいい。

Get-ChildItem -Recurse | Where-Object { $_.PSIsContainer }

再帰的に全ファイルを抽出するのはこんな感じで書ける。

Get-ChildItem -Recurse | ?{ -not $_.PSIsContainer } | Select-Object fullname
ファイルから文字列検索

ファイルから指定した文字列が含まれる行を検索するには「Select-String」が使える。

Select-String C:\hoge.txt "hoge"

なんだけど、日本語を指定する場合はオプション Encoding に Default を指定する必要がある。

Select-String C:\hoge.txt "ほげ" -Encoding Default
コマンド実行時間の出力

Measure-Command を使用する。

Measure-Command { Get-Content hoge.csv | Measure-Object | Format-List count }

出力結果:

Days : 0
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 135
Ticks : 1359836
TotalDays : 1.57388425925926E-06
TotalHours : 3.77732222222222E-05
TotalMinutes : 0.00226639333333333
TotalSeconds : 0.1359836
TotalMilliseconds : 135.9836

フォーマットをHH:MM:SS.msにしたい場合はtoString()が便利。

(Measure-Command { Get-Content hoge.csv | Measure-Object | Format-List count } ).toString()

出力結果:

00:01:33.9708083

プロパティの確認

出力されるオブジェクトと、そのオブジェクトがどんなプロパティを持っているかは Get-Member で調べられる。

Get-ChildItem | Get-Member
別名(エイリアス)の取得

サンプルコードで見かけた「?{}」ってなんじゃろな、と思ったのでPowerShellに聞いてみる。

PS C:\> Get-Alias ?

CommandType     Name                                                Definition
-----------     ----                                                ----------
Alias           %                                                   ForEach-Object
Alias           ?                                                   Where-Object
Alias           h                                                   Get-History
Alias           r                                                   Invoke-History

ForEach-Objectの別名でした、と。
こんな感じで、構文見てなんとなく別名だろうな、と思った場合は「Get-Alias」で聞いてみるといい。「今は『ls』でファイル一覧を表示しているけど、PowerShellのコマンド覚えるかー」と思ったら Get-Alias 使ってコマンド確認する、という状況にも使える(しかしlsのほうが短くて便利である)
※別名の一覧を見たいときは引数なしで呼び出す

が、コマンドについて調べるなら次の「Get-Help」を使ったほうが早かったりする。

何はなくともGet-Help

ヘルプ情報を取得できる Get-Help コマンドが結構できる子。
コマンドのヘルプだけでなく、PowerShellでの別名を調べたり、キーワードから「もしかして」くらいに検索してくれたり、コマンドのサンプルを見たり、意外と有能。

たとえば、ファイル一覧抽出のサンプルを見ていたら「-le」というキーワードがあったけど、コマンドのオプションではなさそう、なんぞこれ?というような状況のときに、ヘルプに聞いてみる。

PS C:\> get-help "-le"

Name Category Synopsis

        • -------- --------

Split-Path Cmdlet 指定されたパス部分を返します。
about_arrays HelpFile データ要素を格納するためのコンパクトなデータ構造について説明します。
about_Break HelpFile Foreach、For、While、Do、または Switch ステートメントを直ちに終了するた...
about_Comparison_Operators HelpFile Windows PowerShell で値を比較する演算子について説明します。
about_For HelpFile 条件テストに基づいてステートメントを実行するのに使用する言語コマンドにつ...
about_operators HelpFile Windows PowerShell でサポートされている演算子について説明します。
about_remote_troubleshooting HelpFile Windows PowerShell でリモート操作のトラブルシューティングを行う方法について
about_scripts HelpFile Windows PowerShellスクリプトを作成および実行する方法について説明します。
about_Special_Characters HelpFile コマンドまたはパラメーター内でその次にある文字を Windows PowerShell がど...

leを含むであろうコマンドやヘルプの一覧が出力されるので、それらしいもの(※)をさらにヘルプで確認する。
(※)ファイル抽出の条件に使っているから演算子かな、というあたりをつける、というのは経験則に基づくのであまりいい例じゃなかった。。

PS C:\> get-help about_operators
トピック
about_Operators
(中略)
比較演算子
比較演算子 (-eq、-ne、-gt、-lt、-le、-ge) は、値を比較して条件をテスト
するときに使用します。たとえば、2 つの文字列値を比較して、それらが等
しいかどうかを判断できます。
(中略)
詳細については、「about_Comparison_Operators」を参照してください。

こんな感じで、ヘルプを追いかけていけばある程度解決できる。

ヘルプのバグ

ヘルプは日本語訳されてる。
が、format-listのヘルプ翻訳で、例2の説明では「これらの」とあるが、2番目のコマンドが記述されていない。
なお、ヘルプの英語版元ページ(https://technet.microsoft.com/ja-jp/library/hh849957.aspx)にはきちんと残っている。
ヘルプに違和感を感じたら英語版読んだほうがいいかも。めったにないと思うけど。

PS C:\> get-help format-list -detailed

名前
Format-List
(中略)
-------------------------- 例 2 --------------------------

C:\PS>$a = get-childitem $pshome\*.ps1xml

説明
-----------
これらのコマンドは、Windows PowerShell ディレクトリの PS1XML ファイルに関する情報を一覧形式で表示します。最初のコマ
ンドは、ファイルを表すオブジェクトを取得し、$a 変数に保存しています。2 番目のコマンドは、Format-List を使用して、$a
に保存されているオブジェクトに関する情報を書式設定しています。このコマンドは、InputObject パラメーターを使用して変
数を Format-List に渡します。変数を受け取った Format-List は書式設定された出力を表示するために既定の出力コマンドレ
ットに送ります。

※抜けているコマンド

PS C:\>format-list -InputObject $a