久しぶりの投稿です。最近はネタが無くて寝たモチベが上がらんのですよ〜。誰か助けて(新しいマイコン教えて)。
今回は Mac に PowerShell を入れてパワーシェル芸がしたいということでやってみます。
Homebrew でのインストール
Microsoft 公式サイトが詳しく説明されています。見るのだるい人は以下を実行すれば良いんです。サイト重いしね。
$ brew tap caskroom/cask
$ brew cask install powershell # インストール時にパスワードを求められる
$ pwsh # Powershell起動
以前インストールしていたことがあったのですが、その時の実行コマンドはまんま powershell
だった記憶があるんですけどねぇ。pwsh
、なんかダサいぞ。
オブジェクトの概念のあるシェル
みなさんが馴染みのある(多分)シェルは bash
等で、アレは UNIX 哲学の 「数値データは ASCII フラットファイルに保存する」というものに基づいているため、数値と言わず文字も基本的にテキストで渡します(参考: 『UNIXという考え方』, p.8)。しかし PowerShell は違って、オブジェクトを渡す というものらしいです。このシェルは大文字小文字の区別がないので色々と楽そうです。でも多分 Linux の ext* とかではファイル名の区別はあるんじゃないかなぁ。
あと、この記事にある内容は大体 第2回 PowerShellの基礎 (1/3):PowerShell的システム管理入門 - @IT とかのシリーズのほうが詳しいのでこちら等を参照してくださいな。この記事は使用感を書くだけにしますね。
遊んでみた
普通に echo
。
PS > echo あ
あ
いきなりなんですが、日本語を打つと文字幅の関係か表示がずれるようです(on iTerm2)。ちょっとこれはいただけませんねぇ。
もちろん普通に外部コマンドも使えます。
PS > echo abc | sed 's/abc/def/'
def
seq
に匹敵するもの。..
を用いることで可能。
PS > 1..5
1
2
3
4
5
パイプは |
でつなぐ。それぞれにフィルタを施したい時は where
を使います。なおパイプで受け取った1つのオブジェクトは $_
という変数で表します。
PS > 1..5 | echo
1
2
3
4
5
PS > 1..5 | where {$_ -eq 2}
2
ここで =
は代入演算子。なので比較は別になる。
- 比較演算子
==
は-eq
<
は-lt
<=
は-le
>
は-gt
>=
は-ge
配列は @()
で表現する。
更にブールの True
False
はそれぞれ $true
、$false
と特殊変数で表す。null
は $null
。
PS > @($true, $true, $false) | where {$_}
True
True
通常のパイプみたいに foreach
するにはどうするかというと、こんな感じ。
PS > 1..10 | foreach {$_ * 2}
2
4
6
8
10
12
14
16
18
20
変数代入もサクッと。
PS > $a=1
PS > echo $a
1
エイリアス
実はこれまで打ってきたコマンド(PowerShell ではコマンドレットという)は正式名称ではありません。基本的にコマンドレット名は 動詞-目的語
という並びで構成されていて、PascalCase です。本当のコマンド一覧を得るにはどうすればいいかというと、Get-Alias
でできます。引数としてエイリアスを指定すると、指定したものが取得されます。
例えばこれまでの一覧はこんな感じ。
エイリアス | コマンドレット実体 |
---|---|
echo | Write-Output |
where | Where-Object |
foreach | ForEach-Object |
? | Where-Object |
% | ForEach-Object |
つまりフィルタは ?
、ForEach は %
で行けるということなんですね。
オブジェクトのプロパティ取得
実際にコマンドを実行して Get-Member
に渡せば行けます。
PS > Get-Alias | Get-Member
TypeName: System.Management.Automation.AliasInfo
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ResolveParameter Method System.Management.Automation.ParameterMetadata ResolveParameter(string name)
ToString Method string ToString()
CommandType Property System.Management.Automation.CommandTypes CommandType {get;}
Definition Property string Definition {get;}
Description Property string Description {get;set;}
Module Property psmoduleinfo Module {get;}
ModuleName Property string ModuleName {get;}
...
なお、Get-Alias
は引数としてエイリアス名しか受け付けません。
ということで、元々が Get-Member
なもののエイリアスを探してみましょう! Get-Member
によると渡されたオブジェクトに対して DisplayName
というものがあるらしいのでこれでフィルタを掛けられます。
PS > get-alias | ?{$_.DisplayName -match "Get-Alias"}
CommandType Name Version Source
----------- ---- ------- ------
Alias gal -> Get-Alias
出ました。どうやらエイリアスは gal
のようです。ギャルですね。
ここで -match
は正規表現マッチで、「含む」を利用できるらしいです。
シェル芸やってみよう
まだ不慣れなので第1回の問題から。前半戦までの内容です。
Q1
PS > get-content /etc/passwd | ?{$_.indexof(':') -ge 0} | %{$_.remove($_.indexof(':'))}
remove
は引数のインデックスから後ろを削除してくれる。Q2 の解答のように split
してから要素番号を選ぶほうが好都合かも。
Q2
PS > get-content /etc/passwd | ?{$_ -match "/.*sh"} | %{$_.split(':')[6]} | group-object
Group-Object
で uniq -c
的なのができるらしい!
Q3
PS > get-childitem | %{echo $_.name} | %{$_.replace('#!/bin/bash', '#!/usr/local/bin/bash')}
ここまでは可能。しかし in-place で書き換えは不可能(セミコロンを使ってしまう)。
Q4
思いつかず。
Q5
PS > 1..100 | %{if($_%15 -eq 0){"FizzBuzz"}elseif($_%3 -eq 0){"Fizz"}elseif($_%5 -eq 0){"Buzz"}else{$_}}
ゴリ押し来ました。
〆
オブジェクトを使うという発想はとても面白かったです。もっと便利な構文が増えると良いなぁと感じますね。