如果存档没有子文件夹,如何在 RAR/ZIP 提取时创建子文件夹?

How to create subfolder on RAR/ZIP extraction if archive does not have one?

我有很多 RAR 或 ZIP 文件需要解压。一些档案包含一个文件夹,其中包含该文件夹中的所有文件。其他一些档案的所有文件都在根级别。

案例 01

Archive01.rar
    MyFolder
       file01.txt
       file02.txt 
       file03.txt 
       etc.

案例02

Archive02.rar
    -file01.txt
    -file02.txt
    -file031.txt
    etc.

我知道如何将所有档案提取到子文件夹中。

但是如何仅在存档中存在 none 时才创建子文件夹?

我的意思是在处理数千个档案的批处理过程中,如果档案文件属于 case 01,则不应在提取时额外创建文件夹。但是,如果存档文件属于 case 02,则应将解压缩到具有存档文件名称的子文件夹中。

案例 01 结果

 MyFolder <- Folder
   file01.txt
   file02.txt 
   file03.txt 
   etc.

案例 02 结果

 Archive02 <-Folder base on the archive name
   -file01.txt
   -file02.txt
   -file031.txt
   etc.

您必须检查数据是在文件夹内还是在根目录中。

如果它在 root 中,请单击 extract to /foldername(案例 02)

否则就在这里提取(案例01)

控制台版本Rar.exe有命令l列出归档文件内容根据Rar.txt程序文件文件夹中的文本文件WinRAR 是控制台版本的手册。使用命令 l(或 L)在 运行ning Rar.exe 上的列表输出可以在批处理文件中处理以确定 RAR 存档文件是否仅在顶层包含单个目录,仅此而已。但是 Rar.exe 支持免费 UnRAR.exe 只是 RAR 压缩文件。

要同时支持 ZIP 存档,必须使用 GUI 版本 WinRAR.exe,它支持提取 RAR 和 ZIP 存档以及其他一些存档类型。

WinRAR.exe的手册是WinRAR的帮助,点击菜单帮助菜单项即可打开关于 运行ning WinRAR 的帮助主题。在帮助选项卡 内容 上有列表项 命令行模式 以及 运行ning [=22] 的参考帮助页面中的所有必要信息=] 来自命令行。

查看 命令列表 可以看出 WinRAR.exe 不支持命令 l 将存档文件内容输出到控制台 window 因为是图形用户界面应用程序。

因此,使用 WinRAR.exe.

时,实际上不可能从命令行或批处理文件中确定存档文件是否在顶层只包含一个目录

但是,这并不重要,因为首先解析存档文件的文件名和目录名然后使用适当的命令来提取存档文件而不在命令行上指定额外的提取文件夹会效率低下.

首先使用一个 WinRAR 调用和开关 -ad 来提取所有 *.rar(然后也是所有 *.zip)文件效率更高将每个存档文件解压缩到一个具有存档文件名称的子目录中,然后消除每个不需要的解压缩目录,因为相应的存档文件只包含一个顶级目录。

下面的批处理文件中使用了这种聪明的方法,它包含以下附加功能,希望对许多 WinRAR 用户有用:

  1. 工作目录可以指定为调用批处理文件的第一个参数,甚至可以是 UNC 路径。

  2. 批处理文件会自动找出 WinRAR.exe 的安装位置,也适用于 32 位或 64 位 WinRAR 的用例正在安装在默认程序文件目录中(在我所有的计算机上)。

注意:下面发布的注释批处理文件不会检查当前或指定目录中是否已提取现有存档文件。因此,不建议在一个目录中多次 运行 批处理文件,归档文件一旦处理就不会从该目录中删除。

@echo off
rem Change working directory if batch file was started with an argument.
if not "%~1" == "" (
    pushd "%~1" 2>nul
    if errorlevel 1 (
        echo Specified directory "%~1" does not exist.
        echo/
        pause
        goto :EOF
    )
)

setlocal EnableExtensions DisableDelayedExpansion

