Reputation: 23
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
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
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
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