Im trying to write a script which works as follows:
My input is a text file with 8 rows and 8 columns, filled with values 0 or 1, with a single space character each separating the columns.
I need to check the 4th number in each row, and output false, if it is 0, and true, if it is 1.
My code at the moment looks like this:
param($fname)
$rows = (Get-Content $fname)
for ($i=0;$i -lt $rows.Length;$i++)
{
if ($rows[$i][6] -eq 1)
{
Write-Host "true"
}
if ($rows[$i][6] -ne 1)
{
Write-Host "false"
}
}
So I use [$i][6], because I get that that's the 4th number, accounting for the number of spaces acting as separators.
I checked and thought it was perfect, but somehow it says false for every line, but when I Write-Host $rows[0][6] it is 1.
tl;dr
# Create sample input file, with values of interest in 4th field
# (0-based field index 3).
#'
0 0 0 0 0 0 0 0
0 0 0 1 0 0 0 0
'# > file
foreach ($line in (Get-Content file)) {
$fields = -split $line
if ($fields[3] -eq '1') {
"true"
} else {
"false"
}
}
yields:
false
true
There are many subtleties to consider in your original code, but the above code:
offers a more awk-like approach by splitting each input line into whitespace-separated fields, whatever the length of the fields, courtesy of the unary -split operator.
subscripts (indices) can then be based on field indices rather than character positions.
All fields returned by -split ... are strings, hence the comparison with string literal '1', but, generally, PowerShell performs a lot of behind-the-scenes conversion magic for you: with the code above - unlike with your own code - using 1 would have worked too.
As for why your approach failed:
Indexing into (using a subscript with) a string value in PowerShell is a special case: it implicitly treats the string as a character array, and, with a single index such as 6, returns a [char] instance.
It is the LHS (left-hand side) of an expression involving a binary operator such as -eq that determines what type the RHS (right-hand side) will be coerced to, if necessary, before applying the operator:
([char] '1') -eq 1 # !! $false
Coercing the (implied) [int] type of RHS 1 to the LHS type [char] yields Unicode codepoint U+0001, i.e., a control character rather than the "ASCII" digit '1', which is why the comparison fails.
#PetSerAl's helpful, but cryptic suggestion (in a comment on the question) to use '1'[0] rather than 1 as the RHS solves the problem in this particular case, because '1'[0] returns 1 as a [char] instance, but the solution doesn't generalize to multi-character field values.
'1' -eq 1 # $true; same as: ([string] 1) -eq 1 or ([string] 1) -eq '1'
Converting integer 1 to a string indeed is the same as '1'.
This script fills a true 2d array with the proper values from the matrix file, but the output doesn't fit.
$Array = New-Object "bool[,]" 8,8
[int]$i=0 ; [int]$j=0
get-content $fname|foreach{ foreach ($b in ($_ -split(' '))) {
"{0},{1}={2}" -f $i,$j,($b -eq 0)
$Array[$i,$j] = ($b -eq 0)
$j++}
$j=0; $i++}
Related
Ok. So I thought this would have been easy, but I am hitting a snag.
$var = (Get-ItemProperty "HKCU:\SOFTWARE\SAP\General" -Name "BrowserControl")."BrowserControl"
$var2 = "HKCU:\SOFTWARE\SAP\General"
$var3 = #('1','0')
#if (($var -eq ($var3 -join'')))
#if (Compare-Object -IncludeEqual $var $var3 -SyncWindow 0)
if ($var -eq $var3)
{
Write-Output "Registry hive exists"
exit 1
}
else
{
Write-Output "Registry hive doesn't exists"
#New-ItemProperty -Path $var2 -name "BrowserControl" -Value "1" -PropertyType "DWORD" -Force | Out-Null
}
If 1 or 0 is returned from BrowserControl, I want it to be a match. If anything else is returned, no match.
If BrowserControl is set to 1, it works. If it is set to 0 or any number other than 1 it doesn't match.
I know I can use else-if and add a couple more lines of code, but I was really wanting to get this to work.
As you can see, I have tried different comparison methods. I also tried (0,1), ('0','1'), 0,1 for var3. None of those worked either.
So... what am I missing?
You cannot meaningfully use an array as the RHS (right-hand side) of the -eq operator.[1]
However, PowerShell has dedicated operators for testing whether a given single value is contained in a collection (more accurately: equal to one of the elements of a collection), namely -in and its operands-reversed counterpart, -contains.
In this case, -in makes for more readable code:
if ($var -in $var3) # ...
[1] PowerShell quietly accepts an array (collection) as the RHS, but - uselessly - stringifies it, by concatenating the elements with a single space by default. E.g., '1 2' -eq 1, 2 yields $true.
By contrast, using an array as the LHS of -eq is meaningfully supported: the RHS scalar then acts as a filter, returning the sub-array of equal LHS elements; e.g. 1, 2, 3, 2 -eq 2 returns 2, 2
I have trouble of getting index of the current element for multiple elements that are exactly the same object:
$b = "A","D","B","D","C","E","D","F"
$b | ? { $_ -contains "D" }
Alternative version:
$b = "A","D","B","D","C","E","D","F"
[Array]::FindAll($b, [Predicate[String]]{ $args[0] -contains "D" })
This will return:
D
D
D
But this code:
$b | % { $b.IndexOf("D") }
Alternative version:
[Array]::FindAll($b, [Predicate[String]]{ $args[0] -contains "D" }) | % { $b.IndexOf($_) }
Returns:
1
1
1
so it's pointing at the index of the first element. How to get indexes of the other elements?
You can do this:
$b = "A","D","B","D","C","E","D","F"
(0..($b.Count-1)) | where {$b[$_] -eq 'D'}
1
3
6
mjolinor's answer is conceptually elegant, but slow with large arrays, presumably due to having to build a parallel array of indices first (which is also memory-inefficient).
It is conceptually similar to the following LINQ-based solution (PSv3+), which is more memory-efficient and about twice as fast, but still slow:
$arr = 'A','D','B','D','C','E','D','F'
[Linq.Enumerable]::Where(
[Linq.Enumerable]::Range(0, $arr.Length),
[Func[int, bool]] { param($i) $arr[$i] -eq 'D' }
)
While any PowerShell looping solution is ultimately slow compared to a compiled language, the following alternative, while more verbose, is still much faster with large arrays:
PS C:\> & { param($arr, $val)
$i = 0
foreach ($el in $arr) { if ($el -eq $val) { $i } ++$i }
} ('A','D','B','D','C','E','D','F') 'D'
1
3
6
Note:
Perhaps surprisingly, this solution is even faster than Matt's solution, which calls [array]::IndexOf() in a loop instead of enumerating all elements.
Use of a script block (invoked with call operator & and arguments), while not strictly necessary, is used to prevent polluting the enclosing scope with helper variable $i.
The foreach statement is faster than the Foreach-Object cmdlet (whose built-in aliases are % and, confusingly, also foreach).
Simply (implicitly) outputting $i for each match makes PowerShell collect multiple results in an array.
If only one index is found, you'll get a scalar [int] instance instead; wrap the whole command in #(...) to ensure that you always get an array.
While $i by itself outputs the value of $i, ++$i by design does NOT (though you could use (++$i) to achieve that, if needed).
Unlike Array.IndexOf(), PowerShell's -eq operator is case-insensitive by default; for case-sensitivity, use -ceq instead.
It's easy to turn the above into a (simple) function (note that the parameters are purposely untyped, for flexibility):
function get-IndicesOf($Array, $Value) {
$i = 0
foreach ($el in $Array) {
if ($el -eq $Value) { $i }
++$i
}
}
# Sample call
PS C:\> get-IndicesOf ('A','D','B','D','C','E','D','F') 'D'
1
3
6
You would still need to loop with the static methods from [array] but if you are still curious something like this would work.
$b = "A","D","B","D","C","E","D","F"
$results = #()
$singleIndex = -1
Do{
$singleIndex = [array]::IndexOf($b,"D",$singleIndex + 1)
If($singleIndex -ge 0){$results += $singleIndex}
}While($singleIndex -ge 0)
$results
1
3
6
Loop until a match is not found. Assume the match at first by assigning the $singleIndex to -1 ( Which is what a non match would return). When a match is found add the index to a results array.
I´m trying to know if all the PC are in the list and if one it´s not on the list the script will say me, the list can be modificate with more PC.
$equipos= "equipo1","equipo2","equipo3","equipo5"
[char]$nequipos = 1
for($i=0;$i -lt $equipos.count;$i++){
[char]$num = $equipos[$i][6]
if($num -ne $nequipos){
write-host "equipo"$nequipos
}
[char]$nequipos= $i++
}
The following finds the gaps in the numbering and lists all available names based on the missing numbers:
# Extract all numbers contained in the names.
[int[]] $numbers = "equipo1", "equipo2", "equipo3", "equipo5" -replace '\D'
# Find the highest number.
$highestNumber = [Linq.Enumerable]::Max($numbers)
# Find all unused numbers.
$unusedNumbers = Compare-Object $numbers (1..$highestNumber) -PassThru
# Report results.
if ($unusedNumbers) {
Write-Verbose -Verbose "The following names are available: "
$unusedNumbers.ForEach({ 'equipo' + $_ })
} else {
Write-Verbose -Verbose "No gaps in numbering found."
}
Output:
VERBOSE: The following names are available:
equipo4
Note:
-replace '\D' removes all non-digit (\D) characters from the names and casts the resulting strings to [int[]] in order to get an array of numbers.
[Linq.Enumerable]::Max($numbers) finds the maximum (highest number) among them. See the .NET docs.
Compare-Object is used to compare the extracted numbers to the (gap-less) range (..) of numbers from 1 to the highest one (1..$highestNumber). By default, the elements that are different between the input collections are reported and, thanks to -PassThru, those extracted numbers that are missing from the range are directly output.
$unusedNumbers.ForEach({ 'equipo' + $_ }) uses the .ForEach() array method to construct the list of names for the unused numbers in the range.
Assuming the names are unique, you can pick the highest number in the sequence and subtract the length of the list - that'll give you the number of gaps:
$equipos = "equipo1","equipo2","equipo3","equipo5" |Sort-Object {$_ -replace '^\D+' -as [int]} -Unique -Descending
$highest = $equipos |Select -First 1
$number = $highest -replace '^\D+' -as [int]
if($number -gt $equipos.Length){
"There are $($number - $equipos.Length) gap(s) in the list"
}
else {
"The list is contiguous"
}
Which, with the given sample values, will output:
There are 1 gap(s) in the list
Power shell to add the odd numbers in the odd lines and the even numbers in the even line the result of each line should come in the standard output! In each line, there are at least 2 numbers. The filename is given by a parameter.
for example this:
1 2 3
4 5 6
5 6 7
7 8 9
4 6 0
the output should be
4
10
12
8
0
You can read the file content with Get-Content or cat, and then turn it to an array by splitting on \n
$lines = (Get-Content $filepath).split('\n')
You can then iterate on each line and split it on ' ', and then iterate on that array and summing up the values you want
for($i=0;$i -lt $lines.length;$i++){
$numsInLine = $lines[i].split(' ')
$lineSum = 0
for($j=0;$j -lt $numsInLine.length;$j++){
if($numsInLine[j] % 2 -eq $i % 2){ #$i is the line number, $numsInLine[j] is a number in that line
$lineSum++
}
}
Write-Host $lineSum
}
Edit: In response to Lee_Dailey's comment, % in this content is the modulo operator. x%2 returns the parity of x, which is exactly what you need here.
Also note that % in powershell is an alias for ForEach-Object.
% or ForEach-Object in powershell is a simple for-each loop, iterates over all values in the array with a simpler syntax, no index though.
In our case I would recommend using it instead of the second for loop only, as we use the index variable in the first (note i is in use). In that case, the second loop would look like:
$numsInLine | %{ #or $numsInLine | ForEach-Object
if($_ % 2 -eq $i % 2){ #$_ refers to the last returned object
$lineSum++
}
}
Note two things here:
1.% or ForEach-Object are always used after pipeline as they must recieve an InputObject. The InputObject must also be iteratable. That's how to differ between % as modulo and % as foreach.
$_ is used here as the current iterated item. Our iterated item does not have a named variable refrencing it. $_ is commonly used in pipeline and foreach loops in particular (however you can use it in any other case too).
Another logically-equal syntax is:
foreach($num in $numsInLine){
if($num % 2 -eq $i % 2){
$lineSum++
}
}
Note that here no pipeline or $_ is needed.
I have seen this here for different scripting languages, however I have my problem in Powershell and as follows:
I want to limit the input of a numeric value, which can either be a decimal or an integer one, to a specific range.
I tried the following:
Do { $value = Read-host "Specify a value between 23.976 and 60"}
while ((23.976..60) -notcontains $value )
However it seems that like this it doesn't accept any decimal values at all but only integers. For instance, when I try to enter 29.97 or 29.970 it stays in the loop.
How can I do this right?
PowerShell's range operator - .. - generates an array of discrete values from the range endpoints, which can have the following types (in a given expression, both endpoints must have the same type):
[int] (System.Int32)
e.g., 1..3 creates array 1, 2, 3
Note: While the minimum and maximum endpoint values allowed are [int]::MinValue and [int]::MaxValue, respectively, the resulting array must additionally not have more than 2,146,435,071 elements (2+ billion), which is the max. element count for a single-dimensional [int] array in .NET - see this answer for background information.
Alternatively, only in PowerShell [Core, 6+]: characters, which are enumerated between the endpoints by their underlying code points ("ASCII values").
e.g., 'a'..'c' creates array [char] 'a', [char] 'b', [char] 'c'
Instances of numeric types other than [int] are quietly coerced to the latter - if they can fit into the [int] range - in which case half-to-even midpoint rounding is performed for non-integral types; e.g., implicit [double] instances 23.5 and 24.5 are both coerced to 24.
Because [int] 23.976 is 24, your 23.976..60 expression creates array 24, 25, 26, ..., 60 which is not your intent.
In short: You cannot use .. to describe an uncountable range of non-integers (fractional numbers).
Instead, use -ge (greater than or equal) and -le (less than or equal) to test against the endpoints:
-not ($value -ge 23.976 -and $value -le 60)
Additionally, in order to make the -ge and -le operations work as intended, convert the return value from Read-Host, which is always a string, to a number.
If you were to use $value as directly returned by Read-Host, you'd get lexical (text sort order-based) comparison, not numeric comparison.
Therefore, cast the Read-Host return value to [double]:
$value = try { [double] (Read-Host 'Specify a value between 23.976 and 60') }
catch {}
Note: The try / catch handles the case when the user enters text that cannot be interpreted as a [double] representation.
To put it all together:
do {
$value = try { [double] (Read-Host 'Specify a value between 23.976 and 60') }
catch {}
} while (-not ($value -ge 23.976 -and $value -le 60))
OK guys, this is how it works ;)
Do { $value = Read-host "Specify a value between 23.976 and 60"}
while (( $value -gt 60 ) -or ( $value -lt 23.976 ))