如何使用批处理脚本更改文件中的行顺序?

时间:2011-11-30 16:59:41

标签: batch-file for-loop

A有一个包含目录结果的文本文件

dir "%local%" /b /a:d /s >> FolderList.txt

但是我希望迭代从最后一行到第一行的For循环。

由于我认为无法在For命令中完成此操作,如何生成包含相同行但按逆序排列的新文件?

5 个答案:

答案 0 :(得分:3)

您无法使用For命令。但您可以使用dir撤消创建文本文件的dir "%local%" /o-n /b /a:d /s >> FolderList.txt商家信息的顺序; -表示“逆转”。

答案 1 :(得分:3)

我喜欢Aacini原始解决方案的一般策略,但正如所写,他们有问题(有些微不足道,有些重要)

使用带有SORT的临时文件的原始Aacini解决方案1:

  • 破坏包含感叹号的行(!)
  • 从每行剥离前导冒号(:)
  • 使用>>创建临时文件效率不如>
  • 使用默认SORT最大行长度4096字节
  • 行数不必要地限制在100万
  • 实际上并未提供要求解决方案(实际文件输出)
  • 留下临时文件

修改后的解决方案1 ​​

这是一个修复问题的版本。唯一的实际限制是最大行长度为8180字节(字符)。我不确定FINDSTR的数量有多高,但这个解决方案可以处理多达999亿行。 (我同意Aacini,没有人愿意等待这么大的文件来完成批量解决方案)可以很容易地调整行限制。

