Как изменить порядок строк в файле, используя пакетный скрипт? - PullRequest
1 голос
/ 30 ноября 2011

A есть текстовый файл, который содержит результаты dir

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

Но я хочу повторить цикл For, идущий от последней к первой строке.

Поскольку я считаю, что это невозможно сделать с помощью команды For, как я могу создать новый файл, содержащий те же строки, но в обратном порядке?

Ответы [ 5 ]

3 голосов
/ 03 декабря 2011

Мне нравится общая стратегия обоих оригинальных решений Aacini, но, как написано, у них есть проблемы (некоторые тривиальные, некоторые существенные)

Оригинальное решение Aacini 1, использующее временный файл с SORT:

  • Повреждение строк, содержащих восклицательный знак (!)
  • Удаление начальных двоеточий (ей) (:) из каждой строки
  • создание временного файла с использованием >> не столь эффективноas>
  • Используется максимальная длина строки SORT по умолчанию, равная 4096 байтов
  • Количество строк, излишне ограниченное 1 миллионом
  • На самом деле не предоставляет запрашиваемое решение (фактический вывод файла)
  • Оставляет временный файл

Модифицированное решение 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

Aacini значительно улучшило надежность и производительность благодаря модифицированному решению 1 с использованием SET / P и нескольких файлов TEMP.Решение SET / P устраняет необходимость в циклическом переключении SETLOCAL / ENDLOCAL, но имеет некоторые ограничения.

  • Строки должны заканчиваться <LF><CR> (нормально для Windows, но стиль Unixиногда встречается в мире Windows).
  • Строки должны быть <= 1024 символа </li>
  • Контрольные символы в конце строки будут удалены.

Модифицированное решение1 дубль 2

Если какое-либо из перечисленных выше ограничений является проблемой, вот адаптация моего 1-го решения, которое использует несколько временных файлов.Как и модифицированное решение Aacinis, оно работает линейно с размером файла.Это примерно на 40% медленнее, чем модифицированная версия Aacinis.

@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, в зависимости от номера строки
  • Максимальный размер файла немного меньше 64 МБ.Это было моим любимым решением, поскольку вывод файла, вероятно, может быть устранен путем непосредственной обработки файла в переменных, что полностью исключает создание любого временного файла. Редактировать Но, основываясь на информации, предоставленной 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%
    
3 голосов
/ 30 ноября 2011

Вы не можете использовать команду For. Но вы можете изменить порядок перечисления dir, создавшего текстовый файл, используя dir "%local%" /o-n /b /a:d /s >> FolderList.txt; - означает «перевернутый».

2 голосов
/ 01 декабря 2011

Существует два относительно простых способа сортировки файлов в обратном порядке.Первый - это прямой метод над содержимым файла: добавьте номера строк во все строки, отсортируйте файл в обратном порядке, исключите номера строк:

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

Другой метод заключается в загрузке строк файла в пакете.массив, который может быть обработан любым способом:

@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 МБ, потому что это ограничение для пакетных переменных.

Оба метода могут быть изменены для правильной обработки специальных символов (> <|). </p>

HOWEVER

Если вы хотите удалить все содержимое дерева папки в порядке снизу вверх, «правильный» способ сделать это с помощью рекурсивной подпрограммы ...

РЕДАКТИРОВАТЬ Ответ на dbenham

Как я уже писал в своем ответе, два предложенных мною метода могут быть изменены для правильной обработки специальных символов и пустых строк.В своем ответе я показал общий метод для «изменения порядка строк» ​​в обратном порядке, не обращая особого внимания на создание выходного файла , потому что ОП сказал в своем собственном ответе, что «Цель состояла в том, чтобы переупорядочить список папок, чтобы избежать проблем при их последовательном удалении », поэтому я подумал, что этого достаточно, чтобы показать ему, как обрабатывать папок в обратном порядке.Я также предположил, что список папок:

  • Не иметь восклицательных знаков (!).
  • Не иметь начальных двоеточий (:).
  • Имена папок корочечем 4096 байт.
  • Иметь менее 1000000 строк.
  • Не иметь пустых строк.

