孫ディレクトリ以下のファイルを子ディレクトリに移動

A (カレント)
 B1
  C11
  C12
  ...
 B2
  C21
  C22
  ...
...

こんな感じのディレクトリ構成があり、ディレクトリC11, C12, ...かその子孫ディレクトリにあるファイルをB1に移動し、
ディレクトリC21, C22, ...かその子孫ディレクトリにあるファイルをB2に移動し、以下同様、ということがしたい。
どんなワンライナーを書けばいいか?

成功例

> gci -directory | %{ gci $_ -file -recurse | mv -Destination $_ }

Where-Objectのスクリプトブロックで複数の文を使いたいとき

へえこんなふうになるんだ

> 1..3 | ?{ $_ % 2 }
1
3
> 1..3 | ?{ $false }
> 1..3 | ?{ $true }
1
2
3
> 1..3 | ?{ $false; $false } # 注目
1
2
3

whereのスクリプトブロックで複数の文使えない!?

おさらい。where-objectの{ }は、フィルタースクリプトと書いてあるように、真偽値を返すスクリプトブロックである。

 Where-Object [-FilterScript] <scriptblock> [-InputObject <psobject>]  [<CommonParameters>]

ってことでスクリプトブロックの評価について調べてみた

> (&{ $false; $false }).GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

>  (&{ $false; }).GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Boolean                                  System.ValueType

> [bool](&{ $false; $false })
True

なるほど、?{ $false; $false }だと、@($false, $false)という配列になってTrueと判定されるみたい

ちなみに配列を真偽値に変換すると?

> [bool] @()
False
> [bool] @($false)
False
> [bool] @($true)
True
> [bool] @($false, $false)
True

なるほど、複数の要素がある配列だとTrueなのかな?だから、?{ $false; $false }だとTrueになると。


では、こうしたい時はどうすればよいか?ファイル名末尾の番号が235以上のものを取得したい。

> gci -file | ?{ $_.Name -match "\d+$"; [int]$matches[0] -ge 235 } # スクリプトブロックがTrueになるので、全ファイルが取得されてしまう!

and演算子を使えばうまくいった

> gci -file | ?{ ($_.Name -match "\d+$") -and ([int]$matches[0] -ge 235) }

()内部が式ではなく文のときもandでうまくいくかは謎だし、andの前の項がTrueを返さないといけなくて微妙

これでもうまくいった。こっちのほうがいいかも

> gci -file | ?{ $( $_.Name -match "\d+$"; [int]$matches[0] -ge 235 )[1] }

$()による部分式は内部で複数の値を返すときに配列になる。たくさんの文を含むときは$(...)[-1}とすれば文の数を数えずに済む

追記5/3 16:25
void型にキャストすれば明示的にコマンドラインの結果を破棄できると知った

> 1..3 | ?{ [void]$false; $_ % 2 }
1
3
> @( "a"; [void]"b"; "c" )
a
c

これを使って次のようにすればうまくいった。文全体をvoidでキャストする点に注意

> gci -file | ?{ [void]($_.Name -match "\d+$"); [int]$matches[0] -ge 235 }

しかし、たくさんの文を含むときはいちいち[void]するのが面倒そうなので、やはり$(...)[-1]の方法も覚えておきたい

ポケットリファレンス購入

CodeZineの記事読破。 http://codezine.jp/author/515 全部自分でサンプルプログラム考えて書いたら基礎力になった気がする。
ただし、Active Directory, Oracleは分からないのでパス。

さて次は?ってことで好評なポケットリファレンス購入。片っ端からサンプルプログラム自作するだけでかなり力がつきそう。500ページ以上もある。これで当分は学習のネタに困らない。
ちなみにGoogleブックスで第1章丸ごと読める。
http://books.google.co.jp/books?id=g_iCgzSrv8YC&printsec=frontcover#v=onepage&q&f=false

あと、昔買って本棚の肥やしになっていた『PowerShellインアクション』も今火を吹く!かも

他にも学習ネタはたくさん候補がある。脳内整理もかねてメモ。

