Windows 10ではPackageManagementというツールによって アプリの管理等がPowerShellからコマンドベースで出来る様になってるので、 Macで Homebrew+ Brew-file 的な感じでやってる設定管理の様なものが出来ないかということで、 取り敢えずPowerShellで出来ないかな、と思いちょっとPowerShellを勉強中。
PowerShellを使ってみて
PowerShellの基本的な事とかはネット上に溢れているので、 取り敢えず気になったこととか、他の言語と似てるとかぜんぜん違うとか、 間違いそうになったところとかのまとめ。
以下Windows10でPowerShell 3.0を使っています。
ExecutionPlicyの設定
上にも書きましたが 実はPowerShellでは初期状態だとスクリプトを実行することが一切出来ません。
管理者権限でPowerShellを立ち上げ、
PS > Set-ExecutionPolicy RemoteSigned
としてスクリプトの実行を許可する状態にしないといけません。
これは一度行えばPowerShellを再度立ち上げたりしても その後ずっとその状態が続きます。
スタイルガイド的な
Microsoftが示してるものがあります。
Strongly Encouraged Development Guidelines: https://msdn.microsoft.com/en-us/library/dd878270#SD03
なんとなく抽象的なものが多い印象ですが、
Cmdlet名やパラメーター名はパスカルケース(StyleGuide
的な)で書け、的なことも。
ただ、コマンド名はGet-Packages
みたいに、-
で繋げてるのも多い、と言うか
大体そんな感じ。
チェインケースとパスカルケースの融合的なものになっています。
bashrc的な個人設定ファイル
Bashの.bashrc的な起動時に読み込む個人設定ファイルは
$PROFILE
という変数に入っている
C:\Users\<USER>\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
みたいなパスにあるファイルです。
変数
変数は$x
の様に$
から始まる形になります。
ただし、シェルスクリプトでは代入時には$
を付けずに使うときに付けますが、
PowerShellでは代入する時にもつけます。
$x = 1
Write-Host $x
な感じで。
代入時はシェルスクリプトとは違って=
の両側は空けても構いません。
また、基本変数の型は自動で決まります。 (明示して他の型の代入を禁止することは可能。)
配列、ハッシュ(辞書)
配列は
$array = @("a", "b", "c")
の様に@()
で囲んで作ります。[]
ではありません。
また、基本PowerShellでは,
で区切ったものは配列、と言う規則らしく、
$array = "a", "b", "c"
としても上と同様の結果になります。
アクセスは$array[0]
等。
配列の長さは$array.Length
で取得できます。
要素の追加は
$array += "d"
と、配列に+=
で要素を一つ加える動作になります。
ハッシュは
$hash = @{"a" = 1; "b" = 2; "c" = 3}
な感じで@{}
で囲みます。
中の区切りは;
、値は=
で関係を書きます。
アクセスは$hash["a"]
等。
新たな要素は
$hash.Add("d", 4)
と、Add
を使います。
ハッシュのキーと値はそれぞれ
$hash.Keys
、$hash.Values
で全て取り出すことが出来ます。
配列を""
内で使う時は注意
追記: 2016/03/27
シェルスクリプトの様に"
で囲った文字列の中では
$
で始まる変数はその値を返しますが、PowerShellの配列の場合はちょっと注意しないといけません。
> $array = @("a", "b", "c")
> Wriet-Host $array
a b c
> Wriet-Host "$array"
a b c
> Wriet-Host $array[0]
a
> Wriet-Host "$array[0]"
a b c[0]
> Wriet-Output $array
a
b
c
> Wriet-Output "$array"
a b c
> Wriet-Output $array[0]
a
> Wriet-Output "$array[0]"
a b c[0]
> if($array -eq "a"){ echo OK }
OK
> if($array[0] -eq "a"){ echo OK }
OK
> if("$array" -eq "a"){ echo OK }
> if("$array[0]" -eq "a"){ echo OK }
> if("$array" -eq "a b c"){ echo OK }
OK
> if("$array[0]" -eq "a b c"){ echo OK }
>
こんな感じで""
で囲まれた中だと[]
の部分がただの文字列と解釈されます。
また、Write-Host
とWrite-Output
で
表示させるときWrite-Host
では""
で囲ってなくても続いた一つの文字列の様に表示されます。
さらにif
とかで判定する際には""
で囲むと全体で一つの文字列として認識され、
また[]
の部分も文字列として追加された形になってしまいます。
シェルスクリプトの様な
${array[0]}
的な書き方は出来ませんが、
"$($array[0])"
の様に$()
で囲ってあげると""
内でも配列の要素を取ってこれます。
追記ここまで
大文字小文字の区別
変数名や関数名において、大文字小文字は区別しません。
コマンドヘルプの見方
コマンドラインからヘルプを見たい時、
PowerShellのコマンドは-h
とか-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
とかがあれば
あった分だけ配列に入れて返します。
return
はその時点で関数が終わり、と言う宣言の意味が強く、
無くても構いません。
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
の値になっています。
/blog/2013/11/11/computer-windows-cygwin/#section/
上に有るような感じでCygwin用にHOMEをWindowsの環境変数として指定してあれば
$Env:HOME
はその指定したものになっているはずです。
PowerShellを立ち上げた時は通常$HOME
の値(C:\Users\user)から
スタートします。
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
とします。これだと連続空白は一つの区切りとして扱ってくれます。
コマンド結果をファイルへ出力
追記: 2016/03/27
> Get-Help Write-Output > help.txt
みたいな事をするとヘルプのオブジェクトが書き出されるため、 バイナリな内容になって開いてもヘルプの内容がそのままでは見れません。
表示される内容を通常のテキストの様に出力するには
> Get-Help Write-Output | Out-File help.txt -encoding utf8
と、オブジェクトをパイプでOut-File
に渡し、
さらにencodingしてあげるとテキストとして書き出すことが出来ます。
追記ここまで
シェルスクリプトでよく使うコマンドの類似コマンド
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 -type directory | New-Item -type fileなら新規ファイル作成。 |
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
PowerShell の基本: http://bonk.red/articles/PowerShell/basic.html