Reputation: 856
I encounter strange issue. I have a file with content like
-----MyData-----
aaa
bbb
ccc
ddd
-----YourData-----
and I wrote a function to get all data of file in a single string except first and last line. The function is as follows
function readFile([string] $datapath){
$returningData = $null
foreach($line in [System.IO.File]::ReadLines($datapath)) {
if(-not ($line -like '*-MyData-*') -and -not ($line -like '*-YourData-*')){
$returningData+=$line
}
}
return $returningData
}
Now, this method is returning "aaabbbcccddd" as expected but in IF block if I use $returningData+$line instead of what I am using write now then it returns "aaa bbb ccc ddd".
I looked for it but couldn't find any answer. Why simple concatenation adding an extra space?
Upvotes: 2
Views: 2284
Reputation: 23673
There are two things in play here:
The way PowerShell displays and/or converts objects (including arrays) into a string:
$MyData = 'aaa', 'bbb', 'ccc', 'ddd'
PS C:\> "$MyData"
aaa bbb ccc ddd
PS C:\> Write-Host $MyData
aaa bbb ccc ddd
Or output (unroll) from the pipeline:
PS C:\> $MyData # Simular to: $MyData | ForEach-Object { $_ }
aaa
bbb
ccc
ddd
Also be aware that the arrays are flattened in a string or output (see e.g.: Why does PowerShell flatten arrays automatically?)
To better display the object and its structure, you might consider to use an serializer, like ConvertTo-Json
:
PS C:\> ConvertTo-Json $MyData
[
"aaa",
"bbb",
"ccc",
"ddd"
]
Or ConvertTo-Expression for even more complex objects like:
$MyObject = 'aaa', @([datetime]'1963-10-07', [version]'1.2.3'), 'bbb'
PS C:\> Write-Host $MyObject
aaa 10/7/1963 12:00:00 AM 1.2.3 bbb
PS C:\> ConvertTo-Expression $MyObject
'aaa',
(
[datetime]'1963-10-07T00:00:00.0000000',
[version]'1.2.3'
),
'bbb'
The other thing in play here is the automatic type casting where objects are implicitly converted to the same type when used as operands with an operator. Meaning if the operands are of a different type, the type of the first operand will be used for the operation and the second operand will be converted to the same type (if possible) as the first one.
This counts for comparison operators:
PS C:\> 10 -gt '9' # 10 > 9
True
PS C:\> '10' -gt 9 # '10' < '9'
False
And most other operators including arithmetic operators:
PS C:\> 10 + '9' # Results in a integer: 10 + 9
19
PS C:\> '10' + 9 # Results in a string: '10' + '9'
109
And also in case of an array:
PS C:\> 'aaa' + 'bbb', 'ccc' # Results in a string: 'aaa' + 'bbb ccc'
aaabbb ccc
PS C:\> 'aaa', 'bbb' + 'ccc' # Results in an array: 'aaa', 'bbb' + @('ccc')
aaa
bbb
ccc
In case the first operand is $Null
(which isn't part of any data type), the type of the second operand is used:
PS C:\> $a = $Null + 'aaa'
PS C:\> $a.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
PS C:\> $a = $Null + @('aaa')
PS C:\> $a.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Changing your line:
$returningData = $null
to:
$returningData = @()
or:
$returningData = ''
Will likely give you different results in this matter.
Note: as with using the increase assignment operator (+=
) to create an object collection, you should generally avoid using the increase assignment operator (+=
) for building strings as it is exponential expensive.
Instead, I recommend you to use the pipeline and the -Join
operator for building strings:
PS C:\> $MyData -Join ''
aaabbbcccddd
Or in your script:
$returningData = @(
foreach($line in $MyData) {
if(-not ($line -like '*a*') -and -not ($line -like '*b*')){ $line }
}
) -Join ''
Upvotes: 6
Reputation: 7067
@iron's answer has a lot of great information, but let's not ignore how concise PowerShell can be. I managed to bring this down to a few lines:
$File = 'C:\temp\MyData.txt'
$Patterns = @('.-MyData-.','.-YourData-.','^$')
$String = (Select-String -Pattern $Patterns -Path $File -NotMatch ).line -join ''
Assuming the use case is as simple as the question indicates.
I'd also point out that if other comments are correct and there are rouge spaces somewhere. you could pipe this to ForEach-Object
to trim each line.
$File = 'C:\temp\MyData.txt'
$Patterns = @('.-MyData-.','.-YourData-.','^$')
$String = ( (Select-String -Pattern $Patterns -Path $File -NotMatch ).line | ForEach-Object{ $_.Trim() } ) -join ''
.Trim()
will only take spaces from the beginning and end of the line. you can use .Replace( ' ', '' )
as suggested by one of the comments if you want to remove all spaces per line.
I'm sure my RegEx's could be a little better, but I gotta head out, if any one has some pointers, either edit or add to the comments. Thanks.
Upvotes: 1