Section 3.5. Do Repetitive Work with Loops


3.5. Do Repetitive Work with Loops

If there's one thing computers are good at, it's executing the same code over and over again. Looping is a powerful concept that can simplify many repetitive tasks. Using a loop, we can write a single block of script that is executed a multiple number of times based on some defined conditions. This lets us focus on getting the real logic working correctly in a single place and then easily scaling it out or applying it as needed. No longer are we constrained to the copying and pasting command sequences to perform the same actions on different files, folders, machines, or settings. Better still, any changes to the logic made once centrally will then be applied in each iteration the next time the script is run.

In the following examples, we'll look at populating a file structure with some report templates. We'll start by creating a report placeholder for each week and then move on to separating them out month by month.

Make MSH Work for You

Looping is a great way to save time: write the logic once and run it over and over again. The pipeline has allowed us to perform the same tasks repeatedly on different objects. When piping the output of get-process to where-object, MSH is running the where-object test on each object that flows through the pipeline.

The explicit looping language constructs we'll see herefor, foreach, and whileallow us to use the same kind of functionality on variables without invoking the pipeline directly.


3.5.1. How Do I Do That?

We'll use a couple of files in the following examples as sample report templates. From the interactive mode, enter the following commands to set up the environment for this example:

     MSH D:\MshScripts> new-item -Name "Reports" -Type Directory     MSH D:\MshScripts> "Weekly report template" >WeeklyTemplate.txt     MSH D:\MshScripts> "Monthly report template" >MonthlyTemplate.txt

Now, with an empty Reports directory into which we'll soon place some reports and a couple of simple templates, we're ready to go. Fire up your favorite text editor and create a file called new-weeklyReports.msh with the script in Example 3-4.

Example 3-4. new-weeklyReports.msh
 for ($week=1; $week -le 52; $week++) {     copy-item "WeeklyTemplate.txt" "Reports\Week $week.txt" }

Run the script by typing the following command:

     MSH D:\MshScripts> ./new-weeklyReports.msh

You can confirm that the script ran properly by opening the Reports folder in Explorer. Inside you should find 52 neatly arranged reports just waiting to be written.

Next, we'll take a look at the while construct. The script in Example 3-5 creates 12 directories and puts a copy of the template in each.

