Tuesday, April 30, 2013

Powershell DIY: Reading every other line in a file


     Recently I have come upon a situation that required that I read every other line in a file.  While this may sound unusual, believe me, it is more common than you may think.  As such I had to come up with a Do – While loop.  This loop simply put says “do ‘A’ while ‘B’ is true” 

     First, as usual we need to create a source file.  This file can contain anything, for our purposes we are going to use this:



Now we are going to define our first variable:

          $source =

This variable is going to have to be an array by using the ampersand (@) followed by parentheses ():

          $source = @()

     Inside the parentheses we need to tell Powershell what exactly is going to be in our array.  Since we are using the contents of the source.txt file, we need to use the Get-Content commandlet to tell it to look in the file, and grab everything in there:

          $source = @(Get-content source.txt)

     Now that we have our array defined, we can define its length.  This is needed as an escape for our loop.  For this we are going to use another variable:

          $length

     And define it with a property of the $source array.  Specifically, we want its length.  To get the property of a variable, simply append a period (.) followed by the attribute:

          $length = $source.length

     Now we need the counter.  A counter is a common piece of code that increments every time an action is taken.  It has two parts, the first is the initial definition, and the second is the incrementing commandlet.  General practice is to use “I” for an initial counter, “j” for a nested counter, and so on and so forth.  Four our purposes we are only using I, and we are setting it with the initial value of “0”:

          $i = 0

So far we have defined our variables with this block of code:

          $source = @(get-content source.txt)
          $length = $source.length
          $i = 0

     Now we are going to initiate the do-while loop with the “do” commandlet.  “Do” must always be followed by braces {} to encapsulate what we intend for it to do.  This tells Powershell what it is exactly you want done while the “While” condition is true:

         Do{}

     Because we want to count every other line we need to set some conditional statements.  For this exercise I am going to use the “If” condition.  If must always be followed by two things, a condition, and an action.  The condition is encapsulated in Parentheses (), and the action is encapsulated in braces {}

          Do{
          If(){}

          }

     We want to tell Powershell “If the line is even, then tell me it is even.” For this we need to define what is odd.  In basic arithmetic an even number is divisible by two with no remainder.  An odd number is divisible by two with a remainder of one.  The key here is the remainder.  It is always either a 1 or a 0.  This is convenient as it gives us a clearly defined variable that is always either true or false.  Four this we use the Percentile operator (%) followed by a 2.  The % returns the remainder rather than the product of a division, and as such is only a 1 or a 0 if we divide by 2.  This needs to be encapsulated in yet another nested set of parentheses ()


     Between the last two closing parentheses we need to give power shell the condition: “ if the result of the aforementioned formula is equal to “0”.  In order to indicate this we need to use the –eq (Equals) operator followed by the value of 0:

          If(($i%2) –eq 0){}


     So we have told Powershell that If the reminder of the counter $i divided by 2 is 0, then do this.  “This” is what we need to define now.  We enter in our action inside the braces that follow the condition statement.  For us, we are only going to have Powershell display the line, and tell us if the line is even or odd.  So we use the Write-Host Commandlet to tell it to display the output:

          {
          Write-Host
          }

     Now we need to tell it what to write.  Start with the source array variable, and in brackets tell it which item to select with the counter variable $i.  this tells Powershell that we are selecting the $i item in the array:

          {
         Write-Host $source[$i]
          }

     And to prove our output is correct, we are going to append “is odd” in quotations after this, remember to have the space between them or else Powershell will think it’s part of the $source[$i] variable:

          {
         Write-Host $source[$i] "is odd"
          }

Roll that together with the condition and you should come up with:

          If(($i%2) -eq 0){
         Write-Host $source[$i] "is odd"
          }

     Now that we have gotten this far we need to rinse and repeat with the even numbers, for this all we change is the –eq value from a 0 to a 1 and change the odd to an even:

          if(($i%2) -eq 1){
         Write-Host $source[$i] "is even"
          }

Combine them to get a solution for both possible vaues:

          if(($i%2) -eq 0){
          Write-Host $source[$i] "is odd"
          }
          if(($i%2) -eq 1){
          Write-Host $source[$i] "is even"
          }

     And at the end we need to increment the counter.  This is the second part of a counter function.  All that is needed is the variable name, $i, and a “++” appended to it to tell Powershell to add 1 to the current value:

          $i++

     Once we have accomplished this we nest this inside the braces following the Do commandlet, and we have our do statement:

          do{
          if(($i%2) -eq 0){
         Write-Host $source[$i] "is odd"
          }
          if(($i%2) -eq 1){
         Write-Host $source[$i] "is even"
          }
          $i++
          }

     With any do statement we need to give it an ending condition as I stated earlier.  This tells Powershell to do “this” until “that” condition is met.  For this we use “While”.  While is always followed by parentheses that encapsulate the desired condition:

          While()

     For our script, we want it to end at the end of the file, which we defined by its length. So we say “so long as the counter $i is less than the length ($length)of the file”  this is accomplished with the –le (Less than) operator:

          While ($i -le $length)

When we append this to the end of the script, and ensure that the variables are before it, it should look like this:

$source = @(Get-Content source.txt)
$length = $source.length
$i = 0
Do{
If(($i%2) -eq 0){
    Write-Host $source[$i] "is odd"
}
if(($i%2) -eq 1){
    Write-Host $source[$i] "is even"
}
$i++
}
While ($i -le $length)

But Wait!  The math is all wrong! If the counter has a remainder of 0 it is even, yet I stated that it is even!  This is intentional.  Arrays start at 0 so they are offset by one.  So 0 is the first position, 1 is the second position, so on and so forth.

In plain English what we have done is this:

“Read this file, and tell me if every line is either even, or odd.  Do this until the end of the file. ”

And there you have it.  Should you have any questions, or even a better way to do this, do not hesitate to let me know.  If you are as fast as me with a mouse, it takes a second to +1, Like, Tweet, Share.



3 comments:

  1. Nice post! Here is another way:

    $r = Get-Content .\test.txt

    for($idx=0; $idx -lt $r.count; $idx+=2) {
    $r[$idx]
    }

    Doug
    http://dougfinke.com

    ReplyDelete
  2. Doug,

    Thank you for the comment! This is exactly why I post these DIY articles, as there is Always another way to do something. And BTW, I have referenced your book before for production scripts.

    -Rob Stevens

    ReplyDelete
  3. Hi Rob,
    why not using this simple form:
    Get-Content -path "Textfile" | where-object {($_.ReadCount % 2) -eq 0 }
    Kind regards,
    Andreas

    ReplyDelete