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: ,


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:


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: ,


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:


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:


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: ,


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: ,


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:


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:

  1. Convert a string to a character array.
  2. Sort the character array.
  3. 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:


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:


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:


 

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: ,


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:


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 …

  1. Convert the input string, $_, into an array of characters in reverse order: $_[-1..-$_.Length].
  2. Convert the array back into a string by using the Join() function with an empty separator: [string]::Join("",<array>).
  3. Compare the reversed and original strings using <s1> -eq <s2>.

Where-Object only outputs a line when comparison operator returns True.

Labels:


 

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.

  1. From first section, $a.length-x refers to the same item as -x
  2. Replace x with $a.length
  3. 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:


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:


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:


This page is powered by Blogger. Isn't yours?