rem Does WinRAR exist in default program files folder?
set "WinRAR=%ProgramFiles%\WinRAR\WinRAR.exe"
if exist "%WinRAR%" goto StartExtraction

rem Does WinRAR exist in default program files folder for x86 applications?
set "WinRAR=%ProgramFiles(x86%\WinRAR\WinRAR.exe"
if exist "%WinRAR%" goto StartExtraction

rem Try to determine installation location of WinRAR.exe from registry.
set "TypeToken=2"
goto GetPathFromRegistry

rem On Windows Vista and later REG.EXE outputs without version info:

rem HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\App Paths\WinRAR.exe
rem    (Default)    REG_SZ    Full path to WinRAR\WinRAR.exe

rem There are only spaces used to separate value name, value type and value string.

rem But REG.EXE version 3.0 outputs on Windows XP with version info:

rem ! REG.EXE VERSION 3.0
rem
rem HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\App Paths\WinRAR.exe
rem     <NO NAME>   REG_SZ  Full path to WinRAR\WinRAR.exe

rem NOTE: There are 4 indent spaces and 2 separating tabs in REG 3.0 output line.

rem So either token 2 or token 3 contains value type REG_SZ
rem used to identify the line with the wanted information.

:GetPathFromRegistry
for /F "skip=1 tokens=%TypeToken%*" %%A in ('%SystemRoot%\System32\reg.exe QUERY "HKLM\Software\Microsoft\Windows\CurrentVersion\App Paths\WinRAR.exe" /ve 2^>nul') do (
    if "%%A" == "REG_SZ" (
        if exist "%%~fB" (
            set "WinRAR=%%~fB"
            goto StartExtraction
        )
    ) else if "%%A" == "NAME>" (
        set "TypeToken=3"
        goto GetPathFromRegistry
    )
)

endlocal
if not "%~1" == "" popd
echo Could not determine directory containing WinRAR.exe.
echo/
echo Please configure it manually in file: %~f0
echo/
pause
goto :EOF


rem WinRAR supports multiple archive types on extraction.
rem Specify here the archive file extensions for extraction.

:StartExtraction
for %%I in (rar zip) do call :ExtractArchives %%I

rem Restore previous command environment, restore previous current directory
rem and exit this batch file without fall through to the subroutines below.
endlocal
if not "%~1" == "" popd
goto :EOF


rem The subroutine ExtractArchives processes all archive files in current
rem directory with the file extension passed to subroutine as first argument.

rem WinRAR is called once to extract all files with specified file extension
rem for extraction into a subdirectory with name of the archive file.

rem Then one more subroutine is called for each archive file to determine
rem if it is safe to move the extracted archive file contents up one level.

:ExtractArchives
if not exist "*.%~1" goto :EOF
"%WinRAR%" x -ad -cfg- -ibck -y -- "*.%~1"
for %%A in ("*.%~1") do call :MoveUpExtracted "%%~nA"
goto :EOF

rem The subroutine MoveUpExtracted first checks if for the archive file
rem passed to the subroutine as first argument a subdirectory exists at
rem all, i.e. the extraction before was successful for that archive.

rem Next it counts the subdirectories in the archive extraction directory.
rem Nothing is moved up if there is more than 1 subdirectory in archive
rem extraction directory.

rem Also nothing is moved up if archive extraction directory contains
rem 1 or more files.

rem After verification of archive extraction directory really containing
rem only a single subdirectory and nothing else, the name of the archive
rem extraction directory is compared case-insensitive with the name of
rem the single subdirectory in archive extraction directory. On equal
rem directory names the archive extraction directory is renamed by
rem appending _tmp to make it possible to move the subdirectory with same
rem name up one level in directory hierarchy. There is hopefully by chance
rem never a directory present in current directory with name of an archive
rem file and _tmp appended.

rem Next it is checked if in current directory there is not already existing
rem a directory with name of the subdirectory from extracted archive in which
rem case it is also not possible to move the directory up one level. In this
rem special use case the archive extraction directory is kept containing just
rem a single subdirectory with restoring original directory name.

rem Then the single subdirectory in archive extraction directory is moved up
rem one level which is very fast as just the file allocation table is updated
rem and no data is really moved.

rem The directory movement could fail if the extracted directory has hidden
rem attribute set. In this case temporarily remove the hidden attribute,
rem move the directory up one level in directory hierarchy and set the
rem hidden attribute again on the directory.

rem On a succesful moving up of the extracted directory the (renamed)
rem extraction directory being now empty is deleted as not further needed.


:MoveUpExtracted
if not exist "%~1\" (
    echo Error: No folder for archive %~1
    goto :EOF
)

echo Processing archive folder "%~1"
set FolderCount=0
set "FolderName="
for /F "delims=" %%D in ('dir "%~1\*" /AD /B 2^>nul') do (
    if defined FolderName goto :EOF
    set /A FolderCount+=1
    set "FolderName=%%D"
)
if not %FolderCount% == 1 goto :EOF

for /F "delims=" %%F in ('dir "%~1\*" /A-D /B 2^>nul') do goto :EOF

set "ParentRenamed=0"
set "ParentFolder=%~1"
if /I "%~1" == "%FolderName%" (
    ren "%~1" "%~1_tmp" 2>nul
    if errorlevel 1 (
        echo Failed to rename "%~1" to "%~1_tmp".
        goto :EOF
    )
    set "ParentFolder=%~1_tmp"
    set "ParentRenamed=1"
)

if exist "%FolderName%" (
    if %ParentRenamed% == 1 ren "%~1_tmp" "%~1"
    echo Error: Folder exists "%FolderName%"
    goto :EOF
)

move "%ParentFolder%\%FolderName%" "%FolderName%" >nul 2>nul
if not errorlevel 1 (
    rd "%ParentFolder%"
    goto :EOF
)

%SystemRoot%\System32\attrib.exe -h "%ParentFolder%\%FolderName%" >nul
move "%ParentFolder%\%FolderName%" "%FolderName%" >nul
if errorlevel 1 (
    if %ParentRenamed% == 1 (
        ren "%ParentFolder%" "%~1"
        goto :EOF
    )
)

%SystemRoot%\System32\attrib.exe +h "%FolderName%"
rd "%ParentFolder%"
goto :EOF

我从 Windows 95 开始使用 32 位 Windows,但我 运行 自己从未进入 MAX_PATH 限制,即绝对 file/folder名称超过 259 个字符。

因此,重写批处理文件使其在存档文件名很长时也能正常工作是一个非常有趣且非常耗时的挑战,例如,文件名 + 文件扩展名正好是 256 个字符。

在开发下面的批处理文件的过程中,我发现了以下内容:

  1. 一些命令,如 DIRFORRDREN 支持路径中的短 8.3 名称和 file/folder 名称,而其他命令如 ATTRIBMOVE仅在路径中支持它们,但在 file/folder 名称中不支持它们(至少在 Windows XP 上)。
    因此无法使用其短 8.3 名称移动文件夹或更改其属性.

  2. 当完整路径的文件夹名称超过 259 个字符时,所有命令都无法使用相对文件夹名称和相对文件夹路径。这意味着 Windows 命令解释器在执行任何命令之前首先确定具有完整路径的文件夹名称。因此,当前目录应该有一个短路径来处理名称很长的档案或包含名称很长的目录。

  3. 我无法弄清楚如何使用 %~fs1 获取文件夹的简称或其路径,如 call /?%%~fsI 所述(在批处理文件中) 正如 for /? 所解释的,当 Windows 命令解释器仅解析相对文件夹路径时,即只是没有路径的文件夹的长名称。

  4. 在 运行ning 命令 DIR 中使用选项 /X 获取目录的短名称,第三列包含短名称名称,第四列是长名称。但是非常短的文件夹名称可能会缺少第三列中的短名称。

