Forum Discussion
Select-Object Substring Blank
Hello, thank you for you assistance in advance. I'm trying to grab the last 10 characters of the Directory. For some reason it's returning blank, and I'm not able to figure out why. Below is what I have. Appreciate any assistance.
Get-ChildItem -Filter *.txt -Recurse "C:\SamIam\Listings" | Where { ! $_.PSIsContainer } | Select-Object @{Name='Directory2';e={Substring(Directory.length - 10,10)}},Name,Directory
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
6 Replies
- fatherjackCopper Contributor
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 } }
- LainRobertsonSilver 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
- LainRobertsonSilver 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
- LainRobertsonSilver Contributor
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
- ThisisitCopper ContributorThank you all for your assistance, and the various options! Great help, and much appreciated!
Thisisit I couldn't get it to work nicely in one line so.. a bit larger script:
#Set total variable to null $total = @() #Set directory to scan $directory = 'D:\test' #Get all txtfiles from $directory path and put them in $variable #If length of directory name is greater than 10, select last ten characters #If length is shorter, just add it to the list foreach ($txtfile in Get-ChildItem -filter *.txt -Recurse -Path $directory | Where-Object { -not $_.PSISContainer }) { if ($txtfile.Directory.name.length -gt 10) { $data = [PSCustomObject]@{ Directory = $txtfile.DirectoryName.substring($txtfile.DirectoryName.length - 10, 10) Name = $txtfile.Name } } else { $data = [PSCustomObject]@{ Directory = $txtfile.Directory.Name Name = $txtfile.Name } } $total += $data } #output found items $total
This will result in a output like this:
Directory Name --------- ---- test x.txt 1234567890 y.txt qrstuvwxyz z.txt
Test directories were like this: