Forum Discussion
Select-Object Substring Blank
- May 23, 2022
Hi.
You're likely getting a blank because there's not such function named "Substring" in PowerShell. What does exist is the Substring() method on a string object, but you're not referencing that, hence the blank result.
Secondly, the property "Directory" is of type [System.IO.FileSystemInfo], not [System.String]. PowerShell can and does do various kind of implicit conversions but it's inefficient, occasionally ambiguous and therefore hard to debug for the uninitiated. Instead of "Directory", use the "DirectoryName" property, as it is of type [System.String].
You do not need the "Where" clause in this example. Get-ChildItem provides a parameter named "-File" which does what your "Where" clause does, just more efficiently.
Lastly, you need to perform some bounds checking as a call .Substring() cannot have a negative number for the starting position, else it will throw a .NET exception.
With those considerations in mind, here's an adjusted version of your command that does what you've asked.
Get-ChildItem -File -Filter *.txt -Recurse -Path "C:\SamIam\Listings" | Select-Object @{Name='Directory2';e={ if ($_.DirectoryName.Length -ge 10) { $_.DirectoryName.Substring($_.DirectoryName.Length - 10) } else { $_.DirectoryName } }},Name,DirectoryName
Cheers,
Lain
Other answers have mentioned a few issues with your script that I would agree with but I also wanted to present an alternate method of retrieving the information that you wanted.
Line 2 uses a Regular Expression to match the last 10 non-space characters of the DirectoryName property and then line 6 references the $Matches variable for that value for the output.
get-childitem c:\windows\system32 -file -recurse -ea silentlycontinue |foreach-object {
$null = $_.DirectoryName -match '(?<LastTen>\S{10})$'
[PSCustomObject]@{
Name = $_.name
Directory = $_.DirectoryName
Directory_Last10 = $matches.LastTen
}
}
- LainRobertsonMay 23, 2022Silver Contributor
Yeah, you definitely can do that. The only thing you'd need to change is the "{10}" would need to be "{1,10}". If you only specify {10}, then you'll fail to produce a match for strings with a length less than 10, which in turn results in a $null value.
Other considerations
Because we're talking about dealing with the file system, there's a pretty good chance performance wouldn't be an issue even if it's over ten or hundreds of thousands of files, but if performance were a requirement and the volume was substantial, a Substring() call would noticeably outpace the Regex methods.
This is going to look quite ugly in comparison to your more readable version - primarily because of the lengthy way in which you have to handle the options, but if I were to Regex it, I'd go via .NET directly.
Get-ChildItem -File -Filter *.txt -Recurse -Path "D:\Data" | Select-Object @{Name='Directory2';e={ [regex]::Match($_.DirectoryName, ".{1,10}$", [System.Text.RegularExpressions.RegexOptions]::RightToLeft -bor [System.Text.RegularExpressions.RegexOptions]::IgnoreCase -bor [System.Text.RegularExpressions.RegexOptions]::CultureInvariant).Value }},Name,DirectoryName
Cheers,
Lain
- LainRobertsonMay 23, 2022Silver Contributor
It's a slow enough night that I figured I'd use this scenario to take a quick peek at the overhead on PowerShell's "-match".
The testing is hardly rigorous given the low number of iterations for each statement, but it's still interesting.
I'll drop the test statements below but here's the takeaway from 20 iterations of each test type:
Test type Duration Effective run time Baseline ~ 2 seconds n/a .Substring() ~ 2.97 seconds 970 ms .NET Regex.Match().Value ~ 3.55 seconds 1.55 seconds PowerShell -match ~ 4.87 seconds 2.87 seconds Test statements:
# Baseline measurement $sw = [System.Diagnostics.Stopwatch]::StartNew(); for ($i = 0; $i -lt 1000000; $i++) { $n = "Prefix$($r.Next(1000, 100000))"; }; $sw.Stop(); $sw; # .Substring() test $sw = [System.Diagnostics.Stopwatch]::StartNew(); for ($i = 0; $i -lt 1000000; $i++) { $n = "Prefix$($r.Next(1000, 100000))"; if ($g.Length -lt 10) { $o = $g } else { $o = $g.Substring($g.Length - 10) } }; $sw.Stop(); $sw; # .NET Regex.Match().Value test $sw = [System.Diagnostics.Stopwatch]::StartNew(); for ($i = 0; $i -lt 1000000; $i++) { $n = "Prefix$($r.Next(1000, 100000))"; $o = [regex]::Match($_.DirectoryName, ".{1,10}$", [System.Text.RegularExpressions.RegexOptions]::RightToLeft -bor [System.Text.RegularExpressions.RegexOptions]::IgnoreCase -bor [System.Text.RegularExpressions.RegexOptions]::CultureInvariant).Value }; $sw.Stop(); $sw; # PowerShell -match test $sw = [System.Diagnostics.Stopwatch]::StartNew(); for ($i = 0; $i -lt 1000000; $i++) { $n = "Prefix$($r.Next(1000, 100000))"; $null = $n -match ".{1,10}$"; $o = $Matches[0] }; $sw.Stop(); $sw;
PowerShell is really meant for administration meaning these metrics really don't count for much, but it's still interesting (though maybe only to me.)
Cheers,
Lain