dir /AD /X 的英文输出 Windows 7 SP1 x64 在 NTFS 分区上执行,德国设置在 Windows 区域和语言设置中:

 Volume in drive C is System
 Volume Serial Number is 7582-4210

 Directory of C:\Temp\Test

29.04.2017  22:39    <DIR>                       .
29.04.2017  22:39    <DIR>                       ..
29.04.2017  22:39    <DIR>          ARCHIV~1     archive_with_a_very_very_very_..._long_name_1
29.04.2017  22:39    <DIR>                       Batch
29.04.2017  22:39    <DIR>                       xyz

同样的命令 dir /AD /X 在德语 Windows XP SP3 x86 上执行,在 FAT32 分区上,德国也在 Windows 区域和语言设置中设置:

 Datenträger in Laufwerk F: ist TEMP
 Volumeseriennummer: CAA5-41AA

 Verzeichnis von F:\Temp

29.04.2017  22:39    <DIR>                       .
29.04.2017  22:39    <DIR>                       ..
29.04.2017  22:39    <DIR>          BATCH        Batch
29.04.2017  22:39    <DIR>                       xxx
29.04.2017  22:39    <DIR>          ARCHIV~1     archive_with_a_very_very_very_..._long_name_1

注意: 很长的目录名是我在这里整理的运行,名字是...

为什么目录 Batch 在 Windows XP 计算机上有短名称 BATCH 但在 Windows 7 上没有短名称对我来说真的无法解释。

只要当前目录的路径很短,这里的批处理脚本还支持存档中的长存档名称和长目录名称。

@echo off
rem Change working directory if batch file was started with an argument.
if not "%~1" == "" (
    pushd "%~1" 2>nul
    if errorlevel 1 (
        echo Specified directory "%~1" does not exist.
        echo/
        pause
        goto :EOF
    )
)

setlocal EnableExtensions DisableDelayedExpansion

rem Does WinRAR exist in default program files folder?
set "WinRAR=%ProgramFiles%\WinRAR\WinRAR.exe"
if exist "%WinRAR%" goto StartExtraction

rem Does WinRAR exist in default program files folder for x86 applications?
set "WinRAR=%ProgramFiles(x86%\WinRAR\WinRAR.exe"
if exist "%WinRAR%" goto StartExtraction

rem Try to determine installation location of WinRAR.exe from registry.
set "TypeToken=2"
goto GetPathFromRegistry

rem On Windows Vista and later REG.EXE outputs without version info:

rem HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\App Paths\WinRAR.exe
rem    (Default)    REG_SZ    Full path to WinRAR\WinRAR.exe

rem There are only spaces used to separate value name, value type and value string.

rem But REG.EXE version 3.0 outputs on Windows XP with version info:

rem ! REG.EXE VERSION 3.0
rem
rem HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\App Paths\WinRAR.exe
rem     <NO NAME>   REG_SZ  Full path to WinRAR\WinRAR.exe

rem NOTE: There are 4 indent spaces and 2 separating tabs in REG 3.0 output line.

rem So either token 2 or token 3 contains value type REG_SZ
rem used to identify the line with the wanted information.

:GetPathFromRegistry
for /F "skip=1 tokens=%TypeToken%*" %%A in ('%SystemRoot%\System32\reg.exe QUERY "HKLM\Software\Microsoft\Windows\CurrentVersion\App Paths\WinRAR.exe" /ve 2^>nul') do (
    if "%%A" == "REG_SZ" (
        if exist "%%~fB" (
            set "WinRAR=%%~fB"
            goto StartExtraction
        )
    ) else if "%%A" == "NAME>" (
        set "TypeToken=3"
        goto GetPathFromRegistry
    )
)

endlocal
if not "%~1" == "" popd
echo Could not determine directory containing WinRAR.exe.
echo/
echo Please configure it manually in file: %~f0
echo/
pause
goto :EOF


rem WinRAR supports multiple archive types on extraction.
rem Specify here the archive file extensions for extraction.

rem But first delete temporary folder from a previous breaked execution.