基礎文法最速マスター、および他の記事
http://winscript.jp/powershell/202
まだ読んでなかったので

マイナビの記事
http://news.mynavi.jp/author/0000584/
今読んでる

PowerShell from Japan!!
http://blog.powershell-from.jp/
まず入門カテゴリから

AdventCalendar2011-2013
http://atnd.org/events/22073
http://atnd.org/events/34319
http://atnd.org/events/45107
分量多いしいろんな人が書いてるから参考になりそう

TechNetスクリプトセンター
http://gallery.technet.microsoft.com/scriptcenter/site/search?f%5B0%5D.Type=ProgrammingLanguage&f%5B0%5D.Value=PowerShell&f%5B0%5D.Text=PowerShell
実用的な例たくさん

Functionプロバイダかっけえ

Functionプロバイダで存在する関数の情報にアクセスできる

たとえば、

> function Foo(){ "test" }

って関数を定義して、

> gci function: | ?{ $_.Name -eq "Foo" } | fl -property *

PSPath              : Microsoft.PowerShell.Core\Function::Foo
PSDrive             : Function
PSProvider          : Microsoft.PowerShell.Core\Function
PSIsContainer       : False
HelpUri             :
ScriptBlock         :  "test"
CmdletBinding       : False
DefaultParameterSet :
Definition          :  "test"
Options             : None
Description         :
Verb                :
Noun                :
HelpFile            :
OutputType          : {}
Name                : Foo
CommandType         : Function
Visibility          : Public
ModuleName          :
Module              :
RemotingCapability  : PowerShell
Parameters          : {}
ParameterSets       : {}

こうなる。

このgci、要はファイルシステムのlsコマンドと同じでしょと思ってたら、任意のプロバイダを想定してたのか。この抽象化面白い。

Aliasプロバイダ、Certificateプロバイダ(デジタル証明書?)、Environmentプロバイダ(環境変数)、FileSystemプロバイダ、
Functionプロバイダ、Registryプロバイダ、Variableプロバイダもあるよ!

FileSystemだと

PS E:\Documents > cd c:\hoge\fuga
PS C:\hoge\fuga >

とするのと同様、Registryプロバイダだと

PS E:\Documents > cd HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer
PS HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer>

こうできる。続けざまに、

PS HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer> gci | %{ $_.PSChildName }
Advanced
AppContract
AutoplayHandlers
BitBucket
CabinetState
...
VisualEffects
Wallpaper
Wallpapers
WordWheelQuery
SessionInfo

レジストリのサブキーを調べたり、

PS HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer> gp .
ExplorerStartupTraceRecorded : 1
ShellState                   : {36, 0, 0, 0...}
UserSignedIn                 : 1
SIDUpdatedOnLibraries        : 1
...
PSParentPath                 : Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\Software\Microsoft\Windows\Current
                               Version
PSChildName                  : Explorer
PSDrive                      : HKCU
PSProvider                   : Microsoft.PowerShell.Core\Registry

レジストリのエントリを調べたりできる

F2キー便利

たとえば

> Get-PSBreakpoint -Script s1.ps1

を実行してから

> Get-PSBreakpoint -Script s2.ps1

を実行したい。

ここで、F2キーを押すと

入力文字の前までコピー:

という表示がでて

1

を入力すると

> Get-PSBreakpoint -Script s

と入力されるので、あとは

2.ps1

を入力するだけで

> Get-PSBreakpoint -Script s2.ps1

になる。

http://technet.microsoft.com/ja-jp/scriptcenter/powershell_owner03.aspx

F7でコマンド番号を覚えておいてF9コマンドラインに入力するのも地味に便利かも

プロファイルでエイリアスの記述された行を表示

PowerShellガンガン学習中。

$lines = gc $profile | ? { $_ -match "^sal|^Set-Alias" }
echo $lines

これがSelect-Stringなら・・・

sls "^sal|^Set-Alias" $profile

こう書ける。

メリットは
・既製のもので短く書ける
・行数表示してくれる
・Contextパラメータで前後の行表示してくれたりする


うーむ、それにしてもPowerShellスーパーpre記法に未対応なのは残念。