Example 3-5. copy-reportsToMonths.msh
 $month=1 while ($month -le 12) {     $monthfolder = "2005-" + $month.ToString("00")     # create the month folder     new-item -Path "Reports" -Name $monthfolder -Type Directory     # copy the template into it     copy-item "MonthlyTemplate.txt" "Reports\$monthfolder\Summary.txt"     $month += 1 }

After running the script, you should see this output:

         Directory: FileSystem::D:\Reports     Mode    LastWriteTime     Length Name     ----    -------------     ------ ----     d----   Aug 19 00:55             2005-01     d----   Aug 19 00:55             2005-02     ...     d----   Aug 19 00:55             2005-11     d----   Aug 19 00:55             2005-12

The command shell is giving feedback indicating that it has created each of the folders in the list. In reality, each time the new-item cmdlet runs successfully, it is putting a DirectoryInfo object into the pipeline, which MSH then displays in tabular form. Sure enough, the Reports folder in Explorer now shows an additional 12 subfolders, each containing a Summary.txt file.

Now, suppose you want to extend this system to include monthly reports filed by individual departments. This new requirement is easy to accommodate by making just a few modifications to the script. You start by creating an array to list the different departments and using the foreach statement to loop over each in turn, repeatedly executing a command block. The whole script is included in Example 3-6.

Example 3-6. copy-reportsToMonthsAndDepts.msh
 $departments = ("Sales", "Marketing", "Research", "Support") $month=1 while ($month -le 12) {     $monthfolder = "2005-" + $month.ToString("00")     new-item -Path "Reports" -Name $monthfolder -Type Directory     copy-item "MonthlyTemplate.txt" "Reports\$monthfolder\Summary.txt"     foreach ($department in $departments)     {         copy-item MonthlyTemplate.txt "Reports\$monthfolder\$department.txt"     }     $month += 1 }

Running the script shows the same output in the command-shell window as the script in Example 3-5, indicating that each of the folders has been created. However, looking into the folders this time, the script modification has created an extra four reports in each month's folder.

3.5.2. What Just Happened?

We've looked at three different ways to have the command shell repeatedly execute a script block.

3.5.2.1. The for loop

Think of the for statement as having four parts:

     for (<initialization>; <condition>; <repeat command>)     { <block> }

The first three are included in the parentheses after the for statement and are separated by semicolons (although a newline can be used to separate them, too). The fourth, the script block, is enclosed in brackets and can contain any number of MSH commands.

The first part, the initialization statement, allows us to set up any variables we'll use in the loop. In this example, we defined a variable $week that started at 1.

The second part, the condition, is checked each time before the code block is run. If it evaluates to false, the loop is finished and execution moves to the line after the script block. If the condition isn't true when the loop is entered, the block will never run. In this case, you want the script to run until it reaches a week that is not less than or equal to 52.

The third component, the repeat command, is executed each time the end of the block is reached. This can be used to update any variables so that the loop runs differently the next time around. In our example, we need to walk through each week of the year, so we incremented the value of $week by one with each iteration.

The content of the block will be executed each time the loop is run.

3.5.2.2. The while loop

The while statement has just two parts, the condition and the block:

     while (<condition>)     { <block> }

Each time the loop is entered, the condition is evaluated. If it returns true, the block is run. The block will be run over and over until the condition no longer holds.

With both for and while loops it's important to make sure your condition will at some point not be trueotherwise, your script will be stuck in an endless loop, repeating the block until it is interrupted. This can be particularly bad if, for example, you're writing a new file to disk, printing a document, or sending an email inside the block. Always make sure that your loop will end when its work is done.

3.5.2.3. The foreach loop

The foreach statement iterates the elements of an array. Although this task could also be achieved with a for or while loop, the foreach statement is usually significantly easier to read and often much more efficient:

     foreach (<element> in <elements>)     { <block> }

On the first iteration, the variable named in <element> will be populated with the first item in the array. When the block finishes its first run, the variable <element> will be updated to contain the second element, and so on.

It was convenient in this example to define an array to contain the different departments as it could easily be extended in one place if the need were to arise in the future.

3.5.2.4. Formatting strings

For the curious, the $month.ToString("00") command returns a string that is formatted with a leading zero. This uses a feature of the string class in the .NET Framework. We'll look at some more uses of the Class Library in Chapter 5.

3.5.3. What About...

...Using the if statement within a loop? Absolutely. There are plenty of cases where a loop seems like a great solution but leaves you saying, "Do this each time except when ...." Enter the break and continue commands. The sole purpose of these two commands is to disrupt the flow of execution. The break command immediately skips to the end of the innermost loop and doesn't come back. The script will start running immediately after the closing brace of the for/while/foreach block. The continue command also skips to the end of the innermost loop, but the loop may run again if the condition still holds true (or if there are additional elements in the array for a foreach loop). For example, the following script will skip every number divisible by 10:

     foreach ($i in (1..100))     {         if (($i % 10) -eq 0) { continue; }         "$i is not divisible by ten"     }

Given three different constructs for building a loop, it's sometimes hard to pick the right one. The good news is that you're free to choose whichever construct best fits your needs. The previous while example could also be written to use a for loop (for ($month=1; $month -le 12; $month++)) or a foreach statement (foreach (1..12)).

The foreach-object cmdlet, which translates the foreach functionality into the pipeline, can be used to run a script block against every object in the pipelinefor example: get-process | foreach-object { $_.HandleCount >"$($_.ProcessName).txt" }. The foreach-object cmdlet is aliased as foreach for convenient use in the pipeline.

3.5.4. Where Can I Learn More?

As always, there are built-in help pages for each of the three looping constructs (about_for, about_while, and about_foreach). The execution flow commands are also there (about_break, about_continue). Looping with for and while relies heavily on conditional tests we explored earlier.

The string-formatting trick that used ToString is just one of the many things possible with the .NET Framework String class. More details about custom formatting of numeric strings are on MSDN: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconcustomnumericformatstrings.asp.




Monad Jumpstart
Monad Jumpstart
ISBN: N/A
EAN: N/A
Year: 2005
Pages: 117

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net