英文:
how to extract zip files into a folder with batch script
问题
我有压缩文件并尝试使用powershell.exe -Command "& {Expand-Archive -Path zip_file_path -DestinationPath destination_folder_path}"
解压缩它们。对于某些zip文件,它会创建一个文件夹并将所有文件解压缩到其中,而对于某些zip文件,它会将所有文件解压缩到目标文件夹中。
例如,rough1.zip
被解压缩到rough1
,但rough2.zip
被解压缩到目标目录中。理想情况下,它应该创建rough2
并将所有内容放入其中。可能的原因是什么?不同的zip文件可能有不同的结构吗?如何解决这个问题?
英文:
i have zip files and am trying to unzip them with powershell.exe -Command "& {Expand-Archive -Path zip_file_path -DestinationPath destination_folder_path}"
. for some zip folders it is creating a folder and extracting all the files into it and for some zip folders it is extracting all the files into the destination folder.
for example, rough1.zip
is getting extracted to rough1
but rough2.zip
is getting extracted into the destination dir. ideally it should create rough2
and put everything in it. what could be the reason? does different zip files have different structures? how to solve it?
答案1
得分: -1
以下是翻译好的部分:
可以使用批处理文件,并且至少需要安装 PowerShell 5.0 来支持 cmdlet [Expand-Archive](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.archive/expand-archive)。
@echo off
setlocal EnableExtensions DisableDelayedExpansion
for %%I in (*.zip) do (
md "%%~nI" 2>nul
if exist "%%~nI\" (
%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -NoLogo -Command "Expand-Archive -LiteralPath '%%I' -DestinationPath '%%~nI' -Force"
if exist "%%~nI\%%~nI\" (
set "Other="
for /F "eol=| delims=" %%J in ('dir "%%~nI\*" /A /B 2>nul | %SystemRoot%\System32\findstr.exe /I /L /V /X "%%~nI"') do set "Other=1"
if not defined Other (
ren "%%~nI" "%%~nI_tmp"
if not errorlevel 1 (
move "%%~nI_tmp\%%~nI" .\ >nul
rd "%%~nI_tmp"
)
)
)
)
)
endlocal
批处理文件旨在处理当前目录中与通配符模式 `*.zip` 匹配的所有非隐藏文件,当然可以是任何目录。
它首先创建一个子目录,子目录的名称是去掉文件扩展名 `.zip` 的 ZIP 文件的名称,在当前目录中。
如果目录可以创建或已经存在,就会执行 PowerShell 命令来将当前 ZIP 文件提取到当前目录中的这个子目录中。不使用 `& {` 开头和 `}` 结尾,因为这仅在命令块中需要,而在 PowerShell 中执行单个 cmdlet 时不需要。
接下来检查批处理文件创建的子目录中是否有一个__目录__(末尾的反斜杠非常重要),其名称与 ZIP 文件的名称相同,没有文件扩展名,因为 ZIP 文件本身包含了这个目录,对于包含目录 `rough1` 的 `rough1.zip` 而言,这是成立的,而对于 `rough2.zip` 而言则不成立。
在这种情况下,必须确定 ZIP 文件或当前目录中与 ZIP 文件名称的__目录__以外没有其他__目录__、文件或*链接*。换句话说,必须确定发生了以下哪种情况。
1. 目标目录仅包含与 ZIP 文件名称相同的__目录__
+ __ZipFileName__
- __ZipFileName__
* __Folder__
* File.txt
2. 目标目录包含与 ZIP 文件名称相同的__目录__以及至少一个以上的其他__目录__
+ __ZipFileName__
- __AnotherFolder__
* __Folder__
* File 1.txt
- __ZipFileName__
* __Folder__
* File 2.txt
3. 目标目录包含与 ZIP 文件名称相同的__目录__以及至少一个以上的文件或*链接*
+ __ZipFileName__
- __ZipFileName__
* __Folder__
* File.txt
- 另一个文件.txt
- *或链接*
4. 目标目录包含与 ZIP 文件名称相同的__目录__以及更多的__目录__/文件/*链接*
+ __ZipFileName__
- __AnotherFolder__
* __Folder__
* File 1.txt
- __ZipFileName__
* __Folder__
* File 2.txt
- 另一个文件.txt
- *或链接*
使用 `for /F` 循环启动一个后台命令进程,其中包括 `%ComSpec% /c` 和附加的带有 `'` 的命令行作为附加参数。这第二个 `cmd.exe` 运行其内部命令 __DIR__,该命令输出所有文件系统条目,包括隐藏文件和文件夹。__FINDSTR__ 过滤与 ZIP 文件名称相同(不区分大小写)的目录名称,并将其输出到后台命令进程的 __STDOUT__,其中包括所有其他__目录__/文件/*链接* 的名称。
阅读 Microsoft 文档以了解 [使用命令重定向运算符](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-xp/bb490982(v=technet.10)) 对 `2>nul` 和 `|` 进行解释。在__FOR__命令行中,必须使用插入字符 `^` 转义重定向运算符 `>` 和 `|`,以便在 Windows 命令解释器在执行__FOR__命令之前处理此命令行时将其解释为文字字符,__FOR__ 命令会在后台启动的单独命令进程中执行嵌入的命令行。
通过处理批处理文件的 `cmd.exe` 捕获输出的名称,并且在__DIR__ 和__FINDSTR__ 执行完毕后,这些名称会被__FOR__命令逐行处理,始终忽略启动的 `cmd.exe` 关闭自身并完成__DIR__ 和__FINDSTR__ 执行后的空行。
输出的名称不应该使用正常的空格和水平制表符作为分隔符来分割为子字符串,这是默认情况下的操作,这也是使用选项 `delims=` 定义一个空分隔符列表的原因。此外,使用选项 `eof=|` 定义垂直条作为行尾字符,而不是默认的分号。__目录__/文件/*链接* 名称可以以 `;` 开头,在这种情况下,__FOR__ 默认会忽略该行,但这在此处是不希望的。但是,正如 Microsoft 在有关 [文件、路径和命名空间命名](https://learn.microsoft.com/en-us
<details>
<summary>英文:</summary>
There could be used a batch file with following code on having at least PowerShell 5.0 installed supporting the cmdlet [Expand-Archive](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.archive/expand-archive):
<!-- language: lang-none -->
@echo off
setlocal EnableExtensions DisableDelayedExpansion
for %%I in (*.zip) do (
md "%%~nI" 2>nul
if exist "%%~nI\" (
%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -NoLogo -Command "Expand-Archive -LiteralPath '%%I' -DestinationPath '%%~nI' -Force"
if exist "%%~nI\%%~nI\" (
set "Other="
for /F "eol=| delims=" %%J in ('dir "%%~nI\*" /A /B 2^>nul ^| %SystemRoot%\System32\findstr.exe /I /L /V /X "%%~nI"') do set "Other=1"
if not defined Other (
ren "%%~nI" "%%~nI_tmp"
if not errorlevel 1 (
move "%%~nI_tmp\%%~nI" .\ >nul
rd "%%~nI_tmp"
)
)
)
)
)
endlocal
The batch file is designed to process all non-hidden files matched by the wildcard pattern `*.zip` in the current directory which of course can be any directory.
It creates first a subdirectory with name of the ZIP file without file extension `.zip` in the current directory.
If the directory could be created or existed already before, there is executed PowerShell to extract the current ZIP file into this subdirectory in the current directory. `& {` at beginning and `}` at end is not used as that is necessary only for a command block and not for a single cmdlet to execute by PowerShell.
There is next checked if in the subdirectory created by the batch file is now a __directory__ (backslash at end is very important) with name of the ZIP file without file extension because of the ZIP file itself contained this directory which is the case for `rough1.zip` containing the directory `rough1` while this is not the case for `rough2.zip`.
There must be found out in that use case if the ZIP file respectively the subdirectory with name of the ZIP file in current directory contains only the __directory__ with name of the ZIP file and no other __directory__ or file or *link*. In other words there must be determined which of the following use cases occurs.
1. Destination directory contains only __directory__ with ZIP file name
+ __ZipFileName__
- __ZipFileName__
* __Folder__
* File.txt
2. Destination directory contains __directory__ with ZIP file name and at least one more __directory__
+ __ZipFileName__
- __AnotherFolder__
* __Folder__
* File 1.txt
- __ZipFileName__
* __Folder__
* File 2.txt
3. Destination directory contains __directory__ with ZIP file name and at least one more file or *link*
+ __ZipFileName__
- __ZipFileName__
* __Folder__
* File.txt
- One more file.txt
- *Or a link*
4. Destination directory contains __directory__ with ZIP file name and more __directories__/files/*links*
+ __ZipFileName__
- __AnotherFolder__
* __Folder__
* File 1.txt
- __ZipFileName__
* __Folder__
* File 2.txt
- One more file.txt
- *Or a link*
There is used a `for /F` loop to start one more command process in background with `%ComSpec% /c` and the command line inside `'` appended as additional arguments. This second `cmd.exe` runs its internal command __DIR__ which outputs all file system entries including hidden files and folders. The output list of __directory__/file/*link* names is redirected as input to __FINDSTR__ which filters out the directory name being case-insensitive equal the ZIP file name without file extension and outputs to __STDOUT__ of background command process all other __directory__/file/*link* names.
Read the Microsoft documentation about [Using command redirection operators](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-xp/bb490982(v=technet.10)) for an explanation of `2>nul` and `|`. The redirection operators `>` and `|` must be escaped with caret character `^` on __FOR__ command line to be interpreted as literal characters when Windows command interpreter processes this command line before executing command __FOR__ which executes the embedded command line with using a separate command process started in background.
The output names are captured by `cmd.exe` processing the batch file and are processed by the command __FOR__ line by line with always ignoring empty lines after started `cmd.exe` closed itself after finishing the execution of __DIR__ and __FINDSTR__.
The output names should not be split up into substrings using normal space and horizontal tabs as delimiters as done by default which is the reason for using the option `delims=` to define an empty list of delimiters. There is used additionally the option `eof=|` to define a vertical bar as end of line character instead of the default semicolon. A __directory__/file/*link* name can begin with a `;` in which case the line would be ignored by __FOR__ by default which is not wanted here. But no file system entry can contain a vertical bar as described by Microsoft on the documentation page about [Naming Files, Paths, and Namespaces](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file).
The `for /F` loop just defines the environment variable `Other` if there are file system entries in the before created subdirectory with ZIP file name __not__ having the ZIP file name. There is nothing output by __FINDSTR__ for the first use case which means `for /F` runs never the command `set "Other=1"`.
In this case the directory created by the batch file itself is renamed by appending `_tmp`. It is very unlikely that this folder rename fails because that can happen only if there is in the current directory already a file system entry with ZIP file name with `_tmp` appended like `rough1_tmp` or another program has opened one of the files which were just extracted from the ZIP file. However, there is nothing done if the folder rename really fails for whatever reason. It is better keeping ``rougth1\rough1\`` in very unlikely use case then doing something wrong.
Next the directory with name of the ZIP file created during extraction of the ZIP file is moved up to the current directory which is just a very fast update of the entry for that directory in file system. There are no file data moved on storage media.
Last the now empty directory created by the batch file with ZIP file name with appended `_tmp` is deleted from the current directory and remaining is for the first use case just the directory with name of the ZIP file as stored in the ZIP file itself.
To understand the commands used and how they work, open a [command prompt](https://www.howtogeek.com/235101/10-ways-to-open-the-command-prompt-in-windows-10/) window, execute there the following commands, and read the displayed help pages for each command, entirely and carefully.
+ `dir /?`
+ `echo /?`
+ `endlocal /?`
+ `findstr /?`
+ `for /?`
+ `if /?`
+ `md /?`
+ `move /?`
+ `powershell /?`
+ `rd /?`
+ `ren /?`
+ `set /?`
+ `setlocal /?`
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论