如何对包含版本号的文本文件的行进行排序,格式为major.minor.build.revision数字?

时间:2015-12-22 13:47:58

标签: sorting batch-file cmd

我有一个txt文件,其值如下:

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

看起来命令排序正确,直到我没有使用2位数字。

5 个答案:

答案 0 :(得分:6)

这是批处理文件中的常见问题。所有排序方法都使用字符串比较,其中" 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

答案 1 :(得分:2)

根据您的示例,这将有效。如果你应该以某种方式得到像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

答案 2 :(得分:2)

在纯批处理脚本中,您可以使用以下代码段:

@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

这将创建一个临时文件,其中包含原始行,前缀为填充版本号和separtor |。填充数字表示每个组件都用前导零填充到四位数。以下是基于您的示例数据的示例:

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 添加到版本号的每个组件(类似于Aacini's answer),以避免{{3} },因为在新的10000实例中未启用此功能。为了输出结果值,我使用cmd循环中的任何一个在for /F上下文而不是cmd上下文中运行的事实,其中batch输出结果到 set /A STDOUT不会使用换行符终止其输出,因此我使用set /A在每个项目之后追加set /P,但最后一项不会追加换行符。对于最后一项,我使用空白.附加换行符。

管道右侧:

排序再次由echo命令完成,其输出由sort解析。这里从每个组件中减去先前添加的值for /F以检索原始数字。为了将结果输出到控制台,使用与管道另一侧相同的技术。

管道数据:

管道传递的数据看起来像这样(再次依赖于问题的例子):

10000

答案 3 :(得分:2)

这是我使用2个临时文件的解决方案,如果其他3个版本号中的一个变得大于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 /?

答案 4 :(得分:2)

最简单的解决方案是调用PowerShell并将版本号视为实际的System.Version对象。这样,Major,Minor,Build和Revision段将被视为整数并相应地进行排序。您可以从批处理脚本中调用它:

powershell "(gc textfile.txt | %%{[version]$_} | sort) -split ' '"

那就是它。简单的单线。如果从cmd提示符处执行此操作,请将double %%替换为单个%。这是命令的细分:

  • 以字符串形式获取以下内容:
    • 获取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

部分赠送金额应转至this question and accepted answer