Windows 10ではPackageManagementというツールによって アプリの管理等がPowerShellからコマンドベースで出来る様になってるので、 Macで Homebrew+ Brew-file 的な感じでやってる設定管理の様なものが出来ないかということで、 取り敢えずPowerShellで出来ないかな、と思いちょっとPowerShellを勉強中。
PowerShellを使ってみて
PowerShellの基本的な事とかはネット上に溢れているので、 取り敢えず気になったこととか、他の言語と似てるとかぜんぜん違うとか、 間違いそうになったところとかのまとめ。
以下Windows10でPowerShell 3.0を使っています。
スタイルガイド的な
Microsoftが示してるものがあります。
なんとなく抽象的なものが多い印象ですが、 Cmdlet名やパラメーター名はパスカルケース(StyleGuide的な)で書け、的なことも。
ただ、コマンド名はGet-Packagesみたいに、-で繋げてるのも多い、と言うか 大体そんな感じ。 チェインケースとパスカルケースの融合的なものになっています。
変数
変数は$xの様に$から始まる形になります。
ただし、シェルスクリプトでは代入時には$を付けずに使うときに付けますが、 PowerShellでは代入する時にもつけます。
$x = 1
Write-Host $x
な感じで。 代入時はシェルスクリプトとは違って=の両側は空けても構いません。
また、基本変数の型は自動で決まります。 (明示して他の型の代入を禁止することは可能。)
大文字小文字の区別
上でも書いたことですが、 変数名や関数名において、大文字小文字は区別しません。
コマンドヘルプの見方
コマンドラインからヘルプを見たい時、 PowerShellのコマンドはhelpを引数で持ってるものは基本的にはなく、 manコマンドの様に
> Get-Help Get-Packages
と、Get-Helpを使います。helpにもエイリアスされてるのでhelp Get-PackagesでもOK。
また、さらに詳しい情報を見たいときは-detaied、-examples、-fullという 引数を使うとさらなる情報が見れます。
このヘルプの書き方は決まりがあって、 コメントとして
#.SYNOPSIS
# Synopsis of the script
の様に、.<KEY>とその下にその説明を書くことでヘルプの内容を書くことが出来ます。
Write-Output (echo)の罠
echoにもエイリアスされているWrite-Outputというコマンドですが、 名前の通り引数をアウトプットに出すコマンドです。
ただし、関数内で使う場合に注意が必要で、 Write-Outputはその場で標準出力に文字を返すのではなく、 単に与えたオブジェクトを返します。
関数内で使うと関数の返り値になります。 関数内ではreturnを使って返り値を返すことが出来ますが、 返り値はこれだけではなく、それまでにWrite-Outputとかがあれば あった分だけ配列に入れて返します。
関数に型は無いので何でも返せる、ということもあります。
で、ここで特にechoとして書けるので、 シェルスクリプト的なノリで使っているとコメントは表示されないわ 中ではおかしな変数が返ってきて混乱してるわで大変なことになります。
直接コマンドラインに表示させたいときはWrite-Hostを使います。
ただし、Write-Hostはこのコマンドを標準出力に表示させようと
> My-Cmd > log
としてもlogを飛ばして表示されてしまうので注意です。
echo的に使いたいなら関数の外で直にWrite-Outputを使うしかありません、多分。
この仕様はパイプにつなぐ時も同様で、 出力を次に渡したいならWrite-Outputです。
ただし、パイプに渡す場合など特に、渡されるものは出力をそのまま文字列にしたものではなく、 与えられたオブジェクトそのものなのでそれを理解したうえで次で使わないといけません。
シェルスクリプト的な単純な脳で行くと上手く行かないので注意。
関数の引数
関数の定義は
function MyFunction ($par0="aaa", $par1) {
...
}
の様なC++ + シェルスクリプト的な感じ。 必ず最初にfunctionが必要です。
()の引数の部分はなくても構いません。
無くても、
function MyFunction {
Param($par0="aaa", $par1)
...
}
みたいに関数の中でPraramというコマンドで作ることも出来ます。
さらに何も指定してなくても
function MyFunction {
Write-Host $args[0]
...
}
みたいに、引数はargsという変数の中に入り、この様に呼ぶことが出来ます。
Paramとargsに関してはスクリプトでも同様にスクリプトへの引数に対して 使うことが出来ます。
関数に引数を渡すには
MyFunction -par0 "bbb" -par1 "ccc"
みたいな感じになります。変数名に$の代わりに-を付けたもので指定。
これらがなくても
MyFunction "bbb" "ccc"
としても引数の順番通りに入っていくのでこの場合は上と同じ事になります。 スクリプトへの渡し方も同様。
HOME
$HOMEという環境変数がありますが、 例えばvimを使う場合などに参照される本当のホームディレクトリは $Env:HOMEです。
$Env:XXXという値に色々Windowsの環境変数が入っていますがそのうちの一つ。 $Env:HOMEPATHという値もあって、これが$HOMEの値になっています。
foreachとForEach-Object
For文を回す際に使える同じようなメソッド。 というか、alias foreachとするとForEach-Objectのエイリアス、と出てきます。
ただ、微妙に使い方は違くて、
foreach($x in $array){
Write-Host $x
}
ForEach-Object -InputObject $array {
Write-Host $_
}
みたいな感じでForEach-Objectの方はコマンドとして使いますが、 foreachの方は条件分岐メソッド的な感じに。
また、実際にやることも、foreachの方は全てのオブジェクトを最初にメモリにロードするのに対し、 ForEach-Objectは一つづつ見ていきます。
なので、物凄い重い内容を持った配列をforeachで行うと パフォーマンスが下がります。
ただ、そこだけでメモリ不足に陥る様なことはあまり無いと思います。 メモリが十分にあるのであれば、最初に用意してしまったほうが速くなるので 大概の場合はforeachを使ったほうがお得。
ちなみにForEach-Objectは%にもエイリアスされていて こちらはForEach-Objectと全く一緒の機能になります。
コマンドを複数行に分ける
長いコマンドが1行に収まらない場合は最後に`をつけると行が続いていると認識してくれます。
MyCommand `
-option1 "aaa" `
-option2 "bbb"
また、()の途中だったり明らかに途中と分かる様な場合は 他の言語同様改行があっても同一行として扱われます。
if ($a -eq “aaa”) return 1みたいな書き方は出来ない
C言語っぽい感じのifとかの使い方なので、 ブラケットなしの
if ($a -eq "aaa") return 1
みたいに一行で済ましてしまう形を使いたくなりますが、これはダメです。
if ($a -eq "aaa") {return 1}
と一行でもきちんと{}で囲う必要があります。
複数空白がある場合のSplit
文字列を分割してくれる関数としてSplit()という関数があります。 引数の文字で区切って配列にするのですが、 引数を与えなければ空白文字で区切ります。 ただ、
> $x = "aaa bbb ccc"
> $x.Split()
とすると、["aaa", "bbb", "", "ccc"] みたいな感じで複数空白がある場合にはそれぞれ一つづつ見て 余計な要素を作ってしまいます。
これを空白部分は全て一つの区切りに、としたい場合には
> -split $str
とします。これだと連続空白は一つの区切りとして扱ってくれます。
シェルスクリプトでよく使うコマンドの類似コマンド
PowerShellのコマンドは基本的にWrite-Outputみたいな チェインケース+パスカルケースの形をしていますが、 echoの様にシェルスクリプトでよく使う名前にエイリアスされているものが結構有ります。
| Alias | Original Command | Note |
|---|---|---|
| cat | Get-Content | |
| cd | Set-Location | cdとだけ打っても移動しない。($HOMEに行ったりしない。)cd -みたいな前に戻る、は出来ない。 |
| clear | Clear-Host | |
| cp | Copy-Item | |
| curl/wget | Invoke-WebRequest | Invoke-WebRequest -Uri <Uri> -OutFile <string>でUriの物をダウンロード。 |
| diff | Compare-Object | |
| echo | Write-Output | 単純なテクスト出力ではなくオブジェクト出力。関数内等で使うと返り値の一部として扱われる。また、コマンドを書かず単に変数名や関数名を書いただけでもその内容が返される。 |
| history (h) | Get-History | |
| kill | Stop-Process | |
| ls | Get-ChildItem | |
| man | Get-Help | |
| mkdir | New-Item | 何故かaliasとしては登録されてない?(alias mkdirはエラー)がGet-Help mkdirするとNew-Itemのヘルプが出て確かに使える。 |
| mv | Move-Item | Rename-Itemというコマンドもあるが、こちらは変更後はパス名を除いたオブジェクト名だけを書く。同じフォルダの中で名前を変える専用。また、名前を変えるだけなので他の同じ名前のファイル等があれば上書きは出来ない。 |
| popd | Pop-Location | |
| ps | Get-Process | |
| rm | Remove-Item | -rとかなしにディレクトリも消せる。 |
| sleep | Start-Sleep | |
| sort | Sort-Object | |
| tee | Tee-Object |
勿論オプションの種類も違いますし Noteに書いてある事以外にも沢山違いが有って、 逆に同じ名前で使える事で違いに戸惑う事もありますが、 取り敢えずこんな感じである程度のコマンドが同じようにあります。
それ以外のエイリアスではないけど同じ様なコマンドとしては、
| Linux Command | PowerShell Command | Note |
|---|---|---|
| alias | Get-Alias/alias | 引数無しで使い全てのエイリアスを表示するコマンドとして。エイリアスをセットするにはSet-Alias |
| grep | Select-String | -Path <string> -Pattern <string>の形で検索ファイルとパターンを渡すがオプション無しで渡す場合にはLinuxと逆の順番。 |
| fg | Receive-Job | |
| find | Get-ChildItem -Recurse | find -name XXXはGet-ChildItem -Recurse|Where-Object {$_.Name -Match "XXX"} |
| jobs | Get-Job | |
| read | Read-Host | |
| sed ‘s/xxx/yyy/g’ a.txt | Get-Content .\a.txt | % {$_ -Replace "xxx", "yyy"} | さらにファイルの中身を置換してしまうならパイプを続けて|Set-Content .\a.txtを追加すれば良い。 |
| tail | Get-Content -Tail | 必ず行数指定が必要。tail -fのようにアップデートを待って表示するにはGet-Content -Tail 5 -Waitの様に-Waitを付ける。スクリプト等の中でファイルを開いて何かを書き出してる間は -Waitが上手く動かない、という話も(ファイルのタイムスタンプが変わらない様な扱いで出力ファイルに記述することが出来るため)1 |
| uniq | Get-Unique | Get-Uniqueでは連続してない行も検索し全ての重複を削除する。PowerShellではguにエイリアスされている。 |
| wc -l | $(Get-Content .\file.txt).Length | |
| Start-Job { | バックグラウンドジョブの始め方。 |
この辺りがLinuxコマンド的な物に近い動作をしてくれます。 自分でSet-Alias uniq Get-Uniqueみたいにしておいても良いかも。
「 Linux ならできるのに、だから Windows は…」「それ PowerShell でできるよ」 - Qiita