简体   繁体   中英

Batch copy and rename files with PowerShell

I'm trying to use PowerShell to batch copy and rename files.

The original files are named AAA001A.jpg, AAB002A.jpg, AAB003A.jpg, etc.

I'd like to copy the files with new names, by stripping the first four characters from the filenames, and the character before the period, so that the copied files are named 01.jpg, 02.jpg, 03.jpg, etc.

I have experience with Bash scripts on Linux, but I'm stumped on how to do this with PowerShell. After a couple of hours of trial-and-error, this is as close as I've gotten:

Get-ChildItem AAB002A.jpg | foreach { copy-item $_ "$_.name.replace ("AAB","")" }

(it doesn't work)

Not Powershell, but Batch File:

(since someone wants to be ultra-pedantic about comments)

@echo off
setlocal enabledelayedexpansion
for %%a in (*.jpg) do (    
    ::Save the Extension
    set EXT=%%~xa

    ::Get the Source Filename (no extension)
    set SRC_FILE=%%~na

    ::Chop the first 4 chars
    set DST_FILE=!SRC_FILE:~4!

    ::Chop the last 1 char.
    set DST_FILE=!DST_FILE:~,-1!

    :: Copy the file
    copy !SRC_FILE!!EXT! !DST_FILE!!EXT! ) 

In Powershell:
(without nasty regexs. We hates the regexs! We does!)

Get-ChildItem *.jpg | Copy-Item -Destination {($_.BaseName.Substring(4) -replace ".$")+$_.Extension} -WhatIf

Details on the expression:

$_.BaseName.Substring(4)  :: Chop the first 4 letters of the filename.
-replace ".$"             :: Chop the last letter.
+$_.Extension             :: Append the Extension

Note:
* While perhaps slightly more complex than abelenky's answer , it (a) is more robust in that it ensures that only *.jpg files that fit the desired pattern are processed, (b) shows some advanced regex techniques, (c) provides background information and explains the problem with the OP's approach.
* This answer uses PSv3+ syntax.

Get-ChildItem *.jpg |
  Where-Object Name -match '^.{4}(.+).\.(.+)$' | 
    Copy-Item -Destination { $Matches.1 + '.' + $Matches.2 } -WhatIf

To keep the command short, the destination directory is not explicitly controlled, so the copies will be placed in the current dir. To ensure placement in the same dir. as the input files, use
Join-Path $_.PSParentPath ($Matches.1 + '.' + $Matches.2) inside { ... } .

-WhatIf previews what files would be copied to; remove it to perform actual copying.

  • Get-ChildItem *.jpg outputs all *.jpg files - whether or not they fit the pattern of files to be renamed.

  • Where-Object Name -match '^.{4}(.*).\\.(.+)$' then narrows the matches down to those that fit the pattern, using a regex (regular expression):

    • ^...$ anchors the regular expression to ensure that it matches the whole input ( ^ matches the start of the input, and $ its end).
    • .{4} matches the first 4 characters ( . ), whatever they may be.
    • (.+) matches any nonempty sequence of characters and, due to being enclosed in (...) , captures that sequence in a capture group , which is reflected in the automatic $Matches variable, accessible as $Matches.1 (due to being the first capture group).
    • . matches the character just before the filename extension.
    • \\. matches a literal . , due to being escaped with \\ - ie, the start of the extension.
    • (.+) is the 2nd capture group that captures the filename extension (without the preceding . literal), accessible as $Matches.2 .
  • Copy-Item -Destination { $Matches.1 + '.' + $Matches.2 } Copy-Item -Destination { $Matches.1 + '.' + $Matches.2 } then renames each input file based on the capture-group values extracted from the input filenames.

    • Generally, directly piping to a cmdlet, if feasible, is always preferable to piping to the Foreach-Object cmdlet (whose built-in alias is foreach ), for performance reasons.
      In the Copy-Item command above, the target path is specified via a script-block argument , which is evaluated for each input path with $_ bound to the input file at hand.

    • Note: The above assumes that the copies should be placed in the current directory, because the script block outputs a mere filename , not a path .

      • To control the target path explicitly, use Join-Path inside the -Destination script block.
        For instance, to ensure that the copies are always placed in the same folder as the input files - irrespective of what the current dir. is - use:
        Join-Path $_.PSParentPath ($Matches.1 + '.' + $Matches.2)

As for what you've tried :

  • Inside "..." (double-quoted strings), you must use $(...) , the subexpression operator , in order to embed expressions that should be replaced with their value.

  • Irrespective of that, .replace ("AAB", "") (a) breaks syntactically due to the space char. before ( (did you confuse the [string] type's .Replace() method with PowerShell's -replace operator ?), (b) hard-codes the prefix to remove, (c) is limited to 3 characters, and (d) doesn't remove the character before the period.

  • The destination-location caveat applies as well: If your expression worked, it would only evaluate to a filename , which would place the resulting file in the current directory rather than the same directory as the input file (though that wouldn't be a problem, if you ran the command from the current dir. or if that is your actual intent).

try this:

Get-ChildItem "C:\temp\Test" -file -filter "*.jpg" | where BaseName -match '.{4,}' | 
    %{ Copy-Item $_.FullName  (Join-Path $_.Directory  ("{0}{1}" -f  $_.BaseName.Substring(4, $_.BaseName.Length - 5), $_.Extension))    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM