mchist
mchist

Reputation: 132

"If" non exist in foreach loops - what?

When I invoke an if comandlet in a foreach loop:

$spisok = Get-Content "OmConv79_2.txt" -TotalCount 100
foreach ($i in $spisok) {
    #Write-Host $i
    $i -match "^(.*)\t(.+)\t?(.*)?" |
        foreach { $Matches[1] + "----" + $Matches[2] + "----" + $Matches[3] } |
        if ($Matches[1] -eq "^\d\d.\d\d.\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d") {
            $Matches[0]
        }
}

I'm getting this error:

if : The term 'if' is not recognized as the name of a cmdlet, function, script file, or
operable program. Check the spelling of the name, or if a path was included, verify that
the path is correct and try again.
At line:7 char:9
+         if ($Matches[1] -eq "^\d\d.\d\d.\d\d \d\d:\d\d:\d\d\.\d\d\d\d ...
+         ~~
    +         CategoryInfo          : ObjectNotFound: (if:String) [], CommandNotFoundException
    +         FullyQualifiedErrorId : CommandNotFoundException

screenshot

"if" is not recognized? I'm confused. How is that possible?

Upvotes: 1

Views: 2391

Answers (1)

mklement0
mklement0

Reputation: 439228

You cannot use an if statement directly in a pipeline.

Your 2nd use of foreach is not a loop, but a call to the ForEach-Object cmdlet in a pipeline.
Regrettably, the ForEach-Object cmdlet is aliased to foreach, causing confusion with the foreach keyword (loop construct).

To incorporate an if statement (conditional) in a pipeline, you have two options, depending on your needs:

  • If the if statement only performs filtering of the input while - selectively - passing it through, use the conditional in the script block of a Where-Object call (aliases: where, ?)

  • If the if statement also produces custom output, place it inside the script block of a ForEach-Object call (aliases: foreach, %).

Simplified examples:

# !! These BREAK - you cannot use `if` directly in a pipeline.
'line 1', 'line 2' | if ($_ -match '1') { $_ }       # filtering only
'line 1', 'line 2' | if ($_ -match '1') { $_ + '!' } # filtering + custom output

Filtering-only solution, using Where-Object:

$ 'line 1', 'line 2' | Where-Object { $_ -match '1' }
line 1

Filtering + custom-output solution, using ForEach-Object:

$ 'line 1', 'line 2' | ForEach-Object { if ($_ -match '1') { $_ + '!' } }
line 1!

As for your code:

From what I can tell (including based on later comments), this may be what you meant to do:

Get-Content "OmConv79_2.txt" -TotalCount 100 |
  ForEach-Object { # process each line, represented as $_
    if ($_ -match '^(.*)\t(.+)\t?(.*)?') {
      # Is the first tab-separated field a date + time?
      if ($Matches[1] -match '^\d\d.\d\d.\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d') {
        $Matches[0] # Output the first 3 tab-separated fields.
      } else {
        # Output line with default date prepended
      }
    }
  }
  • Note the use of a single pipeline, with a single ForEach-Object call to process each input line.

  • -eq had to be replaced with -match to match the ^\d... regex.

  • The regexes are single-quoted, so that PowerShell's string interpolation doesn't get in the way.

Upvotes: 3

Related Questions