2016-09-23 40 views
2
@echo off 
setlocal EnableDelayedExpansion 
set /a N=0 
for /f "tokens=* delims=" %%g in ('dir !FOLDERPATH! /b') do (
setlocal DisableDelayedExpansion 
    set "item=%%g" 
endlocal 
    set /a N+=1 
REM next line loses exclamation marks. replacing %%g with %%item%% gives error: not defined $$ variable 
    call set "$$%%N%%=%%g" 
) 
set "ind=%N%" 
REM View the results 
echo !ind! 
for /f "tokens=* delims=" %%i in ('set $$') do echo %%i 
pause 
EXIT /b 

我看我的问题(stackoverflow.com/questions/3262287许多解决方案的变量; stackoverflow.com/questions/28682268; stackoverflow.com/questions/29869394; stackoverflow.com/questions/3262287),我仍然很困惑。任何帮助赞赏保留跨DisableDelayedExpansion ENDLOCAL

我有一个包含感叹号(和百分号和&符号)的mp3文件名的文件夹。上面的子程序应该用这些文件名填充一个数组= $$。它被称为2000次,每次换一个不同的文件夹。

我想使用EnableDelayedExpansion作为主setlocal(快两倍)。在我的整个批处理程序中,这只是行(设置“item = %% g”),我需要DisableDelayedExpansion。我需要知道如何有效地通过DisableDelayedExpansion endlocal边界(并进入$$集)传递这个变量(item)。另外,我想我可以填充残疾人环境中的$$设置,然后我需要跨越边界传递设置。

我在寻找速度。我可以在整个子程序中使用Disabled,但是这会使处理时间加倍(对于很少的感叹号和百分比符号)。


根据我的反应,我发现包括整个批处理脚本(简化版)可能会很好。该脚本需要28分钟才能在我的小型机器上运行我的数据库。它处理感叹号,百分号,&符号以及其他任何可以扔到它的东西。

@echo off 
chcp 1254>nul 
setlocal DisableDelayedExpansion 
set /p COUNT=Select Desired Number of Random Episodes per Album: 
for /d %%f in (H:\itunes\Podcasts\*) do (
    set buffer="%%f" 
    set /a ind = 0 
    call:Set$$Variables 
setlocal EnableDelayedExpansion 
    if !COUNT! LEQ !ind! (set DCOUNT=!COUNT!) ELSE set DCOUNT=!ind! 
    for /l %%g in (1, 1, !DCOUNT!) do (
    call:GenerateUniqueRandomNumber 
    for %%N in (!num!) do echo !$$%%N!>>"%USERPROFILE%\Desktop\RandomEpisodes.m3u8" 
    ) 
endlocal 
) 
pause 
Exit /b 

:Set$$Variables 
set /a N = 0 
for /f "tokens=* delims=" %%g in ('dir %buffer% /b') do (
    set "item=%%g" 
    set /a N+=1 
    call set "$$%%N%%=%%item%%" 
) 
set "ind=%N%" 
EXIT /b 

:GenerateUniqueRandomNumber 
:nextone 
set /a "num = (((!random! & 1) * 1073741824) + (!random! * 32768) + !random!) %% !ind! + 1" 
for %%N in (!num!) do (
    if !RN%%N!==1 (
    goto:nextone 
) 
    set "RN%%N=1" 
) 
EXIT /b 

对以下内容进行简单更改,并在13分钟后运行。但它不处理感叹号(bangs =!)。

@echo off 
chcp 1254>nul 
setlocal EnableDelayedExpansion 
set /p COUNT=Select Desired Number of Random Episodes per Album: 
for /d %%f in (H:\itunes\Podcasts\*) do (\ 
setlocal 
    set buffer="%%f" 
    set /a ind = 0 
    call:Set$$Variables 
    if !COUNT! LEQ !ind! (set DCOUNT=!COUNT!) ELSE set DCOUNT=!ind! 
    for /l %%g in (1, 1, !DCOUNT!) do (
    call:GenerateUniqueRandomNumber 
    for %%N in (!num!) do echo !$$%%N!>>"%USERPROFILE%\Desktop\RandomEpisodes.m3u8" 
    ) 
endlocal 
) 
pause 
Exit /b 

:Set$$Variables 
set /a N = 0 
for /f "tokens=* delims=" %%g in ('dir %buffer% /b') do (
    set "item=%%g" 
    set /a N+=1 
    call set "$$%%N%%=%%item%%" 
) 
set "ind=%N%" 
EXIT /b 

:GenerateUniqueRandomNumber 
:nextone 
set /a "num = (((!random! & 1) * 1073741824) + (!random! * 32768) + !random!) %% !ind! + 1" 
for %%N in (!num!) do (
    if !RN%%N!==1 (
    goto:nextone 
) 
    set "RN%%N=1" 
) 
EXIT /b 

处理包含惊叹号的极少数文件名的处理时间超过了两倍。资源 - 时间猪是子例程Set $$ Variables。 所以我希望有人能在这两次之间找到一个快乐的中间地带:13分钟比28分钟。在我看来,必须有一种方法来处理15分钟差异中的十几个受影响的文件。

+0

有没有你的文件名cotain'!'? – jeb

+0

你的setlocal disabledDelayedExpansion/endlocal块不会设置任何变量,因为它在endlocal后被销毁 – jeb

+0

我想你的代码通常是一个函数,或者你是否将所有代码按顺序从上到下写入? – jeb

回答

3

如果您真正理解什么会减慢脚本,那么最好。

下都造成不可接受的性能:在一个循环中

  • 过度调用
  • 随机选择号码,直到你得到了尚未选择
  • 重定向追加模式为每个文件一个 - 最好只重定向一次

启用和禁用延迟扩展通常不会对性能造成太大影响(除非你有一个massi真正的大环境空间)

我下面的代码使用FINDSTR技术来快速构建数组,无需延迟扩展。

然后我可以为每个文件夹启用一次延迟扩展。我从来没有跨越ENDLOCAL“屏障”

我保证每个随机操作通过建立可能的数字的列表,的4个固定宽度与领先的空间选择可用的文件传递的任何值。该列表必须适合单个变量,最大长度为〜8190字节,因此该解决方案支持每个文件夹最多2040个文件。每个随机数指定从列表中取出哪个位置,然后提取该值并减少计数。

我把整个外部循环放在一个额外的圆括号中,这样我只需要重定向一次。

我敢肯定,这个代码将比你的第二个不支持的代码快得多!等

benham1.bat

@echo off 
chcp 1254>nul 
setlocal DisableDelayedExpansion 
set /p "maxCnt=Select Desired Number of Random Episodes per Album:" 

pushd "H:\itunes\Podcasts" 
>"%USERPROFILE%\Desktop\RandomEpisodes.m3u8" (
    for /d %%F in (*) do (
    pushd "%%F" 
    set "fileCnt=0" 
    for /f "delims=: tokens=1,2" %%A in ('dir /b /a-d 2^>nul^|findstr /n "^"') do (
     set "$$%%A=%%B" 
     set "fileCnt=%%A" 
    ) 
    setlocal enableDelayedExpansion 
    set "nums=" 
    for /l %%N in (1 1 !fileCnt!) do (
     set "n= %%N" 
     set "nums=!nums!!n:~-4!" 
    ) 
    if !fileCnt! lss !maxCnt! (set "cnt=!fileCnt!") else set "cnt=!maxCnt!" 
    for /l %%N in (1 1 !cnt!) do (
     set /a "pos=(!random!%%fileCnt)*4, next=pos+4, fileCnt-=1" 
     for /f "tokens=1,2" %%A in ("!pos! !next!") do (
     for /f %%N in ("!nums:~%%A,4!") do echo !$$%%N! 
     set "nums=!nums:~0,%%A!!nums:~%%B!" 
    ) 
    ) 
    endlocal 
    popd 
) 
) 
popd 

更新和解决方案

这真的困扰着我,这个代码中表现RKO的手这么差(见他的评论)。所以我做了一些我自己的测试来弄清楚发生了什么。

我在500个文件夹中运行了代码,每个文件夹中有100个文件,并且我要求为每个文件夹输出50个文件。我修改了循环以将每个文件夹的名称打印到stderr,以便我可以监视进度。正如预期的那样,每个文件夹在开始时处理得非常快。但随着程序的进展,每个文件夹都以非线性的方式变得比前一个慢。到了最后,每个文件夹都非常缓慢。

处理500个文件夹需要大约3分钟的时间。然后我把文件夹数加倍,时间爆炸到~18分钟。非常讨厌。

遥想当年,在DosTips我们一群人试图调查CMD.EXE如何管理环境空间,以及如何大环境的冲击性能。请参阅Why does SET performance degrade as environment size grows?

我们确定ENDLOCAL并未实际释放分配的内存,这会导致SET性能随着SET操作数的累积而降低。此问题具有SET操作的批次,因此它变得缓慢是有道理的。

但是RKO的代码有很多低效率的表现比我的表现要好。在没有环境大小问题的情况下,他的代码应该是很多较慢。所以他的代码不能像我一样积累内存。所以我继续寻求隔离其他所有文件夹的内存分配。

我的第一次尝试是将一个文件夹的所有内存分配放在一个新的cmd.exe进程中。它的工作!处理500个文件夹现在花费了大约1分钟时间,并且倍增到1000个文件夹的时间基本上增加了一倍〜2分钟!

benham2.bat

@echo off 
if "%~1" equ ":processFolder" (
    pushd "%folder%" 
    set "fileCnt=0" 
    for /f "delims=: tokens=1,2" %%A in ('dir /b /a-d 2^>nul^|findstr /n "^"') do (
    set "$$%%A=%%B" 
    set "fileCnt=%%A" 
) 
    setlocal enableDelayedExpansion 
    set "nums=" 
    for /l %%N in (1 1 !fileCnt!) do (
    set "n= %%N" 
    set "nums=!nums!!n:~-4!" 
) 
    if !fileCnt! lss !maxCnt! (set "cnt=!fileCnt!") else set "cnt=!maxCnt!" 
    for /l %%N in (1 1 !cnt!) do (
    set /a "pos=(!random!%%fileCnt)*4, next=pos+4, fileCnt-=1" 
    for /f "tokens=1,2" %%A in ("!pos! !next!") do (
     for /f %%N in ("!nums:~%%A,4!") do echo !$$%%N! 
     set "nums=!nums:~0,%%A!!nums:~%%B!" 
    ) 
) 
    popd 
    exit 
) 

setlocal DisableDelayedExpansion 
set /p "maxCnt=Select Desired Number of Random Episodes per Album:" 

pushd "H:\itunes\Podcasts" 
>"%USERPROFILE%\Desktop\RandomEpisodes.m3u8" (
    for /d %%F in (*) do (
    set "folder=%%F" 
    cmd /v:off /c ^""%~f0" :processFolder^" 
) 
) 
popd 
exit /b 

但后来我意识到,我没有重新启动我的控制台的我原来的benham1每个代码测试 - 当批处理脚本终止,内存似乎有重置,因为下一次运行将以与前一次一样快的速度开始。

所以我想,为什么不简单地调用a:子程序而不是启动一个新的cmd.exe。这工作差不多,只是更好一点!

benham3.bat

@echo off 
setlocal DisableDelayedExpansion 
set /p "maxCnt=Select Desired Number of Random Episodes per Album:" 

pushd "H:\itunes\Podcasts" 
>"%USERPROFILE%\Desktop\RandomEpisodes.m3u8" (
    for /d %%F in (*) do (
    set "folder=%%F" 
    call :go 
) 
) 
popd 
exit /b 

:go 
setlocal 
cd %folder% 
set "fileCnt=0" 
for /f "delims=: tokens=1,2" %%A in ('dir /b /a-d 2^>nul^|findstr /n "^"') do (
    set "$$%%A=%%B" 
    set "fileCnt=%%A" 
) 
setlocal enableDelayedExpansion 
set "nums=" 
for /l %%N in (1 1 !fileCnt!) do (
    set "n= %%N" 
    set "nums=!nums!!n:~-4!" 
) 
if !fileCnt! lss !maxCnt! (set "cnt=!fileCnt!") else set "cnt=!maxCnt!" 
for /l %%N in (1 1 !cnt!) do (
    set /a "pos=(!random!%%fileCnt)*4, next=pos+4, fileCnt-=1" 
    for /f "tokens=1,2" %%A in ("!pos! !next!") do (
    for /f %%N in ("!nums:~%%A,4!") do echo !$$%%N! 
    set "nums=!nums:~0,%%A!!nums:~%%B!" 
) 
) 
exit /b 

另一个更新

我取代Aacini对保证每个随机操作优于基于阵列的方法来代替我的基于字符串的方法中选择一个唯一的文件名。它产生的性能稍好:

贝纳姆-aacini.bat

@echo off 
setlocal DisableDelayedExpansion 
set /p "maxCnt=Select Desired Number of Random Episodes per Album:" 

pushd "H:\itunes\Podcasts"  
>"%USERPROFILE%\Desktop\RandomEpisodes.m3u8" (
    for /d %%F in (*) do (
    set "folder=%%F" 
    call :go 
) 
) 
popd 
exit /b 

:go 
setlocal 
cd "%folder%" 
set "fileCnt=0" 
for /f "delims=: tokens=1,2" %%A in ('dir /b /a-d 2^>nul^|findstr /n "^"') do (
    set "$$%%A=%%B" 
    set /a "fileCnt=%%A, RN%%A=%%A" 
) 
setlocal enableDelayedExpansion 
if !fileCnt! lss !maxCnt! (set end=1) else set /a "end=fileCnt-maxCnt+1" 
for /l %%N in (!fileCnt! -1 !end!) do (
    set /a "ran=!random!%%%%N+1" 
    set /a "num=RN!ran!, RN!ran!=RN%%N 
    for %%N in (!num!) do echo !cd!\!$$%%N! 
) 
exit /b 

下面是各个版本的定时摘要:

folders | benham1 benham2 benham3 benham-aacini 
---------+------------------------------------------- 
    500 | 2:49  0:53  0:43  0:41 
    1000 | 17:48  1:56  1:44  1:34 

所以我们的原始思维在DosTips的环境永不缩水是错误的。但我还没有时间来完全测试和确定它何时缩小。

无论如何,我想我终于有一个版本是真正的速度比你目前的13分钟代码:-)

然而,另一个更新由一套完整的(假设少数碰撞)

多个随机选择文件可能会导致重复。我的代码假设请求文件的数量可能是大型列表的一大部分,在这种情况下,您可能会得到许多重复项。

因此,我之前的所有解决方案都做了一些额外的簿记工作,以保证每次随机操作都会产生一个独特的文件。

但是RKO似乎通常只从每个文件夹中选择几个文件。所以碰撞的机会很小。他的代码只是随机选择一个文件,然后如果之前选择了该文件,它将循环回去并再次尝试,直到找到一个新文件。但由于碰撞的机会很小,重试很少发生。这种方法的簿记显着减少。结果是只要请求的数量很小,他的随机选择算法就会更快。

所以我采用我的代码来使用RKO的选择方法的稍微修改版本。我希望这是最快的,但对于一个小的请求很重要。

benham4.bat

@echo off 
setlocal DisableDelayedExpansion 
set /p "maxCnt=Select Desired Number of Random Episodes per Album:" 

pushd "H:\itunes\Podcasts"  
>"%USERPROFILE%\Desktop\RandomEpisodes.m3u8" (
    for /d %%F in (*) do (
    set "folder=%%F" 
    call :selectRandom 
) 
) 
popd 
exit /b 

:selectRandom 
setlocal 
cd "%folder%" 
set "fileCnt=0" 
for /f "delims=: tokens=1,2" %%A in ('dir /b /a-d 2^>nul^|findstr /n "^"') do (
    set "$$%%A=%%B" 
    set "fileCnt=%%A" 
) 
setlocal enableDelayedExpansion 
if !fileCnt! lss !maxCnt! (set /a end=fileCnt) else set /a end=maxCnt 
for /l %%N in (1 1 !end!) do (
    set /a "N=!random!%%fileCnt+1" 
    if not defined $$!N! call :tryAgain 
    for %%N in (!N!) do echo !folder!\!$$%%N! 
    set "$$!N!=" 
) 
exit /b 

:tryAgain 
set /a "N=!random!%%fileCnt+1" 
if not defined $$!N! goto :tryAgain 
exit /b 
+0

28分钟。谢谢你。您分享的代码中有很多很棒的学习资料。我会喜欢破译它。但它不是更快。我的想法是使用我的13分钟版本,并提出特殊处理任何有惊叹号...在这方面的任何提示或提示? – RKO

+0

@RKO - 查看我更新的答案。我想我解决了我的性能问题,它应该比您当前使用的解决方案快得多。 – dbenham

+0

谢谢。我会检查一下,但明天之前无法回复你。我设法想出了一个解决方案(使用我的EnableDelayed版本),它暂时在10分钟内计时。需要更多的测试。 – RKO

0
... 
setlocal DisableDelayedExpansion 
endlocal&set "item=%%g" 
... 

应该工作。

+0

这并不能解决数值数组问题 – jeb

+0

@jeb我回答了所问的问题。看起来实际的问题是“如何从列表中建立一组值?” – Magoo

+1

你说得对,标题说'变量',但在代码中,他发表了2000个项目的数组。 – jeb

1

没有任何好的方法来转移许多变量超出范围(通过最终局部障碍)。

但是,当你一个接一个地转移,而不是它的工作。

@echo off 
setlocal 
set "folderPath=C:\temp" 
call :func 
set $$ 
exit /b 

:func 
setlocal EnableDelayedExpansion 
FOR /F "delims=" %%F in ("!FOLDERPATH!") DO (
    endlocal 
    setlocal DisableDelayedExpansion 
    set /a Counter=0 
    for /f "tokens=* delims=" %%g in ('dir %%F /b') do (  
     set "item=%%g" 
     setlocal EnableDelayedExpansion 
     FOR /F "tokens=1,*" %%C in ("!Counter! !item!") DO (

      endlocal 
      endlocal 
      set "$$%%C=%%D" 
      setlocal DisableDelayedExpansion 
      set /a Counter=%%C + 1 
     ) 
    ) 
) 

EXIT /b 

该解决方案假设,那不关你的文件名中含有爆炸!或调用从禁用延迟扩展方面的功能。

另一种解决方案
当你需要一个防弹变种,那么你可以更换endlocal/set "$$%%C=%%D"macroReturn technic

如果您可以住在临时文件中,也可以使用set /p技术。

:collectFiles 
dir /b !FOLDERPATH! > temp.$$$ 
FOR /F %%C in ('type temp.$$$ ^| find /v /c ""') DO (
    echo count %%C 
    < temp.$$$ (
     for /L %%n in (0 1 %%C) DO (
      set /p $$%%n= 
     ) 
    ) 
) 
exit /b 
+0

如果我不担心刘海,我的原代码效果很好(并且速度很快)。但是我愿意。我不认为我可以忍受临时文件。 macroReturn技术可能工作,但它的长度和复杂性让我怀疑它可以满足我对速度的需求(我甚至没有尝试过)。 – RKO

+0

“over the endlocal _barrier_”**':/'** – Aacini

0
@ECHO OFF 
SETLOCAL DISABLEDELAYEDEXPANSION 
FOR /f "tokens=1*delims=:" %%a IN (
'dir /b /ad c:\106x^|findstr /n /r "."') DO (
SET "thing%%a=%%b" 
) 
SET thing 
GOTO :EOF 

这应该建立你的阵列。 c:\106x只是一个我有一些奇怪的目录名称的目录。


我的目录c:\ 106X:

!dir! 
%a 
%silly% 
- 
1 & 2 
a silly dirname with & and % and ! an things 
exe 
flags 
nasm32working 
nasmxpts 
no test file(s) 
some test file(s) 
stl 
wadug 
with spaces 
with'apostrophes'test 

上面的代码运行

thing1=!dir! 
thing10=nasmxpts 
thing11=no test file(s) 
thing12=some test file(s) 
thing13=stl 
thing14=wadug 
thing15=with spaces 
thing16=with'apostrophes'test 
thing2=%a 
thing3=%silly% 
thing4=- 
thing5=1 & 2 
thing6=a silly dirname with & and % and ! an things 
thing7=exe 
thing8=flags 
thing9=nasm32working 

对我的作品的结果! - 只执行一次setlocal

+0

谢谢,但我试图避免过度延长DisableDelayedExpansion ...这会杀死处理速度。再次,我有工​​作代码,但我需要加快速度......并且我注意到,如果我可以容忍一些缺失的文件名(包含撇号的文件名),我可以将处理时间减半。因此,最初的文章非常详细地介绍了在整个子程序中不使用DisableDelayedExpansion。 – RKO

+0

当然,它的工作原理。如果您将其更改为禁用延迟扩展,那么我的原始代码也是如此。但他们都很慢。这个差异是一个因子2与启用延迟扩展(需要两倍长) – RKO

1

这种方法建立在一个非常快速的方式禁用延迟扩展环境的数组:

@echo off 
setlocal DisableDelayedExpansion 

for /f "tokens=1* delims=:" %%g in ('dir %FOLDERPATH% /b ^| findstr /N "^"') do (
    set "$$%%g=%%h" 
    set "ind=%%g" 
) 

REM View the results 
echo %ind% 
set $$ 

REM View the results using DelayedExpansion 
setlocal EnableDelayedExpansion 
for /L %%i in (1,1,%ind%) do echo %%i- !$$%%i! 

pause 
EXIT /b 

您还可以以非常简单的方式将整个阵列主调程序的环境转移:

for /F "delims=" %%a in ('set $$') do (
    endlocal 
    set "%%a" 
) 

在这种方法中,endlocal命令被执行多次,但它只是在它与函数的初始setlocal匹配时第一次运行。如果此方法可以释放以前的其他环境中,则只需添加一个简单的测试:

set _FLAG_=1 
for /F "delims=" %%a in ('set $$') do (
    if defined _FLAG_ endlocal 
    set "%%a" 
) 

编辑完整示例代码添加

我写了完整的示例代码被张贴后,下面的代码OP;我使用了原始代码中的相同命令和变量名称。此解决方案使用我最初发布的方法在禁用的延迟扩展环境(通过findstr /N "^"命令生成元素的索引)中创建数组,然后使用非常高效的方法以随机顺序提取数组的元素已在this answer中使用。我还插入了一些可提高效率的修改,例如通过标准重定向>(仅执行一次)避免call命令并更改附加重定向>>(对于每个输出行执行一次)。由此产生的程序运行速度要比原来的OP代码快得多。

@echo off 
chcp 1254>nul 
setlocal DisableDelayedExpansion 
set /p COUNT=Select Desired Number of Random Episodes per Album: 

(for /d %%f in (H:\itunes\Podcasts\*) do (
    for /f "tokens=1* delims=:" %%g in ('dir "%%f" /b /A-D 2^>NUL ^| findstr /N "^"') do (
     set "$$%%g=%%h" 
     set "ind=%%g" 
     set "RN%%g=%%g" 
    ) 
    setlocal EnableDelayedExpansion 
    if %COUNT% LEQ !ind! (set /A DCOUNT=ind-COUNT+1) ELSE set DCOUNT=1 
    for /l %%g in (!ind!, -1, !DCOUNT!) do (
     set /A "ran=(!random!*%%g)/32768+1" 
     set /A "num=RN!ran!, RN!ran!=RN%%g" 
     for %%N in (!num!) do echo !$$%%N! 
    ) 
    endlocal 
)) > "%USERPROFILE%\Desktop\RandomEpisodes.m3u8" 

pause 
Exit /b 

2ND编辑

后读取用户dbenham的有关在我们原来的方法对环境的释放问题的解释,我介绍了我的代码由他提出同样的修改,以解决问题。据预计,这两个代码现在运行比原来OP的代码快...

@echo off 
chcp 1254>nul 
setlocal DisableDelayedExpansion 
(
for /F "delims==" %%a in ('set') do set "%%a=" 
set "ComSpec=%ComSpec%" 
set "USERPROFILE=%USERPROFILE%" 
) 
set /p COUNT=Select Desired Number of Random Episodes per Album: 
(for /d %%f in (H:\itunes\Podcasts\*) do call :Sub "%%f" 
) > "%USERPROFILE%\Desktop\RandomEpisodes.m3u8" 
pause 
Exit /b 

:Sub 
setlocal DisableDelayedExpansion 
    cd %1 
    for /f "tokens=1* delims=:" %%g in ('dir /b /A-D 2^>NUL ^| findstr /N "^"') do (
     set "$%%g=%%h" 
     set /A "ind=%%g, N%%g=%%g" 
    ) 
    setlocal EnableDelayedExpansion 
    if %COUNT% LEQ %ind% (set /A DCOUNT=ind-COUNT+1) ELSE set DCOUNT=1 
    for /l %%g in (%ind%, -1, %DCOUNT%) do (
     set /A "ran=!random!%%%%g+1" 
     set /A "num=N!ran!, N!ran!=N%%g" 
     for %%N in (!num!) do echo %CD%\!$%%N! 
    ) 
exit /B 

3RD编辑

产生由dbenham使用独特的随机数的方法和我是一个通用的方法有效地管理大多数情况;然而,似乎这种方法并不适合这个特定的问题。下面的新代码是试图为这个问题编写一个解决方案,以最快的方式运行。

Mod:修正了一个小错误。

@echo off 
chcp 1254>nul 
setlocal DisableDelayedExpansion 
set "findstr=C:\Windows\System32\findstr.exe" 
for /F "delims=" %%a in ('where findstr 2^>NUL') do set "findstr=%%a" 
(
for /F "delims==" %%a in ('set') do set "%%a=" 
set "ComSpec=%ComSpec%" 
set "USERPROFILE=%USERPROFILE%" 
set "findstr=%findstr%" 
) 
set /p COUNT=Select Desired Number of Random Episodes per Album: 
(for /d %%f in (H:\itunes\Podcasts\*) do call :Sub "%%f" 
) > "%USERPROFILE%\Desktop\RandomEpisodes.m3u8" 
pause 
Exit /b 

