tech.guitarrapc.cóm

Technical updates

PowerShell の Pipeline と C# の LINQ の簡単な比較とかなんとか

てきとーです。ふぇぇ、まさかりこわいです。

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 での書き方は、neuecctanaka_733 に教えてもらったりして幸せでした。はい。ありがとうございました!