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

时间:2017-07-16 20:04:08

标签: powershell batch-file cmd delayedvariableexpansion

如何为FOR循环制定解决方法?问题是对于带有“!”的文件名,DelayedExpansion失败在他们中

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

: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

编辑:CALL SET修复了原始代码,完全避免了DelayedExpansion

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"
)

3 个答案:

答案 0 :(得分:2)

最简单的解决方案确实是使用call set来引入另一个解析阶段:

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

此外,我将过滤器选项/A:-D添加到dir以确保不返回任何目录。此外,我使用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

答案 1 :(得分:1)

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提取到%%x,将name提取到%%y(假设没有以[]))

开头的名称

答案 2 :(得分:0)

注意:除了之外,OP最初标记了问题,但后来删除了该标记(自恢复后)。

等效于以下批处理文件(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.
}