:Sub 
setlocal DisableDelayedExpansion 
    cd %1 
    for /f "tokens=1* delims=:" %%g in ('dir /b /A-D 2^>NUL ^| "%findstr%" /N "^"') do (
     set "$%%g=%%h" 
     set "ind=%%g" 
    ) 
    setlocal EnableDelayedExpansion 
    if %COUNT% LEQ %ind% (set "DCOUNT=%COUNT%") ELSE set "DCOUNT=%ind%" 
    for /l %%g in (1, 1, %DCOUNT%) do (
     set /A "num=!random!%%%%g+1" 
     if not defined $!num! call :nextNum 
     for %%N in (!num!) do echo %CD%\!$%%N!& set "$%%N=" 
    ) 
exit /B 

:nextNum 
    set /A num=num%%ind+1 
    if not defined $%num% goto nextNum 
exit /B 
+0

比我张贴的原始代码慢(在您将其更改为disabledelayedexpansion之后)...并且我的原始代码需要使用enableddelayedexpansion两倍,并忽略撇号。所以我的想法是在残疾人的速度和启用之间存在一些东西......因此我想对使用disableddelayedexpansion进行仔细的评估。 – RKO

+0

不错的主意,但是'set'%% a“'在跳转到已启用的延迟扩展上下文 – jeb

+0

感谢代码时仍会销毁'!',但我必须在1小时20分钟后关闭它。这只是大约3/4完成。太慢了。我提到了一些可以包含1500个文件的文件夹吗?其他人可以只包含2. – RKO

1

10分钟!它是原始帖子中显示的13分钟启用延迟扩展版本的修改版本。我正在等待dbenham的一个小小的编码修复,看看他是否是更快的解决方案。

这里是编码的关键部分:

for /f "tokens=* delims=" %%g in ('dir "!buffer!" /b') do (
    setlocal DisableDelayedExpansion 
    for /f "tokens=1* delims=!" %%m in ("%%g") do if not "%%m"=="%%g" (
    set "item=%%g" 
    call set BangFile=%%item:^&=¬%% 
    call set BangFile=%%Bangfile:!=^^^^!%% 
    call echo %%BangFile%%>"G:\BangFilename.txt" 
) 
    endlocal 

是很丑陋的。我不喜欢写入临时文件,但可以找到其他方式。在整个120K集合中,可能只有几十个包含刘海(!)的文件名,因此只有很少的读写。我不得不使用上面的代码两次:一次用于目录名,一次用于文件名。

完整的代码是在这里:

@echo off 
chcp 1254>nul 
setlocal EnableDelayedExpansion 
IF EXIST "%USERPROFILE%\Desktop\RandomEpisodes.m3u8" del "%USERPROFILE%\Desktop\RandomEpisodes.m3u8" 
set /p COUNT=Select Desired Number of Random Episodes per Album: 
call:timestart 
for /d %%f in (G:\itunes\Podcasts\* H:\itunes\Podcasts\*) do (

setlocal DisableDelayedExpansion 
    if exist g:\BangDirectory.txt del g:\BangDirectory.txt 
    for /f "tokens=1* delims=!" %%d in ("%%f") do if not "%%d"=="%%f" (
    set "BangDir=%%f" 
    call set BangDir=%%BangDir:^&=¬%% 
    call set BangDir=%%BangDir:!=^^^^!%% 
    call echo %%BangDir%%>g:\BangDirectory.txt 
) 
endlocal 

setlocal 
    set "buffer=%%f" 
    set directory=%%~nf 
    call:timecalc 

    call:Set$$Variables 

    if !COUNT! LEQ !ind! (set DCOUNT=!COUNT!) ELSE set DCOUNT=!ind! 
    for /l %%g in (1, 1, !DCOUNT!) do (
    call:GenerateUniqueRandomNumber 

    for %%N in (!num!) do echo !$$%%N!>>"%USERPROFILE%\Desktop\RandomEpisodes.m3u8" 
    ) 
endlocal 
) 
pause 
Exit /b 

:Set$$Variables 
set /a cnt = 0 
    if exist g:\BangDirectory.txt for /f "usebackq delims=" %%t in ("G:\BangDirectory.txt") do (
    set "buffer=%%t" 
    set "buffer=!buffer:¬=&!" 
) 
for /f "tokens=* delims=" %%g in ('dir "!buffer!" /b') do (
    setlocal DisableDelayedExpansion 
    if exist g:\BangFilename.txt del g:\BangFilename.txt 
    for /f "tokens=1* delims=!" %%m in ("%%g") do if not "%%m"=="%%g" (
    set "item=%%g" 
    call set BangFile=%%item:^&=¬%% 
    call set BangFile=%%Bangfile:!=^^^^!%% 
    call echo %%BangFile%%>"G:\BangFilename.txt" 
) 
    endlocal 

    if exist g:\BangFilename.txt for /f "usebackq tokens=* delims=" %%p in ("G:\BangFilename.txt") do (
    set Filename=%%p 
    set "Filename=!Filename:¬=&!" 
    set $$!cnt!=!buffer!\!Filename! 
) 
    if not exist g:\BangFilename.txt set "$$!cnt!=!buffer!\%%g" 
    set /a cnt+=1 
) 
set "ind=!cnt!" 
EXIT /b 

:GenerateUniqueRandomNumber 
:nextone 
set /a "num = (((!random! & 1) * 1073741824) + (!random! * 32768) + !random!) %% !ind!" 
for %%N in (!num!) do (
    if !RN%%N!==1 (
    goto:nextone 
) 
    set "RN%%N=1" 
) 
EXIT /b 

:timestart 
for /F "tokens=1-4 delims=:.," %%a in ("%time%") do (
    set /A "start=(((%%a*60)+1%%b %% 100)*60+1%%c %% 100)*100+1%%d %% 100") 
exit /b 

:timecalc 
REM Get end time: 
for /F "tokens=1-4 delims=:.," %%a in ("%time%") do (
    set /A "end=(((%%a*60)+1%%b %% 100)*60+1%%c %% 100)*100+1%%d %% 100" 
) 
REM Get elapsed time: 
set /A elapsed=end-start 
REM Show elapsed time: 
set /A hh=elapsed/(60*60*100), rest=elapsed%%(60*60*100), mm=rest/(60*100), rest%%=60*100, ss=rest/100, cc=rest%%100 
if %mm% lss 10 set mm=0%mm% 
if %ss% lss 10 set ss=0%ss% 
set "TimeElapsed= Time elapsed (mm:ss) %mm%:%ss%" 
title %timeelapsed% WIP: "%directory%" 
exit /b 

上测试注意事项。我测试了所有版本的病毒防护功能,并且没有其他正在运行的程序。我选择了5作为随机情节的期望数量。我使用运行Win XP的Evo N410c。我尽可能地尽可能为每个版本的脚本,但我注意到13分钟运行有时可能会达到10分钟(我猜测是XP创建了&,保留了一些影响运行时的动态指标) 。

120K项目库包含在通过USB连接的外部硬盘上。该图书馆有大约300个目录,数百到数千个项目。它有另外1400个目录,介于几个和几十个项目之间。这是一个实时库,但我包含一个额外的测试目录,文件名包含!,&和%的所有组合和排列组合。

哲学笔记。有几次,我不得不用这个120K的物品库来做'东西',最后一直是避免DisableDelayedExpansion是最好的规则。我通常尽我所能用EnabledDelayedExpansion,然后照顾任何异常(刘海)作为例外。

任何建议,以减少这种解决方案的丑(但不是它的速度)是非常受欢迎的。

后记--------------------

我纳入Aacini的和dbenham的随机数例行到我10分钟版本。他们的编码看起来比我的原始编码更优雅。我删除我的GenerateUniqueRandomNumber子程序和中包括的以下:

if !ind! lss !Count! (set end=1) else set /a "end=ind-Count+1" 
    for /l %%N in (!ind! -1 !end!) do (
    set /a "ran=!random!%%%%N+1" 
    set /a "num=RN!ran!, RN!ran!=RN%%N 
    for %%N in (!num!) do echo !$$%%N!>>"%USERPROFILE%\Desktop\RandomEpisodes.m3u8" 
    ) 

这种变化加入2分钟,以处理时间(增加的运行时间从10分钟至12分钟)。有时候优雅只是不如丑陋一样快。我坚持我的原创。

+0

很好地完成。但我认为我的最新更新请求数低于5应该胜过这个。 – dbenham

+0

另外 - 使用临时文件的好主意。您可能会惊讶地发现临时文件多久会打败纯粹基于内存的批处理解决方案。 – dbenham