具有延迟变量扩展的 For 循环中"!"文本问题的解决方法



如何为 FOR 循环制定解决方法?问题是延迟扩展失败,文件名中包含"!">

代码(这是更大脚本的一部分(

:FileScan
::Sets the filenames(in alphabetical order) to variables
SETLOCAL EnableDelayedExpansion
SET "EpCount=0"
FOR /f "Tokens=* Delims=" %%x IN ('Dir /b /o:n %~dp0*.mkv* *.mp4* *.avi* *.flv* 2^>nul') DO (
SET /a "EpCount+=1"
SET Ep!EpCount!=%%x
)
IF %EpCount%==0 ( Echo. & Echo: No Episodes Found & GOTO :Err )
:FolderScan
::Sets the foldernames(in alphabetical order) to variables
SET "FolderCount=0"
FOR /f "Tokens=* Delims=" %%x IN ('Dir /b /o:n /a:d "%~dp0Put_Your_Files_Here" 2^>nul') DO (
SET /a "FolderCount+=1"
SET Folder!FolderCount!=%%x
)

如果无法批量进行,如何在PowerShell中执行此操作 可以由原始批处理调用的内容,例如:

:FileScan
Call %~dp0FileScanScript.ps1
IF %EpCount%==0 ( Echo. & Echo: No Episodes Found & GOTO :Err )
:FolderScan
Call %~dp0FolderScanScript.ps1

编辑:调用集修复了原始代码,完全避免了延迟扩展

SET "EpCount=0"
FOR /f "Tokens=* Delims=" %%x IN ('Dir /b /o:n %~dp0*.mkv* *.mp4* *.avi* *.flv* 2^>nul') DO (
SET /a "EpCount+=1"
CALL SET "Ep%%EpCount%%=%%x"
)

最简单的解决方案实际上是使用callset引入另一个解析阶段:

setlocal DisableDelayedExpansion
pushd "%~dp0."
set /A "EpCount=0"
for /F "delims=" %%x in ('
dir /B /A:-D /O:N "*.mkv*" "*.mp4*" "*.avi*" "*.flv*" 2^> nul
') do (
set /A "EpCount+=1"
call set "Ep%%EpCount%%=%%x"
)
popd
endlocal

但是,一旦字符串或文件名中出现插入符号^这会导致问题,因为当它们出现引号时call它们会加倍。

要解决此问题,您需要确保字符串在第二个解析阶段也得到扩展,这可以通过使用临时变量来实现,如下所示:

setlocal DisableDelayedExpansion
pushd "%~dp0."
set /A "EpCount=0"
for /F "delims=" %%x in ('
dir /B /A:-D /O:N "*.mkv*" "*.mp4*" "*.avi*" "*.flv*" 2^> nul
') do (
set "Episode=%%x"
set /A "EpCount+=1"
call set "Ep%%EpCount%%=%%Episode%%"
)
popd
set Ep
endlocal

此外,我在dir中添加了过滤器选项/A:-D以确保不返回任何目录。此外,我使用pushdpopd临时更改为脚本的父目录。在您编写脚本时,dir命令行搜索的文件*.mkv*在脚本的父目录中,但所有其他文件都在当前工作目录中,这可能不是您想要的。


另一种选择是切换延迟扩展,以便在禁用延迟扩展时展开for变量引用%%x,并在启用时完成对Ep数组样式变量的分配。但是,您需要实施措施以将变量值传输到endlocal障碍之外,如以下示例所示:

setlocal DisableDelayedExpansion
pushd "%~dp0."
set /A "EpCount=0"
for /F "delims=" %%x in ('
dir /B /A:-D /O:N "*.mkv*" "*.mp4*" "*.avi*" "*.flv*" 2^> nul
') do (
rem /* Delayed expansion is disabled at this point, so expanding
rem    the `for` variable reference `%%x` is safe here: */
set "Episode=%%x"
set /A "EpCount+=1"
rem // Enable delayed expansion here:
setlocal EnableDelayedExpansion
rem /* Use a `for /F` loop that iterates once only over the assignment string,
rem    which cannot be empty, so the loop always iterates; the expanded value
rem    is stored in `%%z`, which must be assigned after `endlocal` in order
rem    to have it available afterwards and not to lose exclamation marks: */
for /F "delims=" %%z in ("Ep!EpCount!=!Episode!") do (
endlocal
set "%%z"
)
)
popd
set "Ep"
endlocal
CALL SET Ep%%EpCount%%=%%x

会适当地设置变量,就像

FOR /f "tokens=1*delims=[]" %%x IN (
'Dir /b /o:n %~dp0*.mkv* *.mp4* *.avi* *.flv* 2^>nul ^|find /v /n ""'
) DO (
SET /a epcount=%%x
CALL SET ep%%x=%%y
)

(在每个名称前面加上[num]然后使用fornum提取到%%xname提取到%%y(假设没有以[]开头的名称((

注意:OP 最初除了批处理文件之外还标记了问题 powershell,但后来删除了该标记(自恢复以来(。

相当于以下批处理文件 (cmd( 循环:

SETLOCAL EnableDelayedExpansion
FOR /f "Tokens=* Delims=" %%x IN ('Dir /b /o:n %~dp0*.mkv* *.mp4* *.avi* *.flv* 2^>nul') DO (
SET /a "EpCount+=1"
SET Ep!EpCount!=%%x
)

PowerShell(PSv3+(中是:

# Collect all episode paths in array $Eps
$Eps = (Get-ChildItem -Recurse $PSScriptRoot -Include *.mkv, *.mp4, *.avi, *.flv).FullName
# Count the number of paths collected.
$EpCount = $Eps.Count

上述内容也适用于嵌入了!实例的路径。
$Eps将是完整文件名的数组


然后逐个处理匹配的文件

foreach ($ep in $Eps) { # Loop over all files (episodes)
# Work with $ep
}

使用单个数组而不是不同的$Ep1$Ep2,...变量使处理更简单、更高效。


以上是逐个处理已收集到内存中的文件名的最快方法。
一种更节省内存(尽管速度稍慢(的基于管道的方法是

Get-ChildItem -Recurse $PSScriptRoot -Include *.mkv,*.mp4,*.avi,*.flv | ForEach-Object {
# Work with $_.FullName - automatic variable $_ represents the input object at hand.
}

最新更新