SNSへはこちら

MacにPowerShellを入れて遊んで見る

久しぶりの投稿です。最近はネタが無くて寝たモチベが上がらんのですよ〜。誰か助けて(新しいマイコン教えて)。
今回は 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-Objectuniq -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{$_}}

ゴリ押し来ました。

オブジェクトを使うという発想はとても面白かったです。もっと便利な構文が増えると良いなぁと感じますね。