Reputation: 1400
I am looking for a way to consistently list directory contents in the following scenario i.e.
$dirs = @("C:/dir1", "C:/dir2", "C:/dir[3-5]")
foreach ($d in $dirs) {
Get-ChildItem $d
}
For the first and second iterations the directory contents get listed, but for the 3rd iteration only the matching directories get listed rather than their contents.
Please provide for PowerShell v2+
Upvotes: 1
Views: 278
Reputation: 437197
Note:
Don Cruickshank's helpful answer provides an effective and elegant solution.
This answer provides some background information and offers an alternative solution based on Convert-Path
.
Indeed, a directory path specified as a wildcard expression causes the matching directories themselves rather than their content to be listed:
Literal path:
# NO wildcard -> directory *content* is listed
PS> Get-ChildItem C:\Windows
Directory: C:\Windows
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 4/11/2018 7:38 PM addins
d----- 8/16/2018 10:06 AM appcompat
d----- 10/9/2018 5:39 PM apppatch
Wildcard path (note the [...]
around s
):
# Wildcard -> resolved directory *itself* is listed
PS> Get-ChildItem C:\Window[s]
Directory: C:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 9/18/2018 10:07 PM Windows
Therefore, in order to list directories' contents, wildcard expressions must be resolved to their matching paths, before passing them to Get-ChildItem
.
One option is to use the Convert-Path
cmdlet first[1], and pass its output to Get-ChildItem
via the -LiteralPath
parameter:
Get-ChildItem -LiteralPath ('C:/dir1', 'C:/dir2' + (Convert-Path 'C:/dir[3-5]'))
Note: Using -LiteralPath
isn't strictly needed, but good practice, because specifying paths positionally - as the first positional argument - implicitly binds to the -Path
parameter, which interprets its arguments as wildcard patterns, and there is a risk of literal paths accidentally looking like wildcard patterns.
Note how the above command passes all (resulting) directories as an array to a single invocation of Get-ChildItem
which makes the command more efficient.
However, in a given scenario you may still need to use individual Get-ChildItem
calls in a loop:
foreach ($dir in 'C:/dir1', 'C:/dir2' + (Convert-Path 'C:/dir[3-5]')) {
Get-ChildItem -LiteralPath $dir
}
Note the use of +
- array concatenation, in this case - to ensure that a flat array of paths is created; by contrast, 'C:/dir1', 'C:/dir2', (Convert-Path 'C:/dir[3-5])
would create a nested array, with the wildcard-resolved paths all stored in a sub-array stored in the output array's last element.
Given that Get-ChildItem -LiteralPath
also accepts arrays, this wouldn't cause an error, but it wouldn't result in one-by-one dir. processing.
[1] There's also a Resolve-Path
cmdlet, but it returns [System.Management.Automation.PathInfo
] instances, which aren't strictly needed here, since it is strings that bind to -LiteralPath
.
Also, Convert-Path
always returns an actual filesystem path, whereas Resolve-Path
resolves to PS drive-based paths, if applicable - while that won't make a difference here, it would if you passed the paths to .NET methods, for instance.
Upvotes: 3
Reputation: 5938
You could use Get-Item
to perform the wildcard expansion and then get the child items of each match:
foreach ($dir in Get-Item $dirs) {
Get-ChildItem -LiteralPath $dir
}
Upvotes: 2
Reputation: 3908
Use Get-ChildItem -LiteralPath $d
instead of Get-ChildItem $d
since the first pipline variable will be used as the -Path
which allows wildcard expressions. -LiteralPath $d
does not.
$dirs = @("C:/dir1", "C:/dir2", "C:/dir[3-5]")
foreach ($d in $dirs) {
Get-ChildItem -LiteralPath $d
}
Upvotes: 0