Dafsio
Dafsio

Reputation: 23

Too many results after executing a powershell script with foreach loops

I'm just starting my journey with Powershell and I'm doing some basic scripting, starting with a foreach and if, elseif constructs.

I have a problem with foreach loops.

I tried to write a simple code with a parameter given by user. I have a CSV file with 2 columns: Zenskie (female) and Meskie (male) filled with names. My code is to find the name given by user in the CSV and as an output give an information about the gender.

Part of the CSV file:

Zenskie;Meskie
Ada;Aaron
Adamina;Abdon
Adela;Abel
Adelajda;Abelard
Adriana;Abraham
Adrianna;Achilles
Agata;Adam
Agnieszka;Adelard
Aida;Adnan
Bogna;Dawid

I added also a little twist for my wife - there should be a special comment just for her name and here I have a problem. The outcome is as it should be but it's shown multiple times (I'm guessing as many as the rows in the CSV).

I also tried to add an "else" part at the end, if the input given by user was not present on the list. But when I did that it gives results from the previous "elseif".

I tried to use another elseif with "elseif ($UserName -ne $Man) but it also gives me the wrong answer.

I bet I'm missing something easy here...


Param (
  [string]$UserName = (Read-Host " Tell me your name ")
)

$Names = Import-Csv -Path C:\Powershell\Moje_nowe\Lista_imion3.csv -Delimiter ";"

ForEach ($item in $Names) {
  $Women = $item.("Zenskie")
  $Man = $item.("Meskie")
  if ($UserName -eq $Women) {
    Write-Output "$UserName you are women!"
  }
  elseif ($UserName -eq $Man) {
    Write-Output "$UserName you are man!"
  }
  elseif ($UserName -eq "Bogna") {
    Write-Output "Kiss me!! :)"
  }
  else {
    Write-Output "$UserName you have an odd name"
  }
}

Expected result:

for input Bogna:

Tell me your name: bogna

Kiss me!! :)

Actual result:

Kiss me!! :)

Kiss me!! :)

Kiss me!! :)

and so on

When I add the part when the input is not on the list:

Expected:

Tell me your name: bdsdsd


bdsdsd you have an odd name

Actual:

no matter what I put as an input

bdsdsd you have an odd name

bdsdsd you have an odd name

bdsdsd you have an odd name

and so on

Upvotes: 0

Views: 1057

Answers (3)

mklement0
mklement0

Reputation: 437998

The outcome is as it should be, but it's shown multiple times (I'm guessing as many as the rows in the CSV).

Indeed: Given that you loop over the objects imported from the CSV rows individually, the if statement is evaluated for each.

Use break to exit the loop once you've found a match in the if and elseif branches to fix that (but not in the else branch, because it must potentially wait for all objects to be examined in order to infer that no match was found).

Caveat: Only use break in a foreach loop (statement) (as in your question), not in the pipeline with the ForEach-Object cmdlet (as in the accepted answer). In the latter case, break looks for an enclosing loop on the call stack, and if it finds none, exits the script as whole.
In short: Only ever use break and continue in loops (foreach, while, ...) or switch statements; break and continue are entirely unrelated to pipelines (but you can use return in a ForEach-Object script block to skip to the next pipeline input object).

As of PowerShell v7, there is no direct support for breaking out of (exiting) a pipeline on demand; adding such support is being requested in this GitHub issue.

However, it is much simpler and more efficient to take advantage of -in PowerShell's, array-membership-test (containment) operator (PSv3+; in PSv2, or alternatively, use -contains where the operand order is reversed), which allows you to perform a single test (per comparison value) across all CSV rows, as also shown in Olaf's helpful answer.

A simplified example:

Note: The code below reads all CSV data into memory first, and then creates two arrays from it, for the values in the two columns. For a file with a list of first names, this shouldn't be a memory concern; when dealing with large input data sets, however, you may need a solution that processes the input CSV file row by row in the pipeline.

# Parse sample CSV input into objects with .Zenskie and .Meskie properties.
$names = @'
Zenskie;Meskie
Ada;Aaron
Adamina;Abdon
Adela;Abel
'@ | ConvertFrom-Csv -Delimiter ';'

# Extract the male and female names into individual arrays.
# Note how no quoting is needed to acces the properties (columns) by names
# and how accessing a property on the input array ($names) automatically
# returns the property values *from all array elements*, a feature known
# as member enumeration.
$maleNames = $names.Meskie
$femaleNames = $names.Zenskie

# Test a few sample names.
'Abdon', 'Bogna', 'Adela', 'Bill' | ForEach-Object {

  if ($_ -eq 'Bogna') { # exception
    "Pocałuj mnie!! :)"
  }
  elseif ($_ -in $maleNames) { # -in tests presence in the array, case-insensitively
    "$_, you are a man."
  }
  elseif ($_ -in $femaleNames) {    
    "$_, you are a woman."
  }
  else {
    "$_, you have an odd name."
  }

}

The above yields:

Abdon, you have are a man.
Pocałuj mnie!! :)
Adela, you have are a woman.
Bill, you have an odd name.

Upvotes: 1

Esperento57
Esperento57

Reputation: 17472

try this :

Param ([string]$UserName = (Read-Host " Tell me your name " ))

#if Bogna, not necessary to loop, print and out
if ($UserName -eq "Bogna")
{
   "Kiss me!! :)  (nice declaration)"
   return
}

#loop on all elements and if you found as you want, break for not continue ==> attention break leave the programm in pipe foreach
$FirstElement=Import-csv -Path "C:\Powershell\Moje_nowe\Lista_imion3.csv" -Delimiter ";" | %{

    if ($_.Zenskie -eq $UserName)
    {
        "$UserName you are women!"

        break
    }
    elseif ($_.Meskie -eq $UserName)
    {
        "$UserName you are man!"

        break
    }

}

# BE CAREFULL IF BREAK IS RAISED THE SCRIPT IS ENDED AND THIS CODE DOESNT EXECUTED
if (! $FirstElement)
{
"$UserName you have an odd name, Brrrrr !"
}

If your script continue after csv loop, for dont leave script with break you can do it :

Param ([string]$UserName = (Read-Host " Tell me your name " ))

#if Bogna, not necessary to loop, print and out
if ($UserName -eq "Bogna")
{
   "Kiss me!! :)  (nice declaration)"
   return
}

#loop on all elements and if you found as you want, break for not continue
$FirstElement=$null

$allElement=Import-csv -Path "C:\Powershell\Moje_nowe\Lista_imion3.csv" -Delimiter ";"

foreach ($item in $allElement)
{
     if ($item.Zenskie -eq $UserName)
    {
        "$UserName you are women!"
        $FirstElement=$item
        break
    }
    elseif ($item.Meskie -eq $UserName)
    {
        "$UserName you are man!"
        $FirstElement=$item
        break
    }
}

#if not element founded
if (! $FirstElement)
{
    "$UserName you have an odd name, Brrrrr !"
}

Upvotes: -1

Olaf
Olaf

Reputation: 5232

You could use a "do ... until" loop to keep the game running ... ;-)

$CSV = @'
Zenskie;Meskie
Ada;Aaron
Adamina;Abdon
Adela;Abel
Adelajda;Abelard
Adriana;Abraham
Adrianna;Achilles
Agata;Adam
Agnieszka;Adelard
Aida;Adnan
Bogna;Dawid
'@ | ConvertFrom-Csv -Delimiter ';'

do {
    $UserName = Read-Host " Tell me your name (End with 'quit')" 
    if ($UserName -eq 'Bogna') { "Kiss me!! :)" }
    elseif ($UserName -in $CSV.Zenskie) { "$UserName you are woman!" }
    elseif ($UserName -in $CSV.Meskie) { "$UserName you are man!" }
    else  { "$UserName you have an odd name" }
} until ($Username -eq 'quit')

Upvotes: 1

Related Questions