Is there a string concatenation shortcut in PowerShell? - powershell

Like with numerics?
For example,
$string = "Hello"
$string =+ " there"
In Perl you can do
my $string = "hello"
$string .= " there"
It seems a bit verbose to have to do
$string = $string + " there"
or
$string = "$string there"

You actually have the operator backwards. It should be +=, not =+:
$string = "Hello"
$string += " there"
Below is a demonstration:
PS > $string = "Hello"
PS > $string
Hello
PS > $string += " there"
PS > $string
Hello there
PS >
However, that is about the quickest/shortest solution you can get to do what you want.

Avoid += for building strings
As for using the increase assignment operator (+=) to create a collection, strings are also mutual, therefore using the increase assignment operator (+=) to build a string might become pretty expensive as it will concatenate the strings and assign (copy!) it back into the variable. Instead I recommend to use the PowerShell pipeline with the -Join operator to build your string.
Apart from the fact it is faster it is actually more DRY as well:
$String = 'Hello', 'there' -Join ' ' # Assigns: 'Hello there'
Or just
-Join #('One', 'Two', 'Three') # Yields: 'OneTwoThree'
For just a few items it might not make much sense but let me give you a a more common example by creating a formatted list of numbers, something like:
[Begin]
000001
000002
000003
[End]
You could do this:
$x = 3
$String = '[Begin]' + [Environment]::NewLine
for ($i = 1; $i -le $x; $i++) { $String += '{0:000000}' -f $i + [Environment]::NewLine }
$String += '[End]' + [Environment]::NewLine
But instead, you might actually do it the PowerShell way:
$x = 3
$String = #(
'[Begin]'
for ($i = 1; $i -le $x; $i++) { '{0:000000}' -f $i }
'[End]'
) -Join [Environment]::NewLine
Performance Testing
To show the performance decrease of using the increase assignment operator (+=), let's increase $x with a factor 1000 up till 20.000:
1..20 | ForEach-Object {
$x = 1000 * $_
$Performance = #{x = $x}
$Performance.Pipeline = (Measure-Command {
$String1 = #(
'[Begin]'
for ($i = 1; $i -le $x; $i++) { '{0:000000}' -f $i }
'[End]'
) -Join [Environment]::NewLine
}).Ticks
$Performance.Increase = (Measure-Command {
$String2 = '[Begin]' + [Environment]::NewLine
for ($i = 1; $i -le $x; $i++) { $String2 += '{0:000000}' -f $i + [Environment]::NewLine }
$String2 += '[End]' + [Environment]::NewLine
}).Ticks
[pscustomobject]$Performance
} | Format-Table x, Increase, Pipeline, #{n='Factor'; e={$_.Increase / $_.Pipeline}; f='0.00'} -AutoSize
Results:
x Increase Pipeline Factor
- -------- -------- ------
1000 261337 252704 1,03
2000 163596 63225 2,59
3000 432524 127788 3,38
4000 365581 137370 2,66
5000 586370 171085 3,43
6000 1219523 248489 4,91
7000 2174218 295355 7,36
8000 3148111 323416 9,73
9000 4490204 373671 12,02
10000 6181179 414298 14,92
11000 7562741 447367 16,91
12000 9721252 486606 19,98
13000 12137321 551236 22,02
14000 14042550 598368 23,47
15000 16390805 603128 27,18
16000 18884729 636184 29,68
17000 21508541 708300 30,37
18000 24157521 893584 27,03
19000 27209069 766923 35,48
20000 29405984 814260 36,11
See also: PowerShell scripting performance considerations - String addition

Related

Blank result for String concatenation in foreach

There might not be the best way but what is wrong with this code. Why do i get blank result from $str in the foreach loop, whereas if it try to concatenate individual cells i get the right result.
$csv = import-csv -Path "C:\Users\abc\csv4script\TEST.csv" -Header 'IPs'
$str = ''
$x = 1
foreach ($cell in $csv.){
if ($x -le 4000){
$str = $str + ", " + $cell.IPs
if ($x -eq 4000){
$x = 1}
$str = ''
}
$x = $x + 1
}
$str
# $str = $str + $csv[1].IPs + ", " + $csv[2].IPs
$str
It doesn't have a value because you misplaced a '}' character.
$x = 1}
Move it to after $str = ''
$x = 1
$str = ''}
Also as Thomas pointed out, remove the period after $csv variable in your foreach statement, it should be like: foreach($cell in $csv)
Also if you want to increment a value,
$x++ is quicker and easier to read than $x = $x + 1
Lastly, as you have it, if your list has more than 4000 IPs the $str variable will empty out.

Concatenate int variable from for loop with string

