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
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
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