(edit) an empty function does in fact return a null, something led me astray, leaving the question here as a reminder...
Where $null does not cut it. My scenario is simplest explained as a snippet, I have a function someone wrote
function PositiveInt([string]$value) {
if ([int]($value) -ge 0) {[int]$value}
}
Which returns nothing in the negative case or throws if the input is not a numeric string. How can I test for the return in the negative case? I tried this
if ($null -eq (PositiveInt -1)) {
write-host "not positive :)"
}
But it obviously won't work, because no return value is not equal to $null . How can I test if a function or expression simply does not return anything at all? Don't try fix my contrived function, its the absence of "$empty" (sic) I want to do a test for, but cannot because powershell binding does not mandate that a function or even an expression actually returns anything at all?
# hacky unclear solution proposed
$temp = #(PositiveInt -1)
if ($temp.length -eq 0) {
write-host "not positive :)"
}
I drew some inspiration from this In Powershell what is the idiomatic way of converting a string to an int? posting. but I'm asking a different question I believe.
Aside from the casting to array workaround, is there a cleaner way?
(edit) Have to admit something environmental or in the actual code context was at play, in PS 5.1 a function call returns $null as #AnsgarWiechers pointed out.
You could use the -is or -isnot type operators to check if the result successfully converted to an integer:
if( (PositiveInt '-1') -isnot [int] ) {
write-host 'negative'
}
You could try the following:
if ((PositiveInt -1) -eq $null) {
write-host "not positive :)"
}
do you like this?
if ( [string]::IsNullOrEmpty( (PositiveInt -1) ) ) {
write-host "not positive :)"
}
Related
I have completed coding this same problem in python and was trying to generate a similar logic or atleast achieve a similar result in powershell.
Python Code-
def FRC(str):
h = {}
for ch in str:
if ch in h:
return ch
else:
h[ch] = 0
return '\0'
print(FRC("abcdedcba"))
I have tried a few possible codes and was able to only enumerate the array of characters to count their occurrences. Thank you for any suggestions.
Update1 - The code I have worked on is as follows:
function get-duplicatechar {
[CmdletBinding()]
param (
[string]$teststring
)
$hash = #()
$teststring = $teststring.ToCharArray()
foreach ($letter in $teststring)
{
if($letter -contains $hash){
return $letter
}else {
$hash = $hash + $letter
}
return "\0"
}
}
get-duplicatechar("saahsahh")
You could use the (.Net) HashSet class for this, which Add method (besides adding the value,) returns true if the element is added to the HashSet<T> object and false if the element is already present.
function get-duplicatechar {
[CmdletBinding()]
param (
[string]$str
)
$h = [System.Collections.Generic.HashSet[char]]::new()
foreach ($ch in $str.ToCharArray()) {
if(!$h.add($ch)) { return $ch }
}
}
Here's a working version using your code as base:
function get-duplicatechar {
[CmdletBinding()]
param (
[string]$teststring
)
$hash = #{}
$CharArray = $teststring.ToCharArray()
foreach ($letter in $CharArray) {
if($letter -in $hash.Keys) {
$letter
break
}
else {
$hash[$letter] = $null
}
}
}
One problem is that you are strongly typing $teststring to be a string, so when you add a character array later PowerShell just converts it into a string and thus $teststring remains a string (try $teststring.GetType() after $teststring = $teststring.ToCharArray() to see this for yourself).
One way to solve this is to do what I did and use a different variable for the character array. You could also solve it by changing the variable to a character array directly by replacing [string]$teststring with [char[]]$teststring, that way any strings input to the function will be automatically cast as a character array.
The next mistake is using -contains where you need -in. The letter doesn't contain the array, you're looking for the letter in the array, just like you did in Python.
You can drop the return keyword entirely, PowerShell does not need it. Any output in your function will be output from the function automatically.
You also call your collection variable "hash", but you made an array. I changed it to be an actual hashtable, just like in your Python code. I also changed the way we add to it to more closely reflect what you did in Python. There are many ways to do this, this is just one. Notice we'll need to add ".Keys" in our if-statement as well so we check for keys matching our letter.
I think that's it, ask if anything is unclear.
I have the following, which works but looks clunky:
if($config.contentDir){
$contentDir = $config.contentDir
} else {
$contentDir = "contents"
}
Is there a nicer way of doing this? I have seen this answer here, but it isn't exactly "nicer". Just wondering if 6.0 brought any improvements?
I'm likely to be handling a large amount of config options, so it's going to get fairly messy.
This is a little shorter...
$contentDir = if ( $config.contentDir ) { $config.contentDir } else { "contents" }
You could also define an iif function:
function iif {
param(
[ScriptBlock] $testExpr,
[ScriptBlock] $trueExpr,
[ScriptBlock] $falseExpr
)
if ( & $testExpr ) {
& $trueExpr
}
else {
& $falseExpr
}
}
Then you could shorten to this:
$contentDir = iif { $config.contentDir } { $config.contentDir } { "contents" }
As an aside, it looks like the next version of PowerShell will support the ternary operator (see https://devblogs.microsoft.com/powershell/powershell-7-preview-4/), so in the future, you'll be able to write something like:
$contentDir = $config.contentDir ? $config.contentDir : "contents"
Update:
Null-coalescing operators were introduced in PowerShell (Core) 7.0 (along with a ternary operator), which enables the following v7+ solution:
$contentDir = $config.contentDir ?? 'content'
PowerShell v6- solutions:
What you'e looking for is null-coalescing, which PowerShell doesn't have as of v7.0.0-preview.4.
For now, this will have to do:
$contentDir = if ($null -eq $config.contentDir) { 'content' } else { $config.contentDir }
Note: $null is deliberately placed on the LHS of -eq to unambiguously test for $null, because as the RHS it would act as a filter if the value to test happens to be array-valued.
An adaptation of Lee Daily's array-based answer enables a more concise solution:
$contentDir = ($config.ContentDir, 'content')[$null -eq $config.ContentDir]
Use of the ternary operator (conditional), which will be implemented in v7.0, enables a similarly concise equivalent:
$contentDir = $null -eq $config.contentDir ? 'content' : $config.contentDir
However, all these approaches have the following undesirable aspects:
They require an explicit reference to $null; note that if ($config.ContentDir) - i.e. coercing the value to a Boolean - may work with strings, but is not generally robust, because non-$null values such as 0 can evaluate to $false too.
$config.contentDir, the value to test for $null, must be accessed twice, which can have side effects.
Defining a custom function named, say, ??, can address these problems:
# Custom function that emulates null-coalescing.
function ?? ($PossiblyNull, $ValueIfNull) {
if ($null -eq $PossiblyNull) { $ValueIfNull } else { $PossiblyNull }
}
$contentDir = ?? $config.contentDir 'content'
However, such a custom function has down-sides:
The down-sides of custom functions are:
You need to include or import them into in every piece of code you want to use them in.
If you choose familiar name such as ??, the placement of operands can get confusing, because you must (invariably) place them differently in PowerShell, given the implementation as a function (e.g., a ?? b in C# vs. ?? $a $b in PowerShell) - especially once true null-coalescing gets implemented in PowerShell: see next section.
And, of course, calling a function adds overhead.
If this GitHub feature request is implemented, you'll be able to use true null-coalescing, which is both the most concise solution and avoids the aforementioned undesirable aspects:
# Hopefully soon
$contentDir = $config.contentDir ?? 'content'
A related feature also proposed in the linked GitHub issue is null-conditional assignment, $config.ContentDir ?= 'content'
as Bill_Stewart showed, there is a ternary operator due in ps7. however, you can get something similar by using a two-item array and taking advantage of how PoSh will coerce values -- $False gives 0, $True gives 1.
$Config = [PSCustomObject]#{
ContentDir = 'SomewhereElse'
}
#$Config.ContentDir = ''
$ContentDir = #('contents', $Config.ContentDir)[[bool]$Config.ContentDir]
$ContentDir
output with line 4 commented out = SomewhereElse
output with line 4 enabled = contents
Sort of like '||' in bash. If the first one is false or null, it will do the second one.
[void](($contentDir = $config.contentDir) -or ($contentDir = "contents"))
I have the following which doesn't allow both variables to be enabled (boolean true value):
If (($Variable1) -and ($Variable2)) {
Write-Warning "Both variables have been enabled. Modify script to enable just one."
Pause
Break
}
This works great, however, how would I ensure only one is ever enabled when 4 possible variables exist? I'm thinking a combination of -and & -or?
You can add the boolean values and check their count:
If (([bool]$Variable1 + [bool]$Variable2 + [bool]$Variable3) -ne 1) {
...
}
but of course you have to make sure that these can actually be cast to boolean.
That's what "exclusive or" (xor) is for:
If ($Variable1 -xor $Variable2 -xor $Variable3) {
....
}
About logical operators in Powershell
Cannot think of a way to do this that avoids using a counter. You have to check the value of each variable and keep count of how many are $true.
$trueCount = 0
($variable1, $variable2, $variable3, $variable4) | % { if ($_ ) { $trueCount++} }
if ($trueCount -eq 1) {
write-host "only one variable true"
}
else {
write-host "condition not met"
}
I'm learning to write scripts with PowerShell, and I found this code that will help me with a project The example comes from Is there a one-liner for using default values with Read-Host?.
$defaultValue = 'default'
$prompt = Read-Host "Press enter to accept the default [$($defaultValue)]"
$prompt = ($defaultValue,$prompt)[[bool]$prompt]
I think I understand that $prompt = ($defaultValue,$prompt) is creating a two-element array and that the [bool] part is forcing the $prompt data type to Boolean, but I don’t understand what this third line of code does as a whole.
This is a common programming pattern:
if (user entered a price)
{
price = user entered value
}
else
{
price = default value
}
and because that is quite common, and also long winded, some languages have a special ternary operator to write all that code much more concisely and assign a variable to "this value or that value" in one move. e.g. in C# you can write:
price = (user entered a price) ? (user entered value) : (default value)
# var = IF [boolean test] ? THEN (x) ELSE (y)
and the ? assigns (x) if the test is true, and (y) if the test is false.
In Python, it's written:
price = (user entered value) if (user entered a price) else (default value)
And in PowerShell, it's written:
# you can't have a ternary operator in PowerShell, because reasons.
Yeah. No nice short code pattern allowed.
But what you can do, is abuse array-indexing (#('x', 'y')[0] is 'x' and #('x', 'y')[1] is 'y' and ) and write that ugly and confusing code-golf line:
$price = ($defaultValue,$userValue)[[bool]$UserEnteredPrice]
# var (x,y) is an array $array[ ] is array indexing
(0,1) are the array indexes of the two values
[bool]$UserEnteredPrice casts the 'test' part to a True/False value
[True/False] used as indexes into an array indexing makes no sense
so they implicitly cast to integers, and become 0/1
# so if the test is true, the $UserValue is assigned to $price, and if the test fails, the $DefaultValue is assigned to price.
And it behaves like a ternary operator, except it's confusing and ugly and in some situations it will trip you up if you're not careful by evaluating both array expressions regardless of which one is chosen (unlike real ? operators).
Edit: What I should really add is a PowerShell form I prefer - you can assign the result of an if test directly in PowerShell and do:
$price = if ($userValue) { $userValue } else { $DefaultValue }
# ->
$prompt = if ($prompt) { $prompt } else { $DefaultValue }
Casting $prompt to [bool] produces a value of $true or $false depending on whether the variable is empty ($null or empty string both become $false) or not (non-emtpy strings become $true).
[bool]'' → $false
[bool]'something' → $true
Using that boolean value in the index operator then implicitly casts the value to an integer where $false becomes 0 and $true becomes 1, hence selecting the first or second element of the array.
[int]$false → 0
[int]$true → 1
($defaultValue,$prompt)[0] → $defaultValue
($defaultValue,$prompt)[1] → $prompt
To complement the great answers given by Ansgar Wiechers and by TessellatingHeckler:
It would be great if PowerShell had operators for ternary conditionals and null-coalescing, such as follows (applied to the example in the question):
Update: PowerShell (Core) 7+ now does have these operators.
# Ternary conditional
# Note: does NOT work in *Windows PowerShell*,
# only in PowerShell (Core) v7+
$prompt = $prompt ? $prompt : $defaultValue
# Or, more succinctly, with null coalescence:
# Note: does NOT work in *Windows PowerShell*,
# only in PowerShell (Core) v7+
# (Note: This example assumes that $prompt will be $null in the default
# case, whereas the code in the question actually assigns the
# empty string to $prompt if the user just presses Enter.)
$prompt = $prompt ?? $defaultValue
Unfortunately, these expressive constructs are not part of Windows PowerShell, the legacy edition of PowerShell, which will see no new functionality.
Below are adapted versions of the functions from a 2006 PowerShell Team blog post with associated alias definitions, whose use then allows the following solution:
# Ternary conditional - note how the alias must come *first*
# Note: Requires the function and alias defined below.
$prompt = ?: $prompt $prompt $defaultValue
# Or, more succinctly, with null coalescence - note how the alias must come *first*
# Note: Requires the function and alias defined below.
$prompt = ?? $prompt $defaultValue
Source code:
Note that the actual functions are quite short; it is the comment-based help that makes this listing lengthy.
Set-Alias ?: Invoke-Ternary -Option AllScope
<#
.SYNOPSIS
Emulation of a ternary conditional operator.
.DESCRIPTION
An emulation of the still-missing-from-the-PS-language ternary conditional,
such as the C-style <predicate> ? <if-true> : <if-false>
Because a function is used for emulation, however, the function name must
come first in the invocation.
If you define a succinct alias, e.g., set-alias ?: Invoke-Ternary,
concise in-line conditionals become possible.
To specify something other than a literal or a variable reference, pass a
script block for any of the tree operands.
A predicate script block is of necessity always evaluated, but a script block
passed to the true or false branch is only evaluated on demand.
.EXAMPLE
> Invoke-Ternary { 2 -lt 3 } 'yes' 'no'
Evaluates the predicate script block, which outputs $true, and therefore
selects and outputs the true-case expression, string 'yes'.
.EXAMPLE
> Invoke-Ternary $false { $global:foo = 'bar' } { Get-Date }
Outputs the result of executing Get-Date.
Note that the true-case script block is NOT evaluated in this case.
.NOTES
Gratefully adapted from http://blogs.msdn.com/powershell/archive/2006/12/29/dyi-ternary-operator.aspx
#>
function Invoke-Ternary
{
[CmdletBinding()]
param($Predicate, $Then, $Otherwise = $null)
if ($(if ($Predicate -is [scriptblock]) { & $Predicate } else { $Predicate })) {
if ($Then -is [ScriptBlock]) { & $Then } else { $Then }
} else {
if ($Otherwise -is [ScriptBlock]) { & $Otherwise } else { $Otherwise }
}
}
Set-Alias ?? Invoke-NullCoalescence -Option AllScope
<#
.SYNOPSIS
Emulation of a null-coalescence operator.
.DESCRIPTION
An emulation of a null-coalescence operator such as the following:
<expr> ?? <alternative-expr-if-expr-is-null>
Because a function is used for emulation, however, the function name must
come first in the invocation.
If you define a succinct alias, e.g., set-alias ?? Invoke-NullCoalescence,
concise in-line null-coalescing becomes possible.
To specify something other than a literal or a variable reference, pass a
script block for any of the two operands.
A first-operand script block is of necessity always evaluated, but a
second-operand script block is only evaluated on demand.
Note that only a true $null value in the first operand causes the second
operand to be returned.
.EXAMPLE
> Invoke-NullCoalescence $null '(empty)'
Since the first operand is $null, the second operand, string '(empty)', is
output.
.EXAMPLE
> Invoke-NullCoalescence '' { $global:foo = 'bar' }
Outputs the first operand, the empty string, because it is not $null.
Note that the second-operand script block is NOT evaluated in this case.
.NOTES
Gratefully adapted from http://blogs.msdn.com/powershell/archive/2006/12/29/dyi-ternary-operator.aspx
#>
function Invoke-NullCoalescence
{
[CmdletBinding()]
param($Value, $Alternative)
if ($Value -is [scriptblock]) { $Value = & $Value }
if ($null -ne $Value) {
$Value
} else {
if ($Alternative -is [ScriptBlock]) { & $Alternative } else { $Alternative }
}
}
For some reason, when I debug - $debug is equal to 1, yet when the debugger hits if(numUsers -gt 2), it enters the if block...
function numUsers()
{#(query user /server:$server).Count - 1
}
$debug = numUsers
if(numUsers -gt 2)
{#true
}
else
{#Nothing
}
Am I using the -gt operator properly? Am I missing something really obvious?
Appreciate any insight
The -gt 2 are being seen as arguments to the numUsers call as written.
You can see that by looking at the value of $args in your numUsers function.
You need to tell powershell that you want to call numUsers on its own for this to work.
if ((numUsers) -gt 2) {
"gt 2"
} else {
"lt 2"
}