MMedina
MMedina

Reputation: 13

Apply powershell to subfolders

I have a lot of folders with image files that accidentally saved without extension. I've managed to work out how to add the extension:

ls -file | ? {$_.extension -eq ''} | % {ren $_ ($_.name + '.jpeg')}

... but I can't work out how to make it apply to all subfolders.
This command doesn't work:

Get-ChildItem -Recurse ls -file | ? {$_.extension -eq ''} | % {ren $_ ($_.name + '.jpeg')}

any ideas where I'm going wrong, please?

Upvotes: 1

Views: 48

Answers (2)

mklement0
mklement0

Reputation: 439832

Mathias' helpful answer explains the problem well and offers an effective solution.


To add to it with a performance improvement:

In order to recursively find all extension-less files, you can replace:

Get-ChildItem -Recurse -File | ? {$_.extension -eq ''}

with:

Get-ChildItem -Recurse -File -Filter *.

which performs much better, because filtering happens at the source (low-level file-system APIs) rather than after the fact via Where-Object (whose built-in aliases are ? and where).

Note:

  • *. to match only extension-less files relies on legacy quirks of the -Filter argument.

    • The -Filter wildcard syntax is not the same as PowerShell's wildcard expressions; notably, *. would not work with Get-ChildItem's -Include / -Exclude parameters, which support only PowerShell wildcards.
  • While helpful in this case, the inconsistencies of the wildcard syntax supported by -Filter are potential pitfalls; notably, -Filter *.* does not limit matches to files with an extension; it matches all files, and is effectively redundant.

    • By contrast, in PowerShell's wildcard syntax, *.* sensibly does match only files with an extension, but there are caveats:
      • The use of -Include and -Exclude comes with its own pitfalls - see the bottom section of this answer.
      • Because, like with the Where-Object approach, filtering is performed after the fact (all files are enumerated and then filtered), it is much slower than -Filter use.
  • The bottom line with respect to filtering with Get-ChildItem:

    • Use -Filter for best performance, but beware its quirks, see this answer for an overview.

    • Use -Include if you need to support multiple patterns and/or quirk-free syntax and/or extended features, namely the use of [...] for character sets and ranges, but beware the counterintuitive behavior when -Recurse is not used - see the bottom section of this answer.

    • Use Where-Object for post-filtering, if you need to perform more sophisticated filtering than wildcard expressions can provide; inside the script block passed to Where-Object, you can use regexes for matching, for instance.

Upvotes: 0

Mathias R. Jessen
Mathias R. Jessen

Reputation: 174825

First, make sure to remove ls from Get-ChildItem -Recurse ls -file - ls is an alias for Get-ChildItem, and since you've already specified the cmdlet name PowerShell will attempt to locate a folder named ls and start the search from there.

Second, instead of passing $_ to Rename-Item (alias ren) as a positional argument, pipe it:

Get-ChildItem -Recurse -File | ? {$_.extension -eq ''} | Rename-Item -NewName { $_.Name + '.jpeg' }

Piping the whole file info object to Rename-Item allows PowerShell to bind the correct absolute path to the file system object, and it'll thus start working for files in nested folders.

Upvotes: 2

Related Questions