てきとーです。ふぇぇ、まさかりこわいです。
PowerShell といえば Pipeline 処理。 C# なら LINQ がわかりやすい対比になると思ったのでてきとーな記事をでっち上げてみます。
今回は文字列(string) を題材にします。
目次
わかりやすい例 : フィルタ
とりあえず Where ですよねー。
PowerShell
Pipeline 処理といえばてけとーにプロセス取得してフィルタしてとか
Get-Process | where Name -eq "powershell"
あるいは、v4からの .Whereオペレータを使って Pipeline使わずつなげると
[System.Diagnostics.Process]::GetProcesses().Where{$_.Name -eq "powershell"}
で、こう
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 429 38 67576 78732 610 5.31 7576 powershell
LINQ
簡単に LINQ でも 書けますよね。
System.Diagnostics.Process.GetProcesses().Where(x => x.ProcessName == "powershell");
あとはてきとーなプロパティを表示すればいいでしょう。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApplication3 { class Program { static void Main(string[] args) { System.Diagnostics.Process.GetProcesses() .Where(x => x.ProcessName == "powershell") .Select(x => x.ProcessName) .ToList() .ForEach(Console.WriteLine); Console.ReadLine(); } } }
Redis の info 結果で試す
あまりやりたくないのですが、string を同様に処理してみましょう。
たとえば、RespClient を使って、テキトーにサーバーから info コマンドの結果を取得します。
Connect-RedisServer Send-RedisCommand -Command "info" Disconnect-RedisServer
結果は、このような string
です。
はい、string[]
ではなく string
です。行ごとに分かれてませんからねー。ほぇぇ。
# Server redis_version:2.8.13 redis_git_sha1:00000000 redis_git_dirty:0 redis_mode:standalone os:Linux 3.10.35-43.137.amzn1.x86_64 x86_64 arch_bits:64 multiplexing_api:epoll gcc_version:4.8.2 process_id:6490 run_id:0023285cc87f5b4f686ec0b438d8e3f884e4e77c tcp_port:6379 uptime_in_seconds:9455054 uptime_in_days:109 hz:10 lru_clock:458978 # Clients connected_clients:0 client_longest_output_list:0 client_biggest_input_buf:0 blocked_clients:0 # Memory used_memory:878480 used_memory_human:857.89K used_memory_rss:7557120 used_memory_peak:935456 used_memory_peak_human:913.53K used_memory_lua:31744 mem_fragmentation_ratio:8.60 mem_allocator:jemalloc-3.2.0 # Persistence loading:0 rdb_changes_since_last_save:7025 rdb_bgsave_in_progress:0 rdb_last_save_time:1409681336 rdb_last_bgsave_status:ok rdb_last_bgsave_time_sec:0 rdb_current_bgsave_time_sec:-1 aof_enabled:0 aof_rewrite_in_progress:0 aof_rewrite_scheduled:0 aof_last_rewrite_time_sec:-1 aof_current_rewrite_time_sec:-1 aof_last_bgrewrite_status:ok # Stats total_connections_received:82988 total_commands_processed:674668037 instantaneous_ops_per_sec:16 rejected_connections:0 expired_keys:0 evicted_keys:0 keyspace_hits:0 keyspace_misses:0 pubsub_channels:0 pubsub_patterns:0 latest_fork_usec:656 # Replication role:slave master_host:127.0.0.1 master_port:6379 master_link_status:up master_last_io_seconds_ago:0 master_sync_in_progress:0 slave_priority:100 slave_read_only:1 connected_slaves:0 # CPU used_cpu_sys:7664.74 used_cpu_user:4442.53 used_cpu_sys_children:0.00 used_cpu_user_children:0.00 # Keyspace db0:keys=16,expires=0
このままだと Linuxで シェルを使うならいいのですが(よくない)、PowerShell や C# からは使いにくいです。
そこで、オブジェクトにしてみましょう。
最終的には、このような形で出力されることを目指します。
Name Value ---- ----- redis_version 2.8.13 redis_git_sha1 00000000 redis_git_dirty 0 redis_mode standalone os Linux 3.10.35-43.137.amzn1.x86_64 x86_64 arch_bits 64 multiplexing_api epoll gcc_version 4.8.2 process_id 6490 run_id 0023285cc87f5b4f686ec0b438d8e3f884e4e77c tcp_port 6379 uptime_in_seconds 9457077 uptime_in_days 109 hz 10 lru_clock 459181 connected_clients 2 client_longest_output_list 0 client_biggest_input_buf 0 blocked_clients 0 used_memory 878480 used_memory_human 857.89K used_memory_rss 7553024 used_memory_peak 935456 used_memory_peak_human 913.53K used_memory_lua 31744 mem_fragmentation_ratio 8.60 mem_allocator jemalloc-3.2.0 loading 0 rdb_changes_since_last_save 4495 rdb_bgsave_in_progress 0 rdb_last_save_time 1409683443 rdb_last_bgsave_status ok rdb_last_bgsave_time_sec 0 rdb_current_bgsave_time_sec -1 aof_enabled 0 aof_rewrite_in_progress 0 aof_rewrite_scheduled 0 aof_last_rewrite_time_sec -1 aof_current_rewrite_time_sec -1 aof_last_bgrewrite_status ok total_connections_received 83118 total_commands_processed 674712373 instantaneous_ops_per_sec 27 rejected_connections 0 expired_keys 0 evicted_keys 0 keyspace_hits 0 keyspace_misses 0 pubsub_channels 0 pubsub_patterns 0 latest_fork_usec 686 role slave master_host 127.0.0.1 master_link_status up master_last_io_seconds_ago 0 master_sync_in_progress 0 slave_priority 100 slave_read_only 1 connected_slaves 0 used_cpu_sys 7664.74 used_cpu_user 4444.35 used_cpu_sys_children 0.00 used_cpu_user_children 0.00 db0 {(keys, 16), (expires, 0)}
文字列のデリミタを探す
返ってみたデータを見ると、 infoの結果は、表題が #
から始まり、区分、データは :
で区切られています。
# Replication role:slave
さらに、データの中身に複数の結果がある場合は、 ,
でデータは区切られて、KVは =
で区切られています。
# Keyspace db0:keys=16,expires=0
ということで、デリミタは確定しました。
PowerShell
さくっと書いてみましょう。HashTable使ってるのは見逃してください。
function ParseInfo ([object]$source) { $source -split "`r`n" ` | where {$_.contains(':')} ` | %{ $split = $_.Split(':') $value = $split[1].split(',') @{ $split[0] = if ($value.length -eq 1){ $split[1] } else { $value ` | %{ $inside = $_.('=') [Tuple]::Create($inside[0], $inside[1]) } } } } }
結果は、先ほどの通りです。
LINQ
先ほどのPowerShell のパイプライン処理を LINQ でやってみます。
public Dictionary<string, object> ParseInfo(object source) { var item = source.ToString().Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); var dic = item .Where(x => x.Contains(':')) .Select(x => x.Split(':')) .ToDictionary(kv => kv[0], kv => { var split = kv[1].Split(','); if (split.Length == 1) return (object)split[0]; return split.Select(x => x.Split('=')) .Select(xs => Tuple.Create(xs[0], xs[1])) .ToArray(); }); return dic; } }
なにができるようになったの
ちなみに、文字列をこのように変換したことで、RespClientで次のような書き方ができるようになります。わーい。
Connect-RedisServer Get-RedisInfo | where Key -eq db0 | select -ExpandProperty Value
まとめ
なんとなく、 LINQ で書いてたのが PowerShell だとこう、あるいはその逆。という例になれば幸いです。
LINQ 便利メソッド多い以上に、ラムダ式が書きやすくていいですよね。PowerShell には、匿名コンストラクタである ScriptBlock がありますが、アロー演算子がなかったりと書きにくいのです。
PowerShell は、何気に動的に型変換かけたりしているのは意図的です。
おまけ
LINQ での書き方は、neuecc と tanaka_733 に教えてもらったりして幸せでした。はい。ありがとうございました!