Я даже думал (и до сих пор думаю), что метод нужен ОПиспользовать для удаления списка папок недостаточно, и я упомянул об этом под большим HOWEVER в своем ответе, предлагающем вместо этого использовать рекурсивную подпрограмму.

Однако кажется, что dbenham подумал, что первоначальный вопрос был чем-то похожим на «Какой самый эффективный метод сортировки большого файла в обратном порядке?»и критиковать мои методы, потому что у них нет таких функций.По этой причине я должен ответить в терминах этого нового вопроса (эффективный метод), верно?

Во-первых, мне смешно, что dbenham критикует мои методы, потому что "на самом деле не предоставляет запрашиваемое решение(фактический вывод файла) ", но в своем собственном Модифицированном решении 2 он написал, что" Это мое любимое решение, потому что вывод файла, вероятно, может быть исключен путем непосредственной обработки файла в переменных, что полностью исключает создание любого временного файла.».???

Два метода, предложенных dbenham, имеют серьезную проблему с точки зрения эффективности, которая уже обсуждалась в этом вопросе : выполняется пара команд setlocal EnableDelayedExpansion и endlocal с каждой строкой файла .Если файл большой (т.е. 200 000 строк и около 8 МБ, как в предыдущем вопросе), среда будет скопирована в новую область памяти, а затем удалена, и это будет повторяться 200 000 раз!Конечно, эта задача отнимает много времени.Эта проблема усугубляется в Модифицированном решении 2 компании dbenham: по мере обработки строк среда растет, так как в этот момент хранится содержимое файла.В последних строках файла среда, почти равная размеру всего файла, будет скопирована в новую область памяти для каждой оставшейся строки файла .Конечно, это наихудший способ достижения этого процесса с точки зрения эффективности!

Существует еще один способ обработки пустых строк и специальных символов, для которых не требуется пара setlocal EnableDelayedExpansion - endlocal.Подробнее об этом методе и дальнейшем обсуждении эффективных способов обработки больших файлов см. Ранее упомянутый вопрос.

Следующие пакетные файлы являются моими модифицированными версиями в разделе «Как отсортировать большой файл в обратном порядке в эффективномпуть».

Модифицированное решение 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

Это решение по-прежнему имеет ограничение «только» 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

РЕДАКТИРОВАТЬ A возможно решение для большой проблемы среды.

Я разработал идею, которая может решить, по крайней мере частично, проблемы с производительностью команды SET, вызванные очень большой средой.Предположим, что внутренняя операция команды SET VAR=VALUE выполняется следующим образом:

  • Когда определяется новая переменная со значением, превышающим текущий размер среды, среда копируется в новую область, еслиобласть за ее пределами недоступна.
  • Новая область достаточно велика, чтобы получить новую переменную.Дополнительное пространство не зарезервировано.
  • Важное значение : при удалении большой переменной оставшееся свободное пространство NOT освобождается.Блок памяти среды никогда не сокращается.

Если предыдущие шаги верны, то проблемы с производительностью могут уменьшиться, если мы сначала зарезервируем желаемое пространство среды через большие (8 КБ) переменные с тем же именемрабочие переменные.Например, чтобы зарезервировать 1024 КБ, мы определяем 128 больших переменных;Я полагаю, что время, необходимое для определения этих 128 переменных, будет меньше, чем время, необходимое для заполнения тех же 1024 КБ более короткими переменными.

Когда процесс запущен, определение первых 128 рабочих переменных займетВремя, необходимое для удаления переменной 8 КБ и определения более короткой, но для переменной 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, чтобы зарезервировать определенный размер в байтах для среды.

У меня нет времени проверять этот метод до конца дня, но здесьэто для вас!

0 голосов
/ 01 декабря 2011

Этот код перевернет текстовый файл, но с некоторыми ограничениями. Пустые строки опущены, а строки, содержащие специальные символы, приводят к сбою: & <> |

@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
0 голосов
/ 30 ноября 2011

Цель состояла в том, чтобы переупорядочить список папок, чтобы избежать проблем при их последовательном удалении.

Я придумал следующий алгоритм.Я принимаю предложения, чтобы сделать его более эффективным или лучше.

@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%"
)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...