【问题标题】:How to sort lines of a text file containing version numbers in format major.minor.build.revision numerical?如何以major.minor.build.revision数字格式对包含版本号的文本文件的行进行排序?
【发布时间】:2015-12-22 13:47:57
【问题描述】:

我有一个 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 位数字之前,该命令似乎执行正确。

【问题讨论】:

  • 嗨 Mofi,我是批处理脚本的新手,因此到目前为止我没有使用任何特殊的批处理代码,只使用经典的排序命令
  • 在 DOSTips.com 上查看我的 JSORT.BAT sorting utility - 它是一个纯粹基于脚本的通用排序解决方案。使用 JSORT.BAT,您的解决方案就像 call jsort versions.txt /n 一样简单

标签: sorting batch-file cmd


【解决方案1】:

这是批处理文件中的常见问题。所有排序方法都使用 string 比较,其中“10”在“2”之前,因此有必要在小于 10 的数字中插入左零。下面的批处理文件这样做,但不是生成一个具有固定数字的新文件,它使用它们来创建一个将自动排序的 array。之后,数组元素按其自然(排序)顺序显示。

编辑:我修改了代码以便管理四部分中的两位数字。

@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

【讨论】:

  • 每个数字加 100 并仅使用最后 2 位数字非常棒。使用环境变量在内存中执行所有操作以及命令 set 输出总是按名称排序的环境变量这一事实也很棒。比起我的解决方案,我更喜欢这个解决方案。
  • 非常感谢 Aacini,这确实对我有用。
【解决方案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

【讨论】:

  • 嗨壁球,谢谢!这确实有效,但是如果我添加例如 3.10.7.4 的值,它会放在这里: 3.1.0.6
    3.10.7.4
    3.1.2.1
  • @NikiSzabo,是的。批量排序变得非常复杂,因为它不进行数字排序。我根据您的最后一个节点的编号大于 9 对其进行了编码。我们在 dostips.com 上有一个名为 SORTN 的批处理文件,它将为您进行数字排序。 dostip.com 上还有一个名为 JSORT 的混合批处理文件。其中任何一个都应该处理你扔给他们的任何东西。
  • 它看起来像 Aacini,您同时对如何使用环境变量在内存中进行高效排序有相同的想法。您始终插入前导 0 并仅使用最后 2 位数字的方法很可能比添加 100 快一点,因为命令处理器不需要将字符串转换为整数。这也是一个很好的解决方案。
  • @Mofi,这是我在 Dostips.com 上看到 Aacini 多次使用的一种技术。我想我可以更新它以处理其他 3 个节点中大于 9 的数字,但 Aacini 已经用他的代码做到了。希望人们在提出问题时能够预先给出准确的例子。
【解决方案3】:

这是我使用 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 /?

【讨论】:

  • 看来你和我同时有一个类似的想法,使用sort和临时文件;这可能不如 Aacini 的解决方案优雅,但它很棒,解释得很好......
【解决方案4】:

最简单的解决方案是调用 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

部分功劳应归this question and accepted answer

【讨论】:

    【解决方案5】:

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

    @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 by 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
    
    管道左侧:

    为了避免delayed expansion,我将10000添加到版本号的每个组件(类似于Aacini's answer)中,而不是像上面使用临时文件的方法中的子字符串扩展语法,因为这不是在任一新的 cmd 实例中启用。为了输出结果值,我利用for /F 循环在cmd 上下文而不是batch 上下文中运行的事实,其中set /A 将结果输出到STDOUTset /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
    

    【讨论】:

    • 这也是这个排序任务的一个很好的解决方案,也很好解释。
    猜你喜欢
    • 1970-01-01
    • 2011-05-01
    • 1970-01-01
    • 2016-03-17
    • 1970-01-01
    • 1970-01-01
    • 2022-01-01
    • 2011-08-20
    • 2013-11-27
    相关资源
    最近更新 更多