I'm passing in possibly four file names: FileName1, FileName2, FileName3, FileName4.
Some may be empty, some not. Because of this I need to check if they are empty before using them. So instead of using four if statements I thought I would just loop through them. How I wanted to do that was like this:
for ($i = 1; $i -lt 5; $i++) {
$FileName = $FileName + $i
Write-Host $FileName
}
So I can get Filename1, FileName2, FileName3, FileName4. Instead I get 1, 1 + 2, etc.
I've also tried $FileName$i, $FileName"$($i)", $FileName + "$($i)".
Any ideas?
EDIT
FileName1, FileName2, FileName3, FileName4 are variables that are passed to the script. They could be FileName1="Budget2018.xlsx", FileName2="MonthlyExpenses.xlx", FileName3="", FileName4="". Or all four variables can contain values ... or just FileName1 can contain a value, etc.
I need to check if they are empty before I continue on processing them. So rather than use 4 if statements to check if they are empty I thought I could loop through them referencing the variables as $Filename$i where $i would be the value 1 to 4. I'm trying to concatenate the two values together to represent the variables that are the parameters.
This produces FileName1, FileName2 etc, but I'm not sure how that squares with "I'm passing in four possible filenames", as there's no list in your script.
For($i = 1; $i -lt 5; $i++){
$FileName = "FileName$i"
Write-Host $FileName
}
If I understand what you want then this should do it:
For($i = 1; $i -lt 5; $i++){
$FileName = 'FileName{0}' -f $i
Write-Host $FileName
}
If you want to define the root portion of the name as a variable then:
$root = 'FileName'
For($i = 1; $i -lt 5; $i++){
$FileName = '{0}{1}' -f $root,$i
Write-Host $FileName
}
Ok, so based on the most recent edit, here is a way to do what I now think you want:
1..4 | %{(ls variable:\$("FileName$_")).value}
If you have a single filename, and you want a list created based off that, you can do something like this:
$FileName = 'test'
$FileList = for ($i = 1; $i -le 4; $i++) { "$FileName$i" }
$FileList
# => test1
# => test2
# => test3
# => test4
But I suspect you have a list of files given your question had "check if null/empty" mentioned in it.

Is there a way to center text in PowerShell

How can we center text in PowerShell? WindowWidth doesn't exist apparently, so is there a way somehow to keep the text centered?
We want this output :
*
***
*****
*******
So I wrote
for ($i=1; $i -le 7; $i+=2)
{
write-host("*" * $i)
}
But we get
*
***
*****
*******
function Write-HostCenter { param($Message) Write-Host ("{0}{1}" -f (' ' * (([Math]::Max(0, $Host.UI.RawUI.BufferSize.Width / 2) - [Math]::Floor($Message.Length / 2)))), $Message) }
Write-HostCenter '*'
Write-HostCenter '***'
Write-HostCenter '*****'
I had a bit of fun and wrote some code based on this, that makes a box and center the text inside.
Im sure someone can make a cleaner version, but this do the job just fine :)
# ----------------------------------------------------------------------------------
#
# Script functions
#
# ----------------------------------------------------------------------------------
function MakeTopAndButtom
{
$string = "# "
for($i = 0; $i -lt $Host.UI.RawUI.BufferSize.Width - 4; $i++)
{
$string = $string + "-"
}
$string = $string + " #"
return $string
}
function MakeSpaces
{
$string = "# "
for($i = 0; $i -lt $Host.UI.RawUI.BufferSize.Width - 4; $i++)
{
$string = $string + " "
}
$string = $string + " #"
return $string
}
function CenterText
{
param($Message)
$string = "# "
for($i = 0; $i -lt (([Math]::Max(0, $Host.UI.RawUI.BufferSize.Width / 2) - [Math]::Max(0, $Message.Length / 2))) - 4; $i++)
{
$string = $string + " "
}
$string = $string + $Message
for($i = 0; $i -lt ($Host.UI.RawUI.BufferSize.Width - ((([Math]::Max(0, $Host.UI.RawUI.BufferSize.Width / 2) - [Math]::Max(0, $Message.Length / 2))) - 2 + $Message.Length)) - 2; $i++)
{
$string = $string + " "
}
$string = $string + " #"
return $string
}
function LinesOfCodeInCorrentFolder
{
return (gci -include *.ps1 -recurse | select-string .).Count
}
$MakeTopAndButtom = MakeTopAndButtom
$MakeSpaces = MakeSpaces
$lines = LinesOfCodeInCorrentFolder
# ----------------------------------------------------------------------------------
#
# Run
#
# ----------------------------------------------------------------------------------
$MakeTopAndButtom
$MakeSpaces
$MakeSpaces
$MakeSpaces
$MakeSpaces
CenterText "Lines of .ps1 code in this folder: $($lines)"
CenterText "Press any key to exit"
$MakeSpaces
$MakeSpaces
$MakeSpaces
$MakeSpaces
$MakeTopAndButtom
Read-Host
This gives an output like this:
# ---------------------------------------------------------------------------------------- #
# #
# #
# #
# #
# Lines of .ps1 code in this folder: 6524 #
# Press any key to exit #
# #
# #
# #
# #
# ---------------------------------------------------------------------------------------- #
You can calculate the spaces you need to add and then include them as follows:
$Width = 3
for ($i=1; $i -le 7; $i+=2)
{
Write-Host (' ' * ($width - [math]::floor($i / 2))) ('*' * $i)
}

