次の方法で共有


Select string. Finding a PowerShell *

I've thinking about odd figures of speech. Like telling one's naughty children "I'll swing for you one of these days". Since no one has been hanged in Britain in my life time , it is anachronistic as well macabre.
I like "Using X is like trying to kick a dead whale across a beach with your bare feet" - although a quick search showed I'd been both misquoting and misattributing it for years, and I'm prone to using the Blackadder-eque phrases like "Doing Y is like looking for a black cat, in a coal cellar, at night, without a torch, when the cat isn't there". (Who has a coal cellar these days. It sent me scurrying back to  Clive James for a thought on what happens to old metaphors.) 

Phrases like these last two bubble up in mind when I find myself working with regular expressions. I suspect people in the Unix world tend to find themselves working with regular expressions more, because the tools (most famously grep) are there. We've had FIND from DOS all the way to today and, well, lets just call it a triumph of backward compatibility. This is another case where the landscape changes with PowerShell. I’ve been trying to finish a new release of my PowerShell library for hyper-V. I mentioned before that when I right utilities for others I try to allow the user to provide input to many commands in more than one way. So I've been testing the each command with different syntax variations, and letting PowerShell make a transcript of session. Is there and easy way to check I have run all the commands I need to test. Cue Select-String.

I meet people with Linux/Unix environments who have taken to PowerShell in a big way, and if you happen to be one of them let me recommend an investigation of select-string. It's the most useful text processing tool I've found since I found about parsing text to columns in Excel (I had Excel 2.0 on my Windows 2.03 system 20 years ago, so this a long time).  With a transcript of my test session, I could do

 Select-String -Pattern "^PS" -Path .\test-pass1.txt 

^PS isn't to scary as regular expressions go , it says "At the start, PS" ,so this will get all the lines with a PowerShell prompt at the start If you just want to know the files that contain a match you can use the -match switch, but I want the details which come back as MatchInfo objects containing the path and short file name, line-number, line of text, the pattern that matched on a line and the what matched for that pattern.

Armed with the lines which contain commands, the next step was to find out which commands where in those lines. Something else I keep pointing out to people about PowerShell is the behaviour of allowing multiple items in a parameter where it makes sense. So for my pattern I could write "Get-VM","Get-VMDISK","Get-VMNIC" except that I have about 100 commands and could do without typing them in, so I saved myself the bother I wrote this instead:

 $Patterns = (get-command -Module hyperv | % {$_.name }) 

Select-String -Pattern "^PS" -Path .\test-pass1.txt | select-object line |  
    Select-String -Pattern $patterns | select-object matches 

This appeared to work, but it only got one match from each line: it turns out there is a -All switch, but worse Add-VM or Get-VM seemed to match instead of Get-VMDisk or Add-VMNIC, or whatever so I needed to check for a space character after the name – that’s \s in regular expression syntax. This still only produced one hit per line. It seems select string doesn't test all the patterns if one matches, that's it. It took a moment to work it out, I needed one giant pattern expression which said, this or this or this or this. You can do that with a | sign so I reworked what my patterns would be

 $patterns=get-command -Module hyperv | ForEach-Object -begin   {$patterns=""} `
                                                                      -process {$patterns += $_.name + "\s|"}` 
                                                                      -end     {$patterns -replace "\|$",""} 

Or in English, Start with an empty string, build it up by adding the name and \s (space) | ("or") , and find a trailing | sign (\| ‘escapes’ the character) and replace it with nothing and return the result.  (I've grown to like that trick of -replace "X$","" - for remove 'X' at the end of something , if there is one. –replace and –match in PowerShell use regular exmpressions).  And I got the right matches, with some grouping, sorting and formating I had what I wanted to know

 Select-String -Pattern "^PS" -Path .\test-pass1.txt | select-object line |  Select-String -Pattern $patterns -all |     ForEach-Object {$_.matches } | group value | sort count | format-table -auto name, count 
Name                     Count 

----                     ----- 
get-vmnic                    1 
remove-vm                    1 
set-vmnicvlan                2 
export-vm                    3 
Get-VMDiskController         3 
Get-VMDriveByController      3 
Add-VMSCSIController         4 
new-vm                       4 get-vmdisk                   5 
New-VMSnapshot               5 
add-vmnic                    5 
new-vhd                      5 
Set-VMMemory                 6 
set-vm                       6 Set-VMCPUCount               6 
add-vmdisk                   8 
Add-VMDrive                 11 
get-vm                      21 

Just for fun I got rid of the pattern variable and made it one line. There are 1028 lines in my transcript, and checking the command history I found it only took 0.34 of a second to do all of the work.

Comments

  • Anonymous
    January 01, 2003
    The comment has been removed
  • Anonymous
    September 14, 2009
    Is there something missing here? $patterns=get-command -Module hyperv | ForEach-Object -begin   {$patterns=""} `