:StartExtraction
rd /Q /S # 2>nul

for %%I in (rar zip) do call :ExtractArchives %%I

rem Restore previous command environment, restore previous current directory
rem and exit this batch file without fall through to the subroutines below.
endlocal
if not "%~1" == "" popd
goto :EOF


rem The subroutine ExtractArchives processes all archive files in current
rem directory with the file extension passed to subroutine as first argument.

rem WinRAR is called once to extract all files with specified file extension
rem for extraction into a subdirectory with name of the archive file.

rem Then one more subroutine is called for each archive file to determine
rem if it is safe to move the extracted archive file contents up one level.

:ExtractArchives
if not exist "*.%~1" goto :EOF
"%WinRAR%" x -ad -cfg- -ibck -y -- "*.%~1"
for %%A in ("*.%~1") do call :MoveUpExtracted "%%~nA" %1
goto :EOF

rem The subroutine MoveUpExtracted first checks if for the archive file
rem passed to the subroutine as first argument a subdirectory exists at
rem all, i.e. the extraction before was successful for that archive, and
rem determines short 8.3 name of this directory.

rem Next it counts the subdirectories in the archive extraction directory
rem using short directory name. Nothing is moved up if there is more than
rem 1 subdirectory in archive extraction directory.

rem Also nothing is moved up if archive extraction directory contains
rem 1 or more files.

rem After verification of archive extraction directory really containing
rem only a single subdirectory and nothing else, the current archive folder
rem is renamed to # (single character folder name) using short folder name.

rem This folder rename should work in general. The current archive folder
rem is kept in case of this folder rename fails unexpected because it is
rem not yet known if the current directory does not already contain the
rem single directory extracted from current archive or rename failed
rem because of a permission or a directory sharing access restriction.

rem Next it is checked if in current directory there is not already existing
rem a directory with name of the subdirectory from extracted archive in which
rem case it is also not possible to move the directory up one level. In this
rem special use case the archive extraction directory is kept containing just
rem a single subdirectory with restoring original directory name. In case of
rem restoring archive directory fails unexpected, the directory with name #
rem is deleted and the archive is extracted once again into a directory with
rem name of archive file.

rem It is clear on this point that the single directory in archive extraction
rem directory can be moved up to current directory from directory wit having
rem now the temporary name #.

rem Moving a directory with command MOVE is not possible if hidden attribute
rem is set on directory. For that reason it is checked next if the directory
rem to move up has hidden attribute set using its short directory name.

rem In case of directory has hidden attribute is indeed set, it is removed
rem which is also verified. The verification can't be done with errorlevel
rem evaluation as external command ATTRIB does not set errorlevel on failed
rem attribute change. So the attribute check is done once again after the
rem hidden attribute is removed with ATTRIB.

rem ATTRIB also fails to change the attribute if absolute folder path is
rem longer than 259 characters. In this case the current extraction folder
rem with temporary name # is deleted completely and the current archive is
rem extracted once again to current directory without creation of an
rem additional directory with name of archive file.

rem Then the single subdirectory in archive extraction directory having
rem now name # is also renamed to # using short directory name to avoid
rem a problem on next command MOVE with an absolute folder path longer
rem than 259 characters as much as possible.

rem The directory extracted from archive with name # in directory # is
rem moved up to current directory with suppressing all errors which could
rem occur for example if path of current directory plus name of directory
rem as extracted from archive file is too long.

rem The directory # in current directory with its subdirectory # is deleted
rem on a moving error and the current archive file is extracted once again
rem into current directory without creation of an additional directory with
rem name of archive file.

rem But on successful movement of the folder with correct name to current
rem directory the hidden attribute is set on folder if the extracted folder
rem has it also set before moving the folder and the finally empty folder #
rem is deleted before exiting subroutine.


