如何在添加文件夹名称到目标文件名称的情况下复制文件?

时间:2019-08-22 07:07:12

标签: batch-file

我需要遍历子文件夹列表,以将所有文件从这些子文件夹复制到新文件夹中。我想用子文件夹名称+ - +文件名命名目标文件夹中的复制文件,因为多个子文件夹可能包含相同名称的文件。

例如,我有以下文件:

C:\Old\Folder1\a.txt
C:\Old\Folder1\b.txt
C:\Old\Folder2\a.txt
C:\Old\Folder2\b.txt

我想将以上文件复制到新文件夹C:\New中。最终结果应该是:

C:\New\Folder1-a.txt
C:\New\Folder1-b.txt
C:\New\Folder2-a.txt
C:\New\Folder2-b.txt

我尝试了以下代码,但无法正常工作。

for /r "C:\Old" %%d in (*) do copy "%%d" "C:\New\%%~nxI-%%~nxf"

如何复制将文件夹名称添加到目标文件名称的文件?

3 个答案:

答案 0 :(得分:0)

第一个批处理文件即使在任何文件或文件夹名称中带有一个或多个感叹号!,也可以工作。

@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "SourceFolder=%~1"
if not defined SourceFolder set "SourceFolder=C:\Old"
set "SourceFolder=%SourceFolder:/=\%"
if not "%SourceFolder:~-1%" == "\" set "SourceFolder=%SourceFolder%\"
set "TargetFolder=%~2"
if not defined TargetFolder set "TargetFolder=C:\New\"
set "TargetFolder=%TargetFolder:/=\%"
if not "%TargetFolder:~-1%" == "\" set "TargetFolder=%TargetFolder%\"

set "TargetCreated="
if not exist "%TargetFolder%" (
    md "%TargetFolder%" 2>nul
    if not exist "%TargetFolder%" (
        echo ERROR: Failed to create destination folder:
        echo/
        echo "%TargetFolder%"
        echo/
        pause
        goto EndBatch
    )
    set "TargetCreated=yes"
)

for /F "delims=" %%I in ('dir "%SourceFolder%*" /A-D-H /B /S 2^>nul ^| %SystemRoot%\System32\findstr.exe /B /L /V /C:"%TargetFolder:\=\\%"') do call :FileCopy "%%I"

if defined TargetCreated rd "%TargetFolder%" 2>nul
goto EndBatch

:FileCopy
set "FilePath=%~dp1"
set "FilePath=%FilePath:~0,-1%"
for %%J in ("%FilePath%") do set "FolderName=%%~nxJ-"
if "%FilePath:~-1%" == ":" set "FolderName="
copy /Y %1 "%TargetFolder%%FolderName%%~nx1" >nul
goto :EOF

:EndBatch
endlocal

缺点是,与下面的第二个批处理文件相比,此批处理文件在复制数千个文件时速度较慢。

@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "SourceFolder=%~1"
if not defined SourceFolder set "SourceFolder=C:\Old"
set "SourceFolder=%SourceFolder:/=\%"
if not "%SourceFolder:~-1%" == "\" set "SourceFolder=%SourceFolder%\"
set "TargetFolder=%~2"
if not defined TargetFolder set "TargetFolder=C:\New\"
set "TargetFolder=%TargetFolder:/=\%"
if not "%TargetFolder:~-1%" == "\" set "TargetFolder=%TargetFolder%\"

set "TargetCreated="
if not exist "%TargetFolder%" (
    md "%TargetFolder%" 2>nul
    if not exist "%TargetFolder%" (
        echo ERROR: Failed to create destination folder:
        echo/
        echo "%TargetFolder%"
        echo/
        pause
        goto EndBatch
    )
    set "TargetCreated=yes"
)

setlocal EnableDelayedExpansion
for /F "delims=" %%I in ('dir "%SourceFolder%*" /A-D-H /B /S 2^>nul ^| %SystemRoot%\System32\findstr.exe /B /L /V /C:"%TargetFolder:\=\\%"') do (
    set "FilePath=%%~dpI"
    set "FilePath=!FilePath:~0,-1!"
    for %%J in ("!FilePath!") do set "FolderName=%%~nxJ-"
    if "!FilePath:~-1!" == ":" set "FolderName="
    copy /Y "%%I" "%TargetFolder%!FolderName!%%~nxI" >nul
)
endlocal

if defined TargetCreated rd "%TargetFolder%" 2>nul

:EndBatch
endlocal

两个批处理文件都可以不带任何参数地启动,只有一个参数被解释为源文件夹路径,或者带有两个参数,第二个参数被解释为目标文件夹路径。如果启动批处理文件时不带任何参数,则C:\Old定义为源文件夹路径;如果启动批处理文件时不带第二个参数,则C:\New定义为目标文件夹路径。批处理文件确保两个文件夹路径都以反斜杠结尾。

已创建目标文件夹(如果尚不存在),并验证目标文件夹的创建是否成功。在无法成功创建目标文件夹上退出批处理文件执行之前,将输出一条错误消息并停止执行批处理文件。

目标文件夹可能是源文件夹的子文件夹。因此, DIR FINDSTR 的命令行由 FOR 在以%ComSpec% /c开头的后台执行的单独命令过程中执行并附加指定的命令行以复制要复制的所有文件名,并过滤掉源文件夹树中的所有文件名(从文件夹路径等于目标文件夹路径开始)。因此,由 FOR 执行的示例是:

C:\Windows\System32\cmd.exe /c dir "C:\Old\*" /A-D-H /B /S 2>nul | C:\Windows\System32\findstr.exe /B /L /V /C:"%C:\\New\\"

DIR 搜索

  • 在指定目录C:\Old所有的子目录中,这是由于选项/S
  • 对于由于选项/A-D-H而引起的非隐藏文件(属性不是目录,也不是隐藏的)
  • 匹配通配符模式*(任何文件名)和
  • 由于选项/B,以纯格式输出的只是文件名
  • 由于选项/S而具有完整路径。

DIR 的输出由后台启动的cmd.exe重定向到 FINDSTR ,其中

  • 由于选项/B,仅搜索所有行的开头
  • 对于由选项/L明确指定的文字解释字符串
  • 使用选项/C:指定的搜索字符串,其中每个反斜杠都使用一个反斜杠进行转义
  • 并由于选项/V输出反转的结果,这意味着所有行从搜索字符串开始。

注意:C:\Old\Temp这样的源文件夹和C:\Old这样的目标文件夹导致不复制任何文件。换句话说,目标文件夹可以是源文件夹的子文件夹,但源文件夹不能是目标文件夹的子文件夹。

FINDSTR 的输出用于处理已启动命令过程的 STDOUT (标准输出),由 FOR 捕获,并在启动后逐行处理cmd.exe终止。

阅读有关Using Command Redirection Operators的Microsoft文章,以获取2>nul|的解释。重定向操作符>|必须用 FOR 命令行上的脱字符号^进行转义,以在Windows命令解释器处理此命令行时将其解释为原义字符。在执行命令 FOR 之前,该命令将在后台启动的单独命令进程中执行嵌入式命令行。

带有选项/F

FOR 默认情况下会忽略此处未出现的所有空行。

带有选项/F

FOR 将默认使用普通空格和水平制表符作为字符串定界符将每行拆分为子字符串,并且仅将第一个空格/制表符分隔的字符串分配给指定的循环变量I。文件/文件夹名称可以包含一个或多个空格。因此,在双引号中指定了选项delims=,以定义一个空的定界符列表,该列表将完全关闭行分割功能,以完全为循环变量I分配每个完全合格的文件名。

默认情况下,带有选项/F

FOR 也会忽略所有以分号开头的行,因为eol=;是在删除每行开头的分隔符(此处没有分隔符)之后的原因行尾选项的默认设置。但是,如果指定的源文件夹路径是UNC路径,则具有完整路径的文件名总是以驱动器号和冒号或两个反斜杠开头。因此不必更改默认的行尾选项。

第一个批处理文件将子程序FileCopy调用为当前的完整合格文件名,这是第一个也是唯一的参数,以避免使用delayed expansion

第二个批处理文件使用延迟的环境变量扩展,速度更快,但缺点是文件夹或文件名中的感叹号被解释为延迟的扩展环境变量引用的开始/结尾,因此两个{{ 1}}被引用的环境变量的值替换;如果不存在具有该名称的环境变量,则简单地从文件/文件夹字符串中删除单个!

文件的完整路径已分配给环境变量!。文件路径始终以反斜杠结尾,必须通过使用另外一个分配给FilePath FOR 确定文件夹名称,然后将其删除。 FolderName引用最后一个反斜杠之后的所有内容,通常是文件名和文件扩展名,但是在这种情况下,包含文件的文件夹的名称。连字符已附加到文件夹名称中。

注意:指定为源文件夹(例如%%~nxJ)的驱动器根文件夹中的文件被此批处理文件复制,目标文件名为源文件名。

复制当前文件时,目标文件名是源文件名,带有文件夹名,并在开头插入连字符(驱动器根目录中的文件除外)。没有对此批文件成功复制文件的验证。

批处理文件会删除之前创建的目标文件夹,并且目标文件夹仍然为空,因为在源文件夹树中找不到要复制的文件。

要了解所使用的命令及其工作方式,请打开命令提示符窗口,在其中执行以下命令,并非常仔细地阅读每个命令显示的所有帮助页面。

  • C:\
  • call /?
  • copy /?
  • dir /?
  • echo /?
  • endlocal /?
  • findstr /?
  • goto /?
  • if /?
  • md /?
  • pause /?
  • rd /?
  • set /?

另请参阅:

答案 1 :(得分:0)

不知道是否还有 级别的子文件夹(for /r的使用暗示了什么)
仅使用C:\OLD的一个子文件夹级别,事情就容易多了:

:: Q:\Test\2019\08\22\SO_57603775.cmd
@Echo off
Set "Src=C:\OLD"
Set "Dst=C:\NEW"

md "%Dst%" >NUL 2>&1 ||(Echo can't create %Dst% ... exiting&pause&Exit /B 1)
cd /d "%Src%"        ||(Echo can't locate %Src% ... exiting&pause&Exit /B 1)

for /D %%D in (*) do for %%F in (%%D\*) do Copy "%%~fF" "%Dst%\%%~nxD-%%~nxF"

否则,会有Mofi个很好且详尽解释的答案。

答案 2 :(得分:0)

由于您的源目录具有一定的层次结构深度,因此我建议不要使用for /R来枚举目录,而for /D来枚举文件:

@echo off
rem // Iterate through the immediate sub-directories of the source directory:
for /D %%D in ("C:\Old\*") do (
    rem // Iterate through all files in the currently iterated sub-directory:
    for %%F in ("%%~D\*.*") do (
        rem /* Copy the currently iterated file into the destination directory
        rem    and rename it so that the parent directory name is prefixed: */
        copy /-Y "%%~F" "C:\New\%%~nxD-%%~nxF"
    )
)

如果不希望系统提示您覆盖目标目录中已经存在的文件,请用/-Y替换/Y