Как отсортировать строки текстового файла с номерами версий в формате major.minor.build.revision numeric?
У меня есть текстовый файл с такими значениями:
3.6.4.2
3.6.5.1
3.6.5.10
3.6.5.11
3.6.5.12
3.6.5.13
3.6.5.2
3.6.7.1
3.6.7.10
3.6.7.11
3.6.7.2
3.6.7.3
Мне нужно написать пакетный скрипт и вернуть отсортированный вывод. Проблема в последнем столбце, цифры.10 и.11 должны идти после.3 и так. Мне нужно, чтобы "последняя версия" была внизу, а в данном случае это 3.6.7.11.
В Linux я использовал "sort -t"." -K1n,1 -k2n,2 -k3n,3 -k4n,4", но я не могу заставить его работать с пакетным скриптом.
Также по некоторым причинам мне не разрешается использовать Cygwin или PowerShell.
В моем пакетном коде я пока пробую только разные версии этого, но у меня ничего не работает:
sort /+n versions.txt
Вывод, используемый в этом вопросе, просто
sort versions.txt
Похоже, что команда сортировки делает это правильно, пока у меня не будет двухзначного номера.
5 ответов
Это распространенная проблема в пакетных файлах. Все методы сортировки используют сравнение строк, где "10" предшествует "2", поэтому необходимо вставить левые нули в числах, меньших 10. Для этого ниже приведен пакетный файл, но вместо того, чтобы генерировать новый файл с фиксированным числа, он использует их для создания массива, который будет автоматически отсортирован. После этого элементы массива отображаются в естественном (отсортированном) порядке.
РЕДАКТИРОВАТЬ: я изменил код для управления двухзначными числами в четырех частях.
@echo off
setlocal EnableDelayedExpansion
for /F "tokens=1-4 delims=." %%a in (input.txt) do (
rem Patch the four numbers as a two digits ones
set /A "a=100+%%a, b=100+%%b, c=100+%%c, d=100+%%d"
rem Store line in the proper array element
set "line[!a:~1!!b:~1!!c:~1!!d:~1!]=%%a.%%b.%%c.%%d"
)
rem Show array elements
for /F "tokens=2 delims==" %%a in ('set line[') do echo %%a
Выход:
3.6.4.2
3.6.5.1
3.6.5.2
3.6.5.10
3.6.5.11
3.6.5.12
3.6.5.13
3.6.7.1
3.6.7.2
3.6.7.3
3.6.7.10
3.6.7.11
В чистом пакетном сценарии вы можете использовать следующий фрагмент кода:
@echo off
setlocal EnableExtensions EnableDelayedExpansion
> "versions.tmp" (
for /F "usebackq tokens=1,2,3,4 delims=." %%I in ("versions.txt") do (
set "ITEM1=000%%I" & set "ITEM2=000%%J" & set "ITEM3=000%%K" & set "ITEM4=000%%L"
echo !ITEM1:~-4!.!ITEM2:~-4!.!ITEM3:~-4!.!ITEM4:~-4!^|%%I.%%J.%%K.%%L
)
)
< "versions.tmp" (
for /F "tokens=2 delims=|" %%S in ('sort') do (
echo %%S
)
)
del /Q "versions.tmp"
endlocal
exit /B
Это создает временный файл, который содержит исходную строку с префиксом с добавленными номерами версий и разделителем |
, Добавленные числа означают, что каждый компонент дополняется начальными нулями до четырех цифр. Вот пример, основанный на данных примера:
0003.0006.0004.0002|3.6.4.2 0003.0006.0005.0001|3.6.5.1 0003.0006.0005.0010|3.6.5.10 0003.0006.0005.0011|3.6.5.11 0003.0006.0005.0012|3.6.5.12 0003.0006.0005.0013|3.6.5.13 0003.0006.0005.0002|3.6.5.2 0003.0006.0007.0001|3.6.7.1 0003.0006.0007.0010|3.6.7.10 0003.0006.0007.0011|3.6.7.11 0003.0006.0007.0002|3.6.7.2 0003.0006.0007.0003|3.6.7.3
Этот временный файл затем передается sort
который делает чисто алфавитную сортировку. Поскольку числа дополняются, порядок сортировки равен истинному буквенно-цифровому порядку. Вот результат сортировки с использованием приведенного выше примера:
0003.0006.0004.0002|3.6.4.2 0003.0006.0005.0001|3.6.5.1 0003.0006.0005.0002|3.6.5.2 0003.0006.0005.0010|3.6.5.10 0003.0006.0005.0011|3.6.5.11 0003.0006.0005.0012|3.6.5.12 0003.0006.0005.0013|3.6.5.13 0003.0006.0007.0001|3.6.7.1 0003.0006.0007.0002|3.6.7.2 0003.0006.0007.0003|3.6.7.3 0003.0006.0007.0010|3.6.7.10 0003.0006.0007.0011|3.6.7.11
Наконец, если вы хотите вернуть только номер последней версии, echo %%S
от set "LVER=%%S"
и место echo !LVER!
после закрытия )
второго for /F
петля.
Обновить:
Вот решение, которое не создает никаких временных файлов, но использует канал |
вместо. Так как труба создает новый cmd
экземпляры как для левой, так и для правой сторон, а также из-за того, что (консольные) выходные данные построены в крошечных битах и что выполняется несколько арифметических операций, это довольно медленно:
@echo off
setlocal EnableExtensions DisableDelayedExpansion
(
for /F "usebackq tokens=1,2,3,4 delims=." %%I in ("versions.txt") do @(
set /A "10000+%%I" & echo( ^| set /P "=."
set /A "10000+%%J" & echo( ^| set /P "=."
set /A "10000+%%K" & echo( ^| set /P "=."
set /A "10000+%%L" & echo(
)
) | (
for /F "tokens=1,2,3,4 delims=." %%S in ('sort') do @(
set /A "%%S-10000" & echo( ^| set /P "=."
set /A "%%T-10000" & echo( ^| set /P "=."
set /A "%%U-10000" & echo( ^| set /P "=."
set /A "%%V-10000" & echo(
)
)
endlocal
exit /B
Левая сторона трубы:Вместо синтаксиса раскрытия подстроки, как в приведенном выше подходе с использованием временного файла, я добавляю 10000
для каждого компонента номеров версий (аналогично ответу Aacini), чтобы избежать отложенного расширения, потому что это не включено ни в одном новом cmd
пример. Для вывода полученных значений я использую тот факт, что любой из for /F
петли бегут в cmd
контекст, а не в batch
контекст, где set /A
выводит результат в STDOUT
, set /A
не завершает свой вывод с переносом строки, поэтому я использую set /P
добавить .
после каждого, кроме последнего элемента, который, в свою очередь, не добавляет разрыв строки. Для последнего пункта я добавляю разрыв строки, используя пробел echo
,
Сортировка снова выполняется sort
команда, чей вывод анализируется for /F
, Здесь ранее добавленная стоимость 10000
вычитается из каждого компонента для получения оригинальных номеров. Для вывода результата на консоль используется та же техника, что и для другой стороны трубы.
Данные, переданные по каналу, выглядят следующим образом (снова обращаясь к примеру вопроса):
10003.10006.10004.10002 10003.10006.10005.10001 10003.10006.10005.10010 10003.10006.10005.10011 10003.10006.10005.10012 10003.10006.10005.10013 10003.10006.10005.10002 10003.10006.10007.10001 10003.10006.10007.10010 10003.10006.10007.10011 10003.10006.10007.10002 10003.10006.10007.10003
На основе вашего примера это будет работать. Если вам нужно каким-то образом получить примеры вроде 3.6.5.02 и 3.6.5.2, то этот код не будет работать.
@echo off
setlocal EnableDelayedExpansion
for /F "tokens=1-4 delims=. " %%G in (FILE.TXT) do (
set N=0%%J
set SORT[%%G%%H%%I!N:~-2!]=%%G.%%H.%%I.%%J
)
for /F "tokens=2 delims==" %%N in ('set SORT[') do echo %%N
pause
Самым простым решением было бы вызвать PowerShell и считать номера версий актуальными. System.Version
объекты. Таким образом, сегменты Major, Minor, Build и Revision будут рассматриваться как целые числа и сортироваться соответствующим образом. Вы можете вызвать это из пакетного скрипта:
powershell "(gc textfile.txt | %%{[version]$_} | sort) -split ' '"
Вот и все. Легкий однострочный. Если вы делаете это из командной строки cmd, замените двойной %%
с одним %
, Вот разбивка команды:
- Получите следующее как строку:
- Получить содержимое textfile.txt
- Для каждой строки приведите данные как
System.Version
объект. - Сортировать как версии
- Строка будет одной строкой, разделенной пробелами. Сплит на пространствах.
Вывод следующий:
3.6.4.2
3.6.5.1
3.6.5.2
3.6.5.10
3.6.5.11
3.6.5.12
3.6.5.13
3.6.7.1
3.6.7.2
3.6.7.3
3.6.7.10
3.6.7.11
Частичный кредит должен перейти на этот вопрос и принятый ответ.
Вот мое решение, работающее с 2 временными файлами, которое также работает, если один из трех других номеров версий становится больше 9.
@echo off
setlocal EnableExtensions EnableDelayedExpansion
set "VersionsFile=versions.txt"
rem Delete all temporary files perhaps existing from a previous
rem run if user of batch file has broken last batch processing.
if exist "%TEMP%\%~n0_?.tmp" del "%TEMP%\%~n0_?.tmp"
rem Insert a leading 0 before each number in version string if the
rem number is smaller than 10. And insert additionally a period at
rem start of each line. The new lines are written to a temporary file.
for /F "useback tokens=1-4 delims=." %%A in ("%VersionsFile%") do (
if %%A LSS 10 ( set "Line=.0%%A." ) else ( set "Line=.%%A." )
if %%B LSS 10 ( set "Line=!Line!0%%B." ) else ( set "Line=!Line!%%B." )
if %%C LSS 10 ( set "Line=!Line!0%%C." ) else ( set "Line=!Line!%%C." )
if %%D LSS 10 ( set "Line=!Line!0%%D" ) else ( set "Line=!Line!%%D" )
echo !Line!>>"%TEMP%\%~n0_1.tmp"
)
rem Sort this temporary file with output written to one more temporary file.
rem The output could be also printed to __stdout__ and directly processed.
%SystemRoot%\System32\sort.exe "%TEMP%\%~n0_1.tmp" /O "%TEMP%\%~n0_2.tmp"
rem Delete the versions file before creating new with sorted lines.
del "%VersionsFile%"
rem Read sorted lines, remove all leading zeros after a period and also
rem the period inserted at start of each line making it easier to remove
rem all leading zeros. The lines are written back to the versions file.
for /F "useback delims=" %%L in ("%TEMP%\%~n0_2.tmp") do (
set "Line=%%L"
set "Line=!Line:.0=.!"
set "Line=!Line:~1!"
echo !Line!>>"%VersionsFile%"
)
rem Finally delete the two temporary files used by this batch file.
del "%TEMP%\%~n0_?.tmp" >nul
endlocal
Первый временный файл с несортированными строками содержит для примера ввода:
.03.06.04.02
.03.06.05.01
.03.06.05.10
.03.06.05.11
.03.06.05.12
.03.06.05.13
.03.06.05.02
.03.06.07.01
.03.06.07.10
.03.06.07.11
.03.06.07.02
.03.06.07.03
Второй временный файл с отсортированными строками содержит для примера ввода:
.03.06.04.02
.03.06.05.01
.03.06.05.02
.03.06.05.10
.03.06.05.11
.03.06.05.12
.03.06.05.13
.03.06.07.01
.03.06.07.02
.03.06.07.03
.03.06.07.10
.03.06.07.11
Чтобы понять используемые команды и то, как они работают, откройте окно командной строки, выполните там следующие команды и полностью прочитайте все страницы справки, отображаемые для каждой команды.
call /?
... объясняет%~n0
(имя командного файла без пути и расширения файла)del /?
echo /?
endlocal /?
for /?
if /?
rem /?
set /?
setlocal /?
sort /?