:MoveUpExtracted
set "FolderToCheck=%~f1"
set "FolderToCheck=%FolderToCheck:~0,258%"
for /F "skip=5 tokens=4*" %%X in ('dir "%FolderToCheck%*" /AD /X 2^>nul') do (
    if "%%Y" == "%~1" set "ArchiveFolder=%%X" & goto Subfolders
    if "%%Y" == "" if /I "%%X" == "%~1" set "ArchiveFolder=%%X" & goto Subfolders
)
echo Error: No folder for archive %~1
goto :EOF

:Subfolders
@echo off
echo Processing archive folder "%~1"
set FolderCount=0
set "FolderName="
for /F "delims=" %%D in ('dir "%ArchiveFolder%\*" /AD /B 2^>nul') do (
    if defined FolderName goto :EOF
    set /A FolderCount+=1
    set "FolderName=%%D"
)
if not %FolderCount% == 1 goto :EOF

for /F "delims=" %%F in ('dir "%ArchiveFolder%\*" /A-D /B 2^>nul') do goto :EOF

ren "%ArchiveFolder%" # 2>nul
if errorlevel 1 (
    echo Error: Failed to rename "%~1"
    goto :EOF
)

set "FolderToCheck=%~dp1%FolderName%"
set "FolderToCheck=%FolderToCheck:~0,258%"
for /F "skip=5 tokens=4*" %%X in ('dir "%FolderToCheck%*" /AD /X 2^>nul') do (
    if "%%Y" == "%FolderName%" goto FolderExist
    if "%%Y" == "" if /I "%%X" == "%FolderName%" goto FolderExist
)

set "HiddenFolder=0"
set "FolderToCheck=%~dp1#\%FolderName%"
set "FolderToCheck=%FolderToCheck:~0,258%"
for /F "skip=5 tokens=4*" %%X in ('dir "%FolderToCheck%*" /AD /X 2^>nul') do (
    if "%%Y" == "%FolderName%" set "FolderToMove=%%X" & goto CheckHidden
    if "%%Y" == "" if /I "%%X" == "%FolderName%" set "FolderToMove=%%X" & goto CheckHidden
)

:CheckHidden
for %%X in ("#\%FolderToMove%") do (
    for /F "tokens=2 delims=h" %%H in ("%%~aX") do (
        if %HiddenFolder% == 1 goto ArchiveExtract
        set "HiddenFolder=1"
        %SystemRoot%\System32\attrib.exe -h "#\%FolderName%"
        goto CheckHidden
    )
)

ren "#\%FolderToMove%" # 2>nul
move #\# "%FolderName%" >nul 2>nul
if errorlevel 1 goto ArchiveExtract

if %HiddenFolder% == 1 %SystemRoot%\System32\attrib.exe +h "%FolderName%"
rd #
goto :EOF

:ArchiveExtract
rd /Q /S #
"%WinRAR%" x -cfg- -ibck -y -- "%~1.%~2"
goto :EOF

:FolderExist
echo Error: Folder exists "%FolderName%"
ren # "%~1" 2>nul
if not errorlevel 1 goto :EOF
rd /Q /S #
"%WinRAR%" x -ad -cfg- -ibck -y -- "%~1.%~2"
goto :EOF

用 C 或 C++ 或 C# 编写控制台应用程序肯定更好,因为它可以识别长路径替换上述批处理脚本中的子例程 MoveUpExtracted

在 Windows10 版本 1607(周年更新)或更高版本 Windows 版本中,可以通过组禁用 MAX_PATH 260 个字符(259 个字符加上终止空字节)的限制策略或通过添加注册表值,请参阅

要了解使用的命令及其工作原理,请打开命令提示符 window,在其中执行以下命令,并仔细阅读为每个命令显示的所有帮助页面。

  • attrib /?
  • call /?
  • dir /?
  • echo /?
  • endlocal /?
  • for /?
  • goto /?
  • if /?
  • move /?
  • pause /?
  • popd /?
  • pushd /?
  • rd /?
  • reg /?
  • reg query /?
  • rem /?
  • ren /?
  • set /?
  • setlocal /?

另请阅读 Microsoft 文章: