foreach loop include next line

Welcome Forums General PowerShell Q&A foreach loop include next line

This topic contains 17 replies, has 4 voices, and was last updated by

Pj
 
Participant
3 months, 3 weeks ago.

  • Author
    Posts
  • #104953
    Pj

    Participant
    Points: 0
    Rank: Member

    i have the following script to get lines of interest. the issue i have is just the one matching PRT0009 i need one additional following line for the rest of the error message im not having luck trying to use movenext. note this needs to be done in order just as the foreach loop would produce as is without the second line.

    $Content = "C:\temp\run.txt"
    
     get-content $Content -ReadCount 1000 | foreach { 
     $_ -match "PRT0004" 
     $_ -match "PRT0005"
     $_ -match "PRT0009" 
     $_ -match "ENG0029"
     $_ -match "ENG0037"
     } | Out-File "C:\temp\Results.txt"
    
  • #104957

    Keymaster
    Points: 1,619
    Helping HandTeam Member
    Rank: Community Hero

    So... as0s, all this is going to do is produce a bunch of "True" or "False" outputs. There's nothing stopping every single line from running, though.

    Are you sure you want -match and not -like? -Match is for regular expressions and you don't appear to be using one.

    What's the goal? What would you ultimately like your output to look like?

    • #104960
      Pj

      Participant
      Points: 0
      Rank: Member

      i want to output all lines from a log file containing one of the -match "errorIDs" in order. an example line looks like this...

      2018-06-19 06:58:46[ERROR[SYS.Base.Run.Engine[PRT0009 – There is a communications problem between the PC and the printer. The following error has been generated:

    • #104987
      js

      Participant
      Points: 202
      Helping Hand
      Rank: Participant

      I would do it this way. Often in powershell parameters can be a list.

      get-content c:\temp\run.txt | select-string prt0004,prt0005,prt0009,eng0029,eng0037 | 
        Out-File c:\temp\results.txt

      Or if you want the filename and linenumber:

      select-string prt0004,prt0005,prt0009,eng0029,eng0037 c:\temp\run.txt | Out-File c:\temp\results.txt

      "get-content -ReadCount 1000" would put 1000 lines together on one line. I'm not sure that's what you intend.

      With a bit of regex you can do it this way too:

      get-content c:\temp\run.txt | where-object { $_ -match 'prt0004|prt0005|prt0009|eng0029|eng0037' } | 
        out-file c:\temp\results.txt
    • #104992
      Pj

      Participant
      Points: 0
      Rank: Member

      how would this handle only one pattern having a -context 0,1? i have modified things to use something similar but still cannot find a way to get that extra line for only specific patterns while maintaining order of events.

      $Content = "C:\temp\run.log"
      $Output = "C:\temp\Log.txt"
      $Pattern = 'PRT0004', 'PRT0005', 'PRT0009','ENG0029', 'ENG0037'
      
      Get-Content $Content | Select-String -pattern $Pattern | Out-File $Output
      
      Start notepad++ $Output
      
    • #104995

      Participant
      Points: 159
      Helping Hand
      Rank: Participant

      Hrmm... nah, there's a simpler way if you just want all lines containing any of those error IDs to be pulled out. No looping, no select-string. Just -match:

      $File = Get-Content 'C:\temp\run.txt'
      $File -match "PRT(0004|0005|0009)|ENG(0029|0037)" | Set-Content -Path 'C:\Temp\Log.txt'
      Start-Process -FilePath 'Notepad' -ArgumentList 'C:\Temp\Log.txt'

      I did group the match string a bit to minimise unnecessary match steps for the regex parser, but you could just as easily have your match string be all your error IDs delimited by pipe symbols (pipe is the regex symbol for 'or').

      This will return all lines from the log file that contain one of the IDs and store them into the file specified.

      -match, much like every other operator that doesn't normally get used with arrays, will pull out every array entry that matches the requisite pattern if applied to an array.

    • #104996
      Pj

      Participant
      Points: 0
      Rank: Member

      i like the clean code but doesnt address only PRT0009 needing the next line in the file as well

    • #104999
      js

      Participant
      Points: 202
      Helping Hand
      Rank: Participant

      You'll have to do something with a foreach and if statements.

    • #105001
      Pj

      Participant
      Points: 0
      Rank: Member

      started with that but couldnt get it to behave

    • #105002

      Participant
      Points: 159
      Helping Hand
      Rank: Participant

      Ah, sorry, I missed that requirement. Hmm. Let's see. This will require a bit of a loop, then:

      $File = Get-Content 'C:\temp\run.txt'
      $File | ForEach-Object {
          if ($_ -match "PRT(0004|0005|0009)|ENG(0029|0037)") {
              if ($Matches[0] -eq "PRT0009") {
                  $LineIndex = $File.IndexOf($_) + 1 # Get Next Line
                  $Output = $_, $File[$LineIndex]
              }
              else { $Output = $_ }
              $Output
          }
      } | Set-Content -Path 'C:\Temp\Log.txt'
      Start-Process -FilePath 'Notepad' -ArgumentList 'C:\Temp\Log.txt'
    • #105014
      Pj

      Participant
      Points: 0
      Rank: Member

      There it is that works beautifully thank you for the help!

      answer...

      $File = Get-Content 'C:\temp\run.txt'
      $File | ForEach-Object {
          if ($_ -match "PRT(0004|0005|0009)|ENG(0029|0037)") {
              if ($Matches[0] -eq "PRT0009") {
                  $LineIndex = $File.IndexOf($_) + 1 # Get Next Line
                  $Output = $_, $File[$LineIndex]
              }
              else { $Output = $_ }
              $Output
          }
      } | Set-Content -Path 'C:\Temp\Log.txt'
      Start-Process -FilePath 'Notepad' -ArgumentList 'C:\Temp\Log.txt'
      
    • #105017
      Pj

      Participant
      Points: 0
      Rank: Member

      There it is that works beautifully thank you for the help!

      answer...

      $File = Get-Content 'C:\temp\run.txt'
      $File | ForEach-Object {
          if ($_ -match "PRT(0004|0005|0009)|ENG(0029|0037)") {
              if ($Matches[0] -eq "PRT0009") {
                  $LineIndex = $File.IndexOf($_) + 1 # Get Next Line
                  $Output = $_, $File[$LineIndex]
              }
              else { $Output = $_ }
              $Output
          }
      } | Set-Content -Path 'C:\Temp\Log.txt'
      Start-Process -FilePath 'Notepad' -ArgumentList 'C:\Temp\Log.txt'
      
  • #105019
    js

    Participant
    Points: 202
    Helping Hand
    Rank: Participant

    This is what I was thinking:

    $list = echo prt0004 prt0005 prt0009 eng0029 eng0037 
    
    $out = foreach ($pattern in $list) {
      if ($pattern -eq 'prt0009') {
        select-string $pattern run.txt -context 0,1
      } else {
        select-string $pattern run.txt
      }
    }
    
    $out | out-file results.txt
    • #105025

      Participant
      Points: 159
      Helping Hand
      Rank: Participant

      That looks pretty solid, yep. I'm not too familiar with Select-String, but it looks like that ought to work.

      Not sure why you're using echo to create an array, though; commas aren't that hard to come by! 😉

    • #105026
      js

      Participant
      Points: 202
      Helping Hand
      Rank: Participant

      And you have to quote every word. Frig that.

      You can't pipe from foreach, which is frustrating. But you can save it to a variable.

  • #105029

    Keymaster
    Points: 1,619
    Helping HandTeam Member
    Rank: Community Hero

    You can pipe out of ForEach-Object, but not the ForEach{} scripting construct. Anything in a ForEach-Object block that is written the pipeline will be emitted to the next command in the pipeline.

    But the "right" pattern would be to build your own function to perform the filtering, right? The ForEach becomes implicit when you pipe input and process it in a PROCESS{} block.

    function Where-Whatever {
     [CmdletBinding()]
     Param(
      [Parameter(ValueFromPipeline=$True)]
      [string]$InputObject
     )
     PROCESS {
      if ($InputObject -eq 'prt0009') {
        select-string $InputObject run.txt -context 0,1
      } else {
        select-string $InputObject run.txt
      }
     }
    }
    
    'echo', 'prt0004', 'prt0005', 'prt0009', 'eng0029', 'eng0037' | 
    Where-Whatever | 
    out-file results.txt
    

    That way your pattern-matching business becomes a self-contained tool, which can be used in a composed command line with other tools like Out-File. And then your inputs don't necessarily have to be quoted, right? Put one per line in a text file and:

    Get-Content input.txt | Where-Whatever | Out-File results.txt

    Or whatever. Hopefully that conveys the general gist.

    • #105032
      js

      Participant
      Points: 202
      Helping Hand
      Rank: Participant

      Needs more quotes and commas.

    • #105035
      Pj

      Participant
      Points: 0
      Rank: Member

      Most likely i will put this into a function as this part is just data gathering for a WPF GUI. The txt file output is just for me to evaluate results at this point. Going forward i intend to write a form of analytics to narrow down based on errors present and such what things the end user needs to be looking for as most likely cause of the problem on the machine. as things are now a flow chart to diagnose without understanding of the codes is a needle in a haystack, all this will still only get a general that direction pointer but how much better can emulated serial port communication get.

The topic ‘foreach loop include next line’ is closed to new replies.