@echo off
setlocal DisableDelayedExpansion
set file="%~1"
set revfile="%~1.rev"
set tempfile="%temp%\revfile%random%.txt"
(
  for /f "delims=" %%a in ('findstr /n "^" %file%') do (
    set "ln=%%a"
    setlocal EnableDelayedExpansion
    for /f "delims=:" %%n in ("!ln!") do set "prefix=000000000000%%n"
    echo !prefix:~-12!!ln:*:=!
    endlocal
  )
)>%tempfile%
(
  for /f "delims=" %%a in ('sort /rec 8192 /r %tempfile%') do (
    set "ln=%%a"
    setlocal EnableDelayedExpansion
    echo(!ln:~12!
    endlocal
  )
)>%revfile%
del %tempfile%

Aacini修改后的解决方案1 ​​

使用SET / P和多个TEMP文件,Aacini通过改进的解决方案1显着提高了稳健性和性能。 SET / P解决方案不需要循环SETLOCAL / ENDLOCAL切换,但它确实有一些限制。

  • 行必须由<LF><CR>终止(Windows正常,但在Windows世界中有时会遇到Unix风格)。
  • 行必须<= 1024个字符
  • 将删除行尾的控制字符。

修改后的解决方案1取2

如果上述任何限制都存在问题,这里是我的第一个使用多个临时文件的解决方案的改编版。与Aacinis修改后的解决方案一样,它与文件大小呈线性关系。它比Aacinis修改版慢约40%。

@echo off
setlocal DisableDelayedExpansion
set file="%~1"
set revfile="%~1.rev"
set "tempfile=%temp%\revfile%random%.txt"
findstr /n "^" %file% >"%tempfile%.1"
(
  for /f "usebackq delims=" %%a in ("%tempfile%.1") do (
    set "ln=%%a"
    setlocal EnableDelayedExpansion
    for /f "delims=:" %%n in ("!ln!") do set "prefix=000000000000%%n"
    echo !prefix:~-12!!ln:*:=!
    endlocal
  )
)>"%tempfile%.2"
sort /rec 8192 /r "%tempfile%.2" >"%tempfile%.3"
(
  for /f "usebackq delims=" %%a in ("%tempfile%.3") do (
    set "ln=%%a"
    setlocal EnableDelayedExpansion
    echo(!ln:~12!
    endlocal
  )
)>%revfile%
del "%tempfile%*"

原始Aacini解决方案2使用环境变量:

  • 破坏包含感叹号的行(!)
  • 剥去空行
  • 实际上并未提供要求解决方案(实际文件输出)

修改后的解决方案2

这是一个修复问题的版本。唯一已知的限制是

  • 最大行长度介于8181和8190之间,具体取决于行号
  • 最大文件大小略低于64MB。

我最喜欢的解决方案,因为可以通过直接处理变量中的文件来消除文件输出,从而完全避免创建任何临时文件。 编辑 但是根据Aacini提供的信息,我了解到随着环境的发展,它会出现严重的性能问题。这个问题比Aacini意识到的还要糟糕 - 即使是简单的SET命令也会因环境大小而受到严重影响。 我在DosTips发布了一个关于此现象的问题。 http://www.dostips.com/forum/viewtopic.php?f=3&t=2597 (我最初是在SO上发布的,但显然这个问题对于这个网站来说太开放了)

@echo off
setlocal disableDelayedExpansion
set file="%~1"
set revfile="%~1.rev"
set num=0
for /f "delims=" %%a in ('findstr /n "^" %file%') do (
  set /a "num+=1"
  set "ln=%%a"
  setlocal enableDelayedExpansion
  for %%n in (!num!) do for /f "delims=" %%b in (""!ln:*:^=!"") do endlocal&set "ln%%n=%%~b"'
)
setlocal enableDelayedExpansion
(
  for /l %%n in (!num! -1 1) do echo(!ln%%n!
)>%revfile%

答案 2 :(得分:2)

有两种相对简单的方法可以按相反的顺序对文件进行排序。第一个是文件内容的直接方法:向所有行添加行号,按相反顺序对文件进行排序,消除行号:

@echo off
setlocal EnableDelayedExpansion
rem Insert line numbers in all lines
for /F "tokens=1* delims=:" %%a in ('findstr /n ^^ %1') do (
    set /A lineNo=1000000+%%a
    echo !lineNo!:%%b>> tempfile.txt
)
rem Sort the file and show the result
for /F "tokens=1* delims=:" %%a in ('sort /r tempfile.txt') do (
    echo Line %%a is %%b
)

另一种方法是在Batch数组中加载文件行,可以按照您希望的任何方式处理:

@echo off
setlocal EnableDelayedExpansion
rem Load file lines in a Batch array
set lineNo=0
for /F "delims=" %%a in (%1) do (
    set /A lineNo+=1
    set "line[!lineNo!]=%%a"
)
rem Process array elements in reversed order:
for /L %%i in (%lineNo%,-1,1) do (
    echo Line %%i is !line[%%i]!
)

此最后一种方法仅在文件大小低于64 MB时才有效,因为这是批处理变量的限制。

可以修改这两种方法以正确处理特殊字符(&gt;&lt; |)。

<强>无论其

如果要以自下而上的顺序删除所有文件夹的树内容,&#34;右键&#34;这样做的方法是通过递归子程序......

编辑 回答dbenham

正如我在回答中写的那样,我提出的两种方法可以修改为正确处理特殊字符和空白行。在我的回答中,我展示了一般方法来改变行的顺序&#34;反向顺序不要特别注意创建一个输出文件,因为OP在他自己的答案中表示&#34;目标是重新排序文件夹列表以防止问题,同时按顺序删除它们# 34;,所以我认为这足以向他展示如何以相反的顺序处理文件夹。我还假设文件夹列表:

  • 没有感叹号(!)。
  • 没有领先冒号(:)。
  • 文件夹名称短于4096字节。
  • 少于1000000行。
  • 没有空行。

我甚至认为(并且仍然认为)OP想要用来删除文件夹列表的方法是不够的,我在答案中提到了一个很大的 HOWEVER 这一点。改为使用递归子程序。

然而似乎dbenham认为原始问题类似于&#34;什么是以相反顺序对大文件进行排序的最有效方法?&#34;并批评我的方法,因为他们缺乏这些功能。出于这个原因,我应该回答这个新问题(有效的方法),对吗?

首先,dbenham批评我的方法对我来说很有趣,因为&#34;实际上并没有提供解决方案(实际文件输出)&#34;,但在他的自己修改了解决方案2他写道&#34;这是我最喜欢的解决方案,因为可以通过直接处理变量中的文件来消除文件输出,从而完全避免创建任何临时文件&#34;。 ???

dbenham提出的两种方法在效率方面存在严重问题,已在this question中讨论过:setlocal EnableDelayedExpansionendlocal命令对每个执行文件的行。如果文件很大(即200 000行和大约8 MB,如前面提到的问题),环境将被复制到新的存储区域然后删除,这将重复200000次!当然,这项任务非常耗时。这个问题在dbenham的修改解决方案2中变得更糟:随着行的处理继续,环境随着它在那时存储文件内容而增长。在文件的最后几行,几乎等于整个文件大小的环境将被复制到文件的每个剩余行的新内存区域。当然,这是在效率方面实现这一过程的最糟糕方式!

还有另一种处理空行和不需要setlocal EnableDelayedExpansion - endlocal对的特殊字符的方法。有关此方法的详细信息以及有关处理大型文件的有效方法的进一步讨论,请参阅前面提到的问题。

以下批处理文件是我修改过的版本&#34;如何以有效的方式按相反顺序对大文件进行排序&#34;。

修改后的解决方案1:使用带有SORT的临时文件

@echo off
setlocal EnableDelayedExpansion
set revfile="%~1.rev"
set tempfile=%temp%\revfile%random%

rem Insert line numbers in all lines
findstr /n ^^ %1 > "%tempfile%1.txt"
find /c ":" < "%tempfile%1.txt" > "%tempfile%2.txt"
set /P lines=< "%tempfile%2.txt"
call :JustifyLineNumbers < "%tempfile%1.txt" > "%tempfile%2.txt"
del "%tempfile%1.txt"

rem Sort the file in reversed order
sort /rec 8192 /r "%tempfile%2.txt" /o "%tempfile%3.txt"
del "%tempfile%2.txt"

rem Remove line numbers
call :RemoveLineNumbers < "%tempfile%3.txt" > %revfile%
del "%tempfile%3.txt"
goto :EOF

:JustifyLineNumbers
for /L %%i in (1,1,%lines%) do (
    set /A lineNo=1000000000+%%i
    set /P line=
    echo !lineNo!!line:*:=!
)
exit /B

:RemoveLineNumbers
for /L %%i in (1,1,%lines%) do (
    set /P line=
    echo !line:~10!
)
exit /B

这个解决方案的限制仍然只有&#34; 1147483647行(最大32位有符号正整数减去初始种子)。虽然这个限制可以通过dbenham建议的方式轻松增加,但这种修改意味着执行速度较慢。结论是:如果真的想要反向排序非常大文件,请不要使用批处理文件,而是使用更高效的编程语言(如C)

修改后的解决方案2:使用批量变量数组

@echo off
setlocal EnableDelayedExpansion
set revfile="%~1.rev"
set tempfile=%temp%\revfile%random%

rem Load file lines in a Batch array
findstr /n ^^ %1 > "%tempfile%1.txt"
find /c ":" < "%tempfile%1.txt" > "%tempfile%2.txt"
set /P lines=< "%tempfile%2.txt"
del "%tempfile%2.txt"
call :CreateArray < "%tempfile%1.txt"
del "%tempfile%1.txt"

rem Process array elements in reversed order:
(for /L %%i in (%lines%,-1,1) do echo=!ln%%i!) > %revfile%
goto :EOF

:CreateArray
for /L %%i in (1,1,%lines%) do (
    set /P line=
    set ln%%i=!line:*:=!
)
exit /B

编辑 可能解决大型环境问题的解决方案。

我设计了一个可以至少部分解决由非常大的环境引起的SET命令的性能问题的想法。让我们假设SET VAR=VALUE命令的内部操作遵循以下步骤:

  • 如果使用超过当前环境大小的值定义新变量,则如果环境之外的区域不可用,则会将环境复制到新区域。
  • 新区域足够大,可以接收新变量。没有预留额外的空间。
  • 重要的:删除大变量时,剩余的可用空间为 NOT 。环境内存块永远不会缩小。

如果前面的步骤为真,那么如果我们首先通过具有相同工作变量名称的大(8 KB)变量保留所需的环境空间,则性能问题可能会降低。例如,要保留1024 KB,我们定义128个大变量;我想定义这128个变量所需的时间将少于用较短的变量填充相同的1024 KB所需的时间。

当进程运行时,前128个工作变量的定义将占用删除8 KB变量并定义较短变量所需的时间,但是对于过程中的变量129必须更快,因为它只是定义了已有空间中的新变量。为了帮助完成这个过程,变量必须有名称,如dbenham所示,将它们放在环境的末尾。

:ReserveEnvSpace sizeInKB
rem Define the first large variable (reserving 6 bytes for variable name)
rem (this method may be done in larger chunks until achieve the fastest one)
set z1=X
for /L %%i in (1,1,8184) do set z1=!z1!X
rem Define the rest of large variables
set /A lastVar=%1 / 8
for /L %%i in (2,1,%lastVar%) do set z%%i=!z1!
exit /B

您可以使用MEM /P命令检查环境内存块的大小和位置。在旧的MS-DOS(command.com)时代,环境被放置在command.com之后,但如果驻留程序放在环境之后,那么它就不能再增长了。因此,在command.com中提供了/ E:nnnnn开关,以便为环境保留一定的字节大小。

我没有时间在今天剩下的时间里检查这种方法,但这里适合你!

答案 3 :(得分:0)

目标是重新排序文件夹列表,以防止在按顺序删除它们时出现问题。

我提出了以下算法。我接受建议,使其更有效率或更好。

@ECHO off
setLocal EnableDelayedExpansion

:: File that contains a list of folders
set file_from=%~1

:: Destination file, that will contain the sorted list
if "%2"=="" (
    set replace=1
    set file_to=_%file_from%
) else (
    set file_to=%~2
)
:: Create empty destination file
if exist "%file_to%" del "%file_to%"
copy NUL "%file_to%"

:: Temporary file
if exist ".\~Remaining.txt" del ".\~Remaining.txt"
copy "%file_from%" .\~Remaining.txt

:: Sort the order of folders

:while
set untouched=1
For /f "tokens=* delims=" %%a in (.\~Remaining.txt) Do (
    :: check if line was already added
    FindSTR /X /C:%%a "%file_to%"
    if errorlevel 1 (
        set untouched=0
        :: check if folder contains sub-folders to be added
        FindSTR /B /C:%%a\ .\~Remaining.txt
        if errorlevel 1 (
            :: remove current line from "~Remaining.txt"
            FindSTR /V /B /E /C:%%a .\~Remaining.txt> .\~Remaining_new.txt
            move .\~Remaining_new.txt .\~Remaining.txt
            :: add current line to destination file
            >> "%file_to%" ECHO %%a
            goto while
        )
    )
)
if untouched LSS 1 (
    goto while
)

if exist .\~Remaining.txt del .\~Remaining.txt

if defined replace (
    ECHO REPLACE!
    :: destination was not provided, so replace
    if exist "%file_from%" del "%file_from%"
    move "%file_to%" "%file_from%"
)

答案 4 :(得分:0)

此代码将反转文本文件,但有一些限制。省略空行,包含特殊字符的行使其失败:&amp; &LT; &GT; |

@Echo Off
If "%1"=="" Goto Syntax
If "%2"=="" Goto Syntax
If Not Exist %1 (
    Echo File not found: %1
    Exit /B 2
)
SetLocal EnableDelayedExpansion
Set SOF=~StartOfFile~
Set InFile=%~snx1~in
Set OutFile=%2
Set TempFile=%~snx1~temp
If Exist %OutFile%  Del %OutFile%
If Exist %TempFile% Del %TempFile%
Copy %1 %InFile% >nul
:Loop
Set "Line=%SOF%"
For /F "tokens=*" %%a In (%InFile%) Do (
    If Not "!Line!"=="%SOF%" Echo !Line!>>%TempFile%
    Set "Line=%%a"
)
Echo %Line%>>%OutFile%
Del %InFile%
If Not Exist %TempFile% (
    EndLocal
    Exit /B 0
)
Rename %TempFile% %InFile%
Goto Loop

:Syntax
Echo Usage:
Echo %~n0 input-file output-file
Echo.
Exit /B 1