How to count occurrence of each word on each line?

I'm stuck as I have to use Powershell at work. I've attached my code and results so far below.
$data = Get-Content "/Users/mikeshobes/Documents/Powershell/nfl.csv"
write-host $data.count total lines read from file
foreach ($line in $data)
{
write-host $line
}
13 total lines read from file
1,Tom Brady,NE,QB,93,142,65.5,47.3,"1,137",8,379,7,3,55,38.7,48,17,3,9,97.7
2,Matt Ryan,ATL,QB,70,98,71.4,32.7,"1,014",10.3,338,9,0,54,55.1,73T,13,2,8,135.3
3,Aaron Rodgers,GB,QB,80,128,62.5,42.7,"1,004",7.8,334.7,9,2,53,41.4,42T,17,1,10,103.8
4,Ben Roethlisberger,PIT,QB,64,96,66.7,32,735,7.7,245,3,4,35,36.5,62T,8,3,2,82.6
5,Russell Wilson,SEA,QB,40,60,66.7,30,449,7.5,224.5,4,2,22,36.7,42,5,2,6,97.2
6,Dak Prescott,DAL,QB,24,38,63.2,38,302,7.9,302,3,1,16,42.1,40T,3,1,2,103.2
7,Eli Manning,NYG,QB,23,44,52.3,44,299,6.8,299,1,1,12,27.3,51,3,2,2,72.1
8,Matt Moore,MIA,QB,29,36,80.6,36,289,8,289,1,1,16,44.4,37,3,0,5,97.8
9,Matthew Stafford,DET,QB,18,32,56.3,32,205,6.4,205,0,0,10,31.3,30,3,0,3,75.7
10,Alex Smith,KC,QB,20,34,58.8,34,172,5.1,172,1,1,9,26.5,24,3,0,1,69.7
11,Brock Osweiler,HOU,QB,14,25,56,25,168,6.7,168,1,0,9,36,38,1,0,0,90.1
12,Connor Cook,OAK,QB,18,45,40,45,161,3.6,161,1,3,11,24.4,20,1,0,3,30
13,Julian Edelman,NE,WR,0,1,0,0.3,0,0,0,0,0,0,0,--,0,0,0,39.6
if what you want to get is the amount of times every word in a line is in that line, first you need to split the line in words an then get the amount of times that word is in that line.
I think a snippet of code explain it better:
For ($i = 0; $i -lt $Data.Length; $i++) {
$Line = $Data[$i]
Foreach ($Word in $Line.Split(' ,')) {
Write-Host ('Line {0} contains the word: "{1}" {2} time(s)' -f ($i + 1), $Word, (($Line -split $Word).Count-1))
}
}

Powershell - search for string, remove excess whitespace, print second field

I'm having a problem parsing some string data in powershell and need a little help. Basically I have an application command that doesn't output objects, rather string data.
a = is the item I'm searching for
b = is the actual ouput from the command
c = replaces all the excess whitespace with a single space
d = is supposed to take $c "hostOSVersion 8.0.2 7-Mode" and just print "8.0.2 7-Mode"
However, $d is not working it just prints the same value as $c. I'm a UNIX guy and this would be easy in one awk statement. If you know how to do this in one command that would be nice, or just tell me what's wrong with my $d syntax below.
$a = "hostOSVersion"
$b = "hostOSVersion 8.0.2 7-Mode"
$c = ($a -replace "\s+", " ").Split(" ")
$d = ($y -replace "$a ", "")
Well you might have to futz around with the exact pattern, but one way is with a regex:
$b = "hostOSVersion 8.0.2 7-Mode"
$b -match '(\d.*)'
$c = $matches[1]
If you really wanted to oneline it with -replace:
$($($b -replace $a, '') -replace '\s{2}', '').trim()
Your line
$c = ($a -replace "\s+", " ").Split(" ")
should reference the $b variable instead of $a
$c = ($b -replace "\s+", " ").Split(" ")
Then, you will notice the output of $d becomes
hostOSVersion
8.0.2
7-Mode
and a statement like $d[1..2] -join ' ' would produce 8.0.2 7-Mode