Saturday, 12 July 2008
Extract Columns From Tabular Text - Powershell and Python
Finishing off different ways to extract columns, here's the PowerShell and Python versions:
foreach-object { $_.Split('<delimiter>')[-1] }
$_ is the current object (or record) in the loop. When processing tabular text, $_ is a .Net String class, so we use its Split() method to divide the input on the <delimiter>. Split() returns a String array, and index -1 refers to the last String (or column) in that array.
python -c "import sys; print ''.join(s.split('<delimiter>')[-1] for s in sys.stdin)"
Unlike Perl or Ruby, Python doesn't have any special command-line support to iterate through all lines of input or split the input, so we have to use this generator hack. Like the PowerShell version, each record (s) is a string, so we use a string's split() function to divide the input into an array and use index -1 to refer to the last column in that array.
See Also
Labels: PowerShell, Python
Wednesday, 30 January 2008
Add Path Environment Variable in PowerShell
Adding a path to the PATH environment variable in the current PowerShell session is simpler than I thought:
> $env:path += ";path"
Note: remember to prepend the semi-colon to the new path make a valid path list.
You can make your PATH variable persistent using the SetEnvironmentVariable() .Net method:
[System.Environment]::SetEnvironmentVariable("PATH", $Env:Path + ";path", "target")
… where target is "Machine", "User" or "Process". Check the .Net documentation for what these values mean.
Labels: PowerShell
Saturday, 26 January 2008
Prune Directories with PowerShell
I made a backup of all files with a certain pattern files from one directory to another. If the pattern was, say PostScript (*.ps) files, you can use the following PowerShell statement:
Copy-Item -recurse -filter *.ps <source> <destination>
Now I had a new directory with the same structure as the original one, but Copy-Item made many new empty directories because there were files in the source directories but these files were not copied. Just to be tidy, I wanted to prune the empty directories in the destination path. The Remove-Item cmdlet does not have an option to remove empty directories, so I wrote the following short PowerShell script:
1 function prune-directory {
2 param ([string]$path)
3 if ($path.Length -le 0) {
4 write-host "Empty path."
5 return
6 }
7 if (-not (test-path -literalPath $path)) {
8 write-host "Invalid path:", $path
9 return
10 }
11 if (@(get-childitem $path).Count -le 0) {
12 remove-item $path
13 return
14 }
15 get-childitem $path | where-object { $_.PsIsContainer} | foreach { prune-directory $_.FullName }
16 if (@(get-childitem $path).Count -le 0) {
17 remove-item $path
18 }
19 }
To use it, just enter:
prune-directory <path>
You should verify that the function works the way you expect before using it. Once your directories or files are deleted, they're GONE.
prune-directory() is a recursive function that walks a directory tree and deletes any empty directory it finds. Lines 3-10 check for invalid parameters, lines 11-14 delete the current directory if it is empty and line 18 calls this function for all children which are containers in the current directory. Lines 19-22 are required in case the current directory has no children because they were all deleted by line 15.
In line 11 and 16, we use @(…) to force the result of get-childitem $path to be an array, otherwise we may not be able to count the number of children in a directory. It's a known - uh - nuance in PowerShell that if a cmdlet finds zero or one object, it returns an scalar value rather than an array.
2008-05-15: This change should fix the problem of escape characters in the path string: test-path -literalPath $path.
Labels: PowerShell, Programming
Sunday, 20 January 2008
PowerShell File Version Information
Compiled files in your Windows computer, such as executables and libraries (or files with a .exe and .dll suffix in their names), can contain some additional information stored in a FileVersion structure. You can see these properties in Explorer's Properties dialog, Details tab.
Before releasing a Windows-based product, I wanted to check that the Copyright and Product Version fields in all compiled files were correctly updated. We always increment the number in Product Version and if the product was released in the start of the year, we also update the Copyright field. You can use Windows Explorer to view these properties in several files at once (just select all relevant files and show the Properties dialog), but if a file had a different value from the others, the Properties / Details tab shows the unhelpful multiple values text. Which file has a value different from the others? It isn't that hard to check each field individually but why not automate the test?
You can use the following PowerShell one-liner to list the Copyright field of all compiled files:
> get-childitem * -include *.dll,*.exe | foreach-object { "{0}`t{1}" -f $_.Name, [System.Diagnostics.FileVersionInfo]::GetVersionInfo($_).LegalCopyright }
To test on the Windows PowerShell folder:
gpowershell.exe Copyright (c) Microsoft Corporation. All rights reserved. powershell.exe © Microsoft Corporation. All rights reserved. pwrshmsg.dll © Microsoft Corporation. All rights reserved. pwrshsip.dll © Microsoft Corporation. All rights reserved.
The first command get-childitem * -include *.dll,*.exe retrieves a list of files in the current directory that have a *.dll or *.exe suffix. The Get-ChildItem cmdlet has a -filter option but it only accepts one pattern.
The second command outputs the filename and the Copyright information of each file using the format (-f) operator. We use the .Net [System.Diagnostics.FileVersionInfo]::GetVersionInfo() method to obtain the file's LegalCopyright (or Copyright) field.
To check Product Version field, use .ProductVersion instead of .LegalCopyright. If you are interested in other fields, check MSDN for a complete list of field returned by GetVersionInfo.
Labels: PowerShell
Wednesday, 9 January 2008
PowerShell and Cat Extract Lines with Line Numbers
I wanted to extract some lines of text, each line prefixed with its line number, from a text file. Frustratingly, while IDEs such as Visual Studio and NetBeans and text editors such as Vim happily (if software were said to have emotion) show line numbers in their display, you can't select the line numbers with text! In the last century, I would use cat -n | head -n1 | tail -n2 and copy the required lines from the output. Fast forward to yesterday where I found myself using cat -n again in PowerShell. This time, I could use Select-Object to extract only the lines I wanted …
> cat -n <file> | select-object -first n1 | select-object -last n2 Get-Content : A parameter cannot be found that matches parameter name 'n'. …
It turns out that cat is aliased to Get-Content, which doesn't process the -n parameter. Shay Levi and Richard Siddaway provided me with some solutions (see newsgroup microsoft.public.windows.powershell, topic "Temporarily ignore alias") and I was on my way again:
> cat.exe -n <file> | select-object -first n1 | select-object -last n2
Of course, since the first command creates an array of strings, you can slice it and end up with a much shorter statement:
> (cat.exe -n <file>)[r1..r2]
Note the following relationship: r1 = n1-n2 and r2 = n1.
But if you don't have cat.exe installed, you can reproduce the behaviour of cat -n with this PowerShell solution:
> get-content <file> | foreach-object { $i=1 } { "{0,4} {1}" -f $i, $_; $i++ }
Let's analyze the second command: -f is the PowerShell format operator which formats the right-hand argument ($i, $_) using the left-hand argument ("{0,4} {1}"). The first format control string ({0,4}) means "format input 0 in 4 spaces, right aligned". The second format control string ({1}) just writes each line without any formatting.
Labels: PowerShell
Tuesday, 1 January 2008
PowerShell Group-Object and Anagrams
In an earlier article, we used an associative array to group words with the same property (in this case, the same set of letters) to find anagrams. While that solution worked, it seemed to me that there should be an easier solution using the Group-Object cmdlet.
> "add", "dad", "dam", "mad", "made", "madam", "set" | group { $_.toCharArray() | sort-object }
Count Name Group
----- ---- -----
2 a d d {add, dad}
2 a d m {dam, mad}
1 a d e m {made}
1 a a d m m {madam}
1 e s t {set}
Looking good, so let's try a bigger set of words in a file:
> get-content test.txt | group-object { $_.toCharArray() | sort-object }
Count Name Group
----- ---- -----
2 a d d {test.txt, test.txt}
2 a d m {test.txt, test.txt}
1 a d e m {test.txt}
1 a a d m m {test.txt}
1 e s t {test.txt}
That's mighty weird. For some reason, the group has the name of the file rather than the actual word while the signature in the Name column is computed correctly. Is the problem to do with the expression for the group-object? Let's try a simpler expression:
> get-content test.txt | group-object { $_.length }
Count Name Group
----- ---- -----
5 3 {test.txt, test.txt, test.txt, test.txt...}
1 4 {test.txt}
1 5 {test.txt}
It's very puzzling and it seems like group-object was treating each file rather than each word as an input. But then, why is the expression being computed for each word?
Even stranger is when you assign the contents of a file to a variable and get the same result!
> $l = get-content test.txt
> move-item test.txt test2.txt #Ensure original file is no longer available.
> $l | group-object {$_.length}
Count Name Group
----- ---- -----
5 3 {test.txt, test.txt, test.txt, test.txt...}
1 4 {test.txt}
1 5 {test.txt}
In this case, I would have thought that group-object would operate on a list of words and not refer to the original file.
Later … .Net has a function string[] ReadAllLines() that returns an array of strings, so the following works a treat:
> [System.IO.File]::ReadAllLines("C:\temp\download\doc\language\test.txt") | group-object {$_.ToCharArray() | sort-object}
Count Name Group
----- ---- -----
2 a d d {add, dad}
2 a d m {dam, mad}
1 a d e m {made}
1 a a d m m {madam}
1 e s t {set}
At least PowerShell's integration with the .Net Framework makes it possible to solve a problem if the pre-defined cmdlets don't work as you expect.
2-Jan-2008. If you're using PowerShell 2.0 CTP, the Get-Content version works.
Labels: DotNet, PowerShell
Friday, 21 December 2007
PowerShell Associative Arrays and Anagrams
Jon Bentley's Programming Pearls describes the following pipeline for finding anagrams from a list of words: generate a signature for the word, then group together all words with the same signature. The signature
is just a sorted list of all letters in a word. For instance, dame
, edam
, made
and mead
all have the same signature adem
.
Associative Arrays
To implement an anagram-finder in PowerShell, let's use an associative array
and we store the signatures in the keys and each word that has the same signature is stored in an array related to that key. Below is a concrete example of what we plan to do:
> $a = @{adem:("dame","edam","made","mead")}
> $a
Name Value
---- -----
adem {dame, edam, made, mead}
Notes
- The key for an associative array does not have to be quoted if it is a string without a whitespace.
- Oddly, arrays in the value column are printed delimited by braces instead of parentheses.
Generating Word Signatures
A word signature is just a string with the letters in the original word sorted. We split a string into a char[] type, sort it, then make it into a string again:
> $sig = [string]("edam".ToCharArray() | sort-object)
> $sig
a d e m
> $sig.length
7
Note that the signature sig is a string with a space between each character. We can prettify the signature but it doesn't hurt because all the signatures will have the same format.
Find Anagrams in a Word List
Now that we can create a signature, we can find all anagrams in a word list by assigning each word's signature as a key in the associative array's value and adding the word to that key's array:
> get-content <test.txt> | foreach-object { $h = @{} } { $t = $_.clone(); $sig = [string]($t.ToCharArray() | sort-object); if (!$h.containsKey($sig)) { $h[$sig] = @() } $h[$sig] += $t } { $h }
Name Value
---- -----
adem {dame, edam, made, mead}
Let's decompose this longish statement to understand what it is doing:
get-content <test.txt> |- Send every line in the input file into a pipeline.
foreach-object- Apply some operation on each object in the pipeline.
{ $h = @{} }- Initialize the associative array h in the begin script block.
- { process block }
- Here is where words with the same signature are grouped together in the associative array.
$t = $_.clone();- Copy the input word from the current pipeline object before it is overwritten in the next statement.
$sig = [string]($t.ToCharArray() | sort-object);- Get the signature of the input word.
if (!$h.containsKey($sig)) { $h[$sig] = @() }- Create an array of anagrams if the signature does not already exist.
$h[$sig] += $t- Add the current word to the array of anagrams.
- { $h }
- Output the associative array h in the end script block.
Conclusion
This article presented an imperative approach to grouping data with the same property using a loop and an associative array. You could apply the same style to any programming language and get the same result. In a future article, I will explore how to use a more streamlined approach to solve similar problems.
Labels: PowerShell, Programming
Wednesday, 19 December 2007
Find Longest (or Shortest) Line in a File
Quick scripts to find the longest line in a file. To find the shortest line, just invert the appropriate test.
Gawk
gawk "{ if (length > max) { max = length; m = $0 } } END { print m }" <file>
Gawk + Sort + Tail
gawk "{ printf("""%4.0f %s\n""", length,$0) }" <file> | sort | tail -1
In Windows Cmd, three double-quotes are required to escape a double-quote, and the %4.0f format control ensures that lines up to 9999 characters long are sorted correctly.
PowerShell
get-content <file> | sort-object -property length | select-object -last 1
WC
wc -L gives the length of the longest line, but not the line itself.
2008-04-20: Consolidated all samples into this article.
Labels: PowerShell
Monday, 17 December 2007
PowerShell String and Char[] Sort and Conversion
Introduction
I wanted to sort the characters in a string to use as a signature for that string. A string is basically an array of characters but System.String class does not have a Sort() method. Hmm, looks like we have to break the process into the following steps:
- Convert a string to a character array.
- Sort the character array.
- Convert the character array back into a string.
First cut
A hitch: System.String can only be converted into a char[], so we have to use an external sorter such as Sort-Object cmdlet. If we could have made an Array of char, then we could have used the Array's Sort() method. The first cut looks like this:
> $s = "mad" > $sig = sort-object -inputObject $s.ToCharArray() > $sig m a d
Eh? Why isn't sig sorted? Either Sort-Object or PowerShell doesn't do the expected when presented with an array using the -inputObject parameter. Let's test this:
> "m","a","d" | sort-object a d m > sort-object -inputObject "m","a","d" m a d
Second Cut
Because of the strange behaviour found in the previous section, we call Sort-Object in a pipeline. In addition, we reconstruct the output into a string:
> $s = "mad" > $sig = (string)($s.ToCharArray() | sort-object) > $sig a d m > $sig.length 5
What's wrong now? Why does sig have a whitespace between each character? This was getting a bit deep into PowerShell for me at the moment, so let's use an appropriate constructor in System.String such as String(char[]).
21-Dec-2007: Solution is to change OFS (Output Field Separator) to an empty string, like this: $OFS = "".
Third Cut
We try the System.String(char[]) constructor and make the sorted array output into a string:
> $s = "mad" > $sig = new-object String(($s.ToCharArray() | sort-object)) New-Object : Exception calling ".ctor" with "3" argument(s): "Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: startIndex" At line:1 char:16 + $t = new-object <<<< String($s.ToCharArray())
What gives? The error indicates that a different constructor, String(char[], startIndex, length) should be used. It's not clear why the first constructor is not available.
Fourth Cut
Finally, taking into account all that we learnt above, we end up with the following statement which gives us the result we wanted:
> $s = "mad" > $sig = new-object String(($s.ToCharArray() | sort-object), 0, $s.length) > $sig adm
Conclusion
The resulting statement to sort characters in a string is mostly noise because the purpose of the statement statement is obscured by the need to convert an object from one type to another. In an earlier article about palindromes, another way to make a string from an character array is to use the static method [string]::Join(). That is …
> $sig = [string]::Join("", ($s.ToCharArray() | sort-object))
The second method is shorter but still rather obscure because it relies on the side-effect of the empty string argument when calling the Join() method. It's a rather disappointing end to this exercise because I spent most of the time fighting instead of using PowerShell.
19-Dec-2007. PowerShell 2.0 will have a new Join operator that should make this exercise moot.
21-Dec-2007. Fourth method is to change OFS first, leading to:
> $OFS = "" > $s = "mad" $gt; $sig = [string]($s.ToCharArray() | sort-object) > $sig adm > $sig.length 3
Labels: PowerShell
Saturday, 15 December 2007
PowerShell Hello World Window
Here's an attempt at a Hello World Window using PowerShell:
# PowerShell Hello World Window
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$b = new-object System.Windows.Forms.Button
$b.Text = "Press Me"
$l = new-object System.Windows.Forms.Label
$l.Text = "A label:"
$l.Size = new-object System.Drawing.Size(50,20)
$l.TextAlign = [System.Drawing.ContentAlignment]::BottomLeft
$p = new-object System.Windows.Forms.FlowLayoutPanel
$p.Controls.Add($l)
$p.Controls.Add($b)
$f = new-object System.Windows.Forms.Form
$f.Text = "Hello World"
$f.Size = new-object System.Drawing.Size(160,70)
$f.Controls.Add($p)
$f.ShowDialog()
Mini Observations
- System.Drawing namespace and assembly is loaded with System.Windows.Forms at the start.
- Unlike IronPython, only the actual .Net constructors for System.Windows.Forms classes are available, so you have to set properties of objects in separate statements after an object is created.
Labels: PowerShell
Monday, 10 December 2007
PowerShell Where-Object Predicates
In an earlier article about palindromes , I wrote an if-statement in the script block of the Where-Object cmdlet. While that's fine when the test is simple, the purpose of the Where-Object is obscured when the script block spans several lines, and it is not possible to re-use the code in the script block.
For example, let's find all prime numbers from a list of numbers. If you follow a top-down programming style, your code might look like this:
> 1..20 | Where-Object { Test-Prime $_ }
Now we have to supply a predicate Test-Prime that returns True if a number is prime, and False otherwise. Below is an example using Eratosthenes Sieve:
> function Test-Prime {
$isprime = $True
foreach ($i in 2..([math]::Sqrt($args[0]))) {
if ($args[0]%$i -eq 0) {
$isprime = false
break
}
}
if ($isprime) {
$args[1] += $args[0]
return $True
} else {
return $False
}
}
Test-Prime requires an array of existing primes, so the resulting script block is slightly different from our original plan. Oh well, can't plan everything in all the time. Let's make the modification and do a test run:
> $primes = (1,2); 1..20 | Where-Object { Test-Prime $_ $primes }
3
5
7
11
13
17
19
Not too shabby. It would be nice if Test-Prime can maintain its own list of prime numbers instead of relying on the caller to create it. I'll revisit this problem in future when I study more about PowerShell.
Labels: PowerShell
Convert Number Bases
Convert a number from base-10 to another base using the .Net Convert.ToString(number, target-base) static function. Test in PowerShell:
> [Convert]::ToString(15,16) f > [Convert]::ToString(15,2) 1111
To convert from base-n to base-10, use .Net Convert.ToInt32(String, source-base). There are also ToInt16() and ToInt64() methods. Examples using ToInt32() below:
> [Convert]::ToInt32("f",16)
15
> [Convert]::ToInt32("1111",2)
15
If you use the wrong source-base, PowerShell shows this error:
Exception calling "ToInt32" with "2" argument(s): "Could not find any recognizable digits." At line:1 char:19 + [Convert]::ToInt32( <<<< "f",2)
Note: Don't mix this class with DOS convert command which converts a filesystem from FAT32 to NTFS!
Labels: PowerShell, Programming
Sunday, 9 December 2007
Find Duplicate Objects in PowerShell
You can find unique lines in a file using Get-Unique but what if you want to know which lines are duplicates? Below are two variations of a one-liner that finds duplicate lines. In both cases, an object is added into an array $a if it does not already exist in that array ($a -contains …). If the object exists in the array (i.e. not unique), it is printed out. A side-effect is that the unique lines are available in $a after the statement is finished.
> $a = @(); foreach ($l in Get-Content <file>) { if ($a -contains $l) { $l } else { $a += $l } }
> Get-Content <file> | Foreach-Object { $a = @() } { if ($a -contains $_) { $_ } else { $a += $_ } }
The first version is implemented using the foreach statement. The $a array is initialized in the first statement, then the file is read into $l one line at a time in the second statement. It's actually a short two-liner.
The second version is implemented using the Foreach-Object cmdlet in a command pipeline. The Foreach-Object can have three command blocks: beginning, middle and ending. The $a array is initialized in the beginning command block ($a = @()) and the test and output executed in the middle command block (if () …). The ending command block is not used in this example. This version is more tidy because only one variable, $a has to be created; the other version requires another variable $l.
Labels: PowerShell
Saturday, 8 December 2007
Powershell Palindromes
A palindrome is a word or phrase that reads the same forwards or backwards. A well-known example is Madam, I am Adam
. Another example is Weird Al
Yankovic's song Bob
, which is composed of rhyming palindromes. If you have a list of words in a file, here's how you can find palindromes using a one-liner in PowerShell.
> Get-Content <file> | Where-Object {[string]::Join("",$_[-1..-$_.Length]) -eq $_ }
…
aha
ala
dud
mm
mum
pap
pip
…
The one-liner translates to: for every line in an input file, display only lines which are the same forward and backward
.
The complex part of this pipeline is the filter in Where-Object. In this filter, we …
- Convert the input string, $_, into an array of characters in reverse order: $_[-1..-$_.Length].
- Convert the array back into a string by using the Join() function with an empty separator: [string]::Join("",<array>).
- Compare the reversed and original strings using <s1> -eq <s2>.
Where-Object only outputs a line when comparison operator returns True.
Labels: PowerShell
Slicing PowerShell Arrays and Ranges
Introduction
You can slice (or extract elements) from an array in PowerShell in several ways. Let's start with a simple array of numbers. We start with 0, because PowerShell's array indices start from 0 and to make it easier to see the effect of each statement. We use the Write-Host cmdlet where necessary to keep the number of lines in the output to a minimum.
> $a = 0,1,2,3,4,5 > $a.length 6 > Write-Host $a 0 1 2 3 4 5
The rest of this article describes how to obtain a single item, multiple items and a reverse slice, and how to combine an index list with a range.
Get an Item
Let's retrieve an item from different indices in our array, $a:
> $a[0] 0 > $a[$a.length] > $a[-$a.length-1] > $a[$a.length-1] -eq $a[-1] True > $a[$a.length-2] -eq $a[-2] True
The first item is in index 0 and the last index is $a.length-1. Also, $a.length-x is the same as -x. If you try to access an index value greater than $a.length-1 (e.g. 6) or less than -$a.length (e.g. -7), you will get nothing.
Get Arbitrary Set of Items
We can also retrieve multiple items and items more than once.
> Write-Host $a[0,-1,5,2,4,1] 0 5 5 2 4 1
Get Multiple Items Using a Range
We can also retrieve a range of values using the .. (dot-dot) operator.
> Write-Host $a[0..2] 0 1 2 > Write-Host $a[2..5] 2 3 4 5 > Write-Host $a[0..($a.length-1)] 0 1 2 3 4 5
We enclose the arithmetic expression in parentheses otherwise the command processor generates the error below. It seems like 0..$a.length-1 is expanded into an range and the length method is called for the last number. The error seems a bit unexpected because I would have thought that the binary minus operator had a higher precedence than the range operator. Also, the number converted into System.Object, doesn't have a length method, and PowerShell would have reported that error instead of an error about the op_Subtraction method.
> write-host $a[0..$a.length-1] Method invocation failed because [System.Object[]] doesn't contain a method named 'op_Subtraction'. At line:1 char:28 + write-host $a[0..$a.length-1 <<<< ] >> write-host 0..$a.length-1 0..0 1 2 3 4 5.length-1
Get a Reversed Slice
We can also get a reversed slice of the array by specifying the right index before the left index in the range.
> Write-Host $a[5..2] 5 4 3 2 > Write-Host $a[($a.length-1)..0]
We know that -1 represents the last item, so can we simplify the second statement?
> Write-Host $a[-1..0] 5 0
What went wrong? How does PowerShell interpret -1..0?
> -1..0 -1 0 > Write-Host $a[-1] $a[0] 5 0
The range operator expanded -1..0 to 5 and 0. Is there another way specify a reverse slice of the original array? Let's try the following line of reasoning.
- From first section, $a.length-x refers to the same item as -x
- Replace x with $a.length
- So $a.length-$a.length (or 0) refers to the same item as -$a.length
> Write-Host (-1..-$a.length) -1 -2 -3 -4 -5 -6 > Write-Host $a[-1..-$a.length] 5 4 3 2 1 0
Combining Numeric and Range Indices
Since an array slice can be specified by either a list of numbers or a range, can we combine them?
> Write-Host $a[0,3,2..5] Cannot convert "System.Object[]" to "System.Int32". At line:1 char:22 + Write-Host $a[0,3,2..5 <<<< ] > 0,3,2..5 Cannot convert "System.Object[]" to "System.Int32". At line:1 char:8 + 0,3,2..5 <<<< > Write-Host 0,3,(2..5) 0 3 2 3 4 5 > Write-Host $a[0,3,(2..5)] 0 3
In the first and second statements, the comma array constructor operator has higher precedence than the range operator, so PowerShell could not create the list from 2..5. In the third statement, we add parentheses to modify the precedence so that the range operator creates a list 2 3 4 5.
However, in the fourth statement, it seems like PowerShell has ignored the list generated by the range operator because only two items are displayed. You can combine a range with a list of indices using the + (Plus) operator. The syntax is a little inconsistent because you can produce an index list without this operator:
> Write-Host $a[0,3+2..5] 0 3 2 3 4 5
Furthermore, you have to use the Plus operator before and after a range:
> Write-Host $a[0,3+2..5,1] Cannot convert "System.Object[]" to "System.Int32". At line:1 char:22 + write-host $n[0,3+2..5 <<<< ,1] > write-host $n[0,3+2..5+1,4] 0 3 2 3 4 5 1 4
Conclusion
This article has demonstrated how to slice arrays in PowerShell, starting from single item slices, progressing to multiple items, using the range operator and described two approaches to obtain a reversed list of items from an array. We also found that you have to use the Plus operator to combine the comma array constructor operator and range operator to get an array slice.
Labels: PowerShell
Tuesday, 4 December 2007
PowerShell Directories, Collection and Range Operator
What are the biggest files in a directory? Using the Select-Object Cmdlet provides this command chain:
Get-ChildItem c:\windows\*.* | Sort-Object length -descending | Select-Object -first 3
In the article, the first two commands produce a collection and Select-Object picks the first three items. It's a little tedious having long command chains, so can we make it shorter? Since we have a collection, we can access its items using the array syntax and an index:
(Get-ChildItem c:\windows\*.* | Sort-Object length -descending)[3]
Using the dot-dot range operator (..), we can generate a list of indices using [0..2] (collections start with a 0 index) and reproduce the effect of Select-Object -first 3:
(Get-ChildItem c:\windows\*.* | Sort-Object length -descending)[0..2]
What about getting last 3 items using Select-Object -last 3? It can be replaced by [-3..-1]. Note that the last item in a collection starts from -1.
(Get-ChildItem c:\windows\*.* | Sort-Object length -descending)[-3..-1]
Labels: PowerShell
Sunday, 25 November 2007
Beginning PowerShell
Introduction
Microsoft's PowerShell is a scripting environment based on the .Net framework. PowerShell is aimed at replacing Windows Cmd and Windows Scripting Host (WSH) for administrative tasks. What interests me is that I can use .Net objects interactively and in a script, without having to write and compile a program. As I explore PowerShell, I'll write about features that I find interesting and useful.
In this first article, I describe really basic use of the PowerShell console. I assume you downloaded and installed Powershell.
Help!
Powershell installation provides a lot of on-line help. You obtain information using the get-help cmdlet (essentially a built-in command). There's so much information that the trick is to figure out the appropriate help topic:
> help # or 'get-help'
< 299 lines … >
> help about_Arithmetic_Operators
TOPIC
Arithmetic operators
SHORT DESCRIPTION
Operators that can be used in the Windows PowerShell to perform
mathematical operations
…
You can also download the on-line help as a CHM file (search for "Windows PowerShell Graphical Help File").
Interactive Mode
You can enter commands for processing immediately:
> 'abc' + 'def' # String concatenation abcdef > 5+6 # Addition 11 > 5*6 # Multiplication 30 > 30%7 # Remainder 2
The '#' marks the start of a comment. All text from '#' to the end of the line is a comment.
Using .Net Classes and Objects
How long is a string (said with a straight face)? Since we are using .Net String objects, each string should have a Length property:
> 'abc'.Length
3
> 'def'.Length
3
> ('abc' + 'def').Length
6
You can find the methods of an object using get-member.
> 'abc' | get-member TypeName: System.String Name MemberType Definition ---- ---------- ---------- … get_Length Method System.Int32 get_Length() …
What's 2 to the power of 8? The .Net System.Math class defines a set of common mathematical functions. Again, you can use the get-member cmdlet to find out what functions are available in any class, but this time, with the -static option because the class, not individual objects, define these functions.
> [math] | get-member -static TypeName: System.Math Name MemberType Definition ---- ---------- ---------- … Pow Method static System.Double Pow(Double x, Double y) … > [math]::Pow(2,8) 256
The System.Math is pre-loaded by PowerShell and can be accessed as [System.Math], [Math] or [math]. Square brackets refer to .Net classes and some aliases have been defined for these classes.
How do you know whether an object or a class has the required function? No easy answer: trial-and-error, and experience with other class libraries.
Tab Expansion and Aliases
It's pretty tedious typing a full cmdlet string, so there are two shortcuts available: tab expansion and aliases.
If you enter get-m<TAB>, you should see Get-Member. For some reason, you have to enter the name of cmdlet up to the '-' (dash) character before tab expansion works.
The get-member cmdlet also aliased as gm. What aliases are available? Use the get-alias cmdlet, which is also aliased as gal, for a list of available aliases.
> Get-Alias CommandType Name Definition ----------- ---- ---------- Alias gal Get-Alias … Alias gm Get-Member …
Conclusion
At this point, you can run some simple commands using PowerShell and know how to find some on-line information.
Labels: PowerShell
Del.icio.us
Stumble It!


