Board logo

标题: [文件操作] 批处理怎样拆分文件,补全序号位数与最大值位数相同? [打印本页]

作者: qd2024    时间: 2024-1-31 07:34     标题: 批处理怎样拆分文件,补全序号位数与最大值位数相同?

我经常会导出一些文件,以PDF为主,但不仅限于PDF,以“_”下划线为分界,前端为原文件文件名,后端为自然序号,对应原文件的页码,想统一导出文件,“_”下划线后端自然序号的位数,

当运行BAT时,先自动判断“_”下划线后序号的最大值,以确定序号的位数,如例图中是124,则所有“_”下划线后序号要补充为3位数,          若最大值为78则,所有“_”下划线后序号要补充为2位数,若最大值为1178则,所有“_”下划线后序号要补充为4位数,也就是最大值为几位,就把其他序号都补充到几位。

补充数位的结果是,在“_”下划线和原有序号之间插入相应个数的“0”.

谢谢
链接:https://pan.baidu.com/s/1o7aCmYyNIepGhqU0alKqfw?pwd=vt04
提取码:vt04
--来自百度网盘超级会员V10的分享


下面2张图片是 处理前 后的对比,
【处理前】


【处理后】

作者: newswan    时间: 2024-1-31 08:31

一个文件夹就一个序列?
作者: wanghan519    时间: 2024-1-31 08:32

本帖最后由 wanghan519 于 2024-1-31 08:34 编辑
  1. #@&cls&powershell "type '%~0'|out-string|iex"&pause&exit
  2. dir *.pdf | group {$_.Name -replace '_.*$'} | %{$m=0;$_.Group | sort {$_.Name.Length} -Descending | %{$a=$_.Name -split '[_.]';if($a[-2].Length -gt $m){$m=$a[-2].Length};rni $_.Name ($_.Name -replace '_(\d+)\.',"_$($a[-2].PadLeft($m,'0')).")}}
复制代码
写的比较啰嗦,凑活能用,回头再改
作者: qd2024    时间: 2024-1-31 08:32

回复 2# newswan


    是的
作者: qixiaobin0715    时间: 2024-1-31 09:37

本帖最后由 qixiaobin0715 于 2024-2-1 08:45 编辑

假设序号不超过5位数:
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. for %%i in (1 10 100 1000 10000) do if exist *_%%i.pdf set n=%%i
  4. set /a n*=10
  5. for /f "tokens=1* delims=_" %%i in ('dir /b /a-d *_*.pdf') do (
  6.     set m=%%~nj
  7.     set /a m+=n
  8.     ren "%%i_%%j" "%%i_!m:~1!%%~xj"
  9. )
  10. pause
复制代码

作者: pd1    时间: 2024-1-31 09:40

本帖最后由 pd1 于 2024-1-31 09:45 编辑

我是直接看文件个数判断总位数的,如果你数字从1开始不间断的可以这样试试
  1. <# :
  2. @echo off
  3. powershell -NoProfile -ExecutionPolicy bypass "Get-Content -literal '%~f0' |Out-String|Invoke-Expression"
  4. pause
  5. #>
  6. $file=dir *.pdf
  7. $n=([string]$file.Length).Length
  8. $file|%{ren $_ ([regex]::Replace($_.name,'(?<=_)\d+',{param([string]$a) "{0:d${n}}" -f [int]$a}))}
复制代码

作者: pd1    时间: 2024-1-31 10:00

如果数字不连续或者不是从1开始  这样试试
  1. <# :
  2. @echo off
  3. powershell -NoProfile -ExecutionPolicy bypass "Get-Content -literal '%~f0' |Out-String|Invoke-Expression"
  4. pause
  5. #>
  6. $file=dir *.pdf
  7. $max=($file|%{[int]$_.BaseName.split("_")[1]}|Measure-Object -Maximum).Maximum
  8. $n=([string]$max).Length
  9. $file|%{ren $_ ([regex]::Replace($_.name,'(?<=_)\d+',{param([string]$a) "{0:d${n}}" -f [int]$a}))}
复制代码

作者: ShowCode    时间: 2024-1-31 11:40

回复 1# qd2024
  1. @echo off
  2. cd /d "%~dp0"
  3. setlocal enabledelayedexpansion
  4. set "MaxLen=0"
  5. for /f "tokens=2 delims=_" %%a in ('dir /b /a-d *_*.pdf') do (
  6.     for /f "skip=1 delims=:" %%i in ('^(echo "%%a"^&echo.^)^|findstr /o ".*"') do (
  7.         set /a StrLen=%%i-5-4
  8.     )
  9.     if !StrLen! gtr !MaxLen! (
  10.         set "MaxLen=!StrLen!"
  11.     )
  12. )
  13. set "MaxNum=1"
  14. for /l %%a in (1,1,!MaxLen!) do (
  15.     set /a MaxNum=MaxNum*10
  16. )
  17. for /f "tokens=1* delims=_" %%a in ('dir /b /a-d *_*.pdf') do (
  18.     set "FileNum=%%b"
  19.     set "FileNum=!MaxNum!!FileNum:~0,-4!"
  20.     ren "%%a_%%b" "%%a_!FileNum:~-3!%%~xb"
  21. )
复制代码

作者: aloha20200628    时间: 2024-1-31 16:54

本帖最后由 aloha20200628 于 2024-2-1 19:35 编辑


用纯P拿下本帖的一个看点是如何准确简捷地获取文件名末尾的序号最大长度(无论序号连续与否),给一个请findstr出场的版本,其一是goto方案,是可控循环。其二是for/L方案,是不可控循环,只能预设上限。
第3行代码用了老帖中关于!v!续命传值给%v%的方法,此处用之为免其后截取变量字符串时不必调用for句式方可完成的开销。
代码可存为test.bat,将目标目录拖至脚本运行即可完成其中文件更名,如*_1.pdf 》*_001.pdf
  1. @echo off &setlocal enabledelayedexpansion
  2. dir /b/s/a-d "%~1\*.pdf">0.0 & (call :getMax 0.0 m)
  3. (endlocal & set "max=%m%") & setlocal enabledelayedexpansion
  4. for /L %%n in (1,1,%max%) do set "p=0!p!"
  5. for /f "delims=" %%F in (0.0) do (
  6.    set "xF=%%~xF"
  7.    for /f "tokens=1-2 delims=_" %%1 in ("%%~nF") do (set "q=!p!%%2" & ren "%%~F" "%%1_!q:~-%max%!!xF!")
  8. )
  9. del 0.0 & endlocal & exit/b
  10. :getMax // %1=listF %2=digits
  11.   :[loop]
  12.   set/a "k+=1" & set "n=!n![0-9]" & findstr "!n!\.pdf" %1>nul && goto[loop] || (set/a "%~2=k-1" & exit/b)
复制代码
随附 for/L 方案如下仅供参考
  1. :getMax // %1=listF %2=digits
  2.   for /L %%n in (0,1,9) do set "n=!n![0-9]" & findstr "!n!\.pdf" %1>nul || (set/a "%~2=%%n" & exit/b)
复制代码

作者: buyiyang    时间: 2024-1-31 19:42

回复 8# ShowCode


    请问这里"set /a StrLen=%%i-5-4"的 5 是怎么得来的?
作者: ShowCode    时间: 2024-1-31 20:38

回复 10# buyiyang


在CMD窗口里面执行这个命令应该能理解:
  1. (echo "ABC"&echo.)|findstr /o ".*"
复制代码

作者: buyiyang    时间: 2024-1-31 21:03

本帖最后由 buyiyang 于 2024-1-31 21:12 编辑

回复 11# ShowCode
执行
  1. (echo,a)>1.txt
复制代码
1.txt十六进制为:
  1. 61 0D 0A
复制代码
  1. echo,a|find /v "">1.txt
  2. 61 0D 0A
复制代码
  1. (echo,a)|find /v "">1.txt
  2. 61 20 0D 0A
复制代码
  1. (echo,a&echo,b)>1.txt
  2. 61 0D 0A 62 0D 0A
复制代码
  1. (echo,a&echo,b)|more>1.txt
  2. 61 20 0D 0A 62 20 0D 0A 0D 0A
复制代码
主要是疑惑为什么通过管道会多空格字符(20h)。
作者: ppll2030    时间: 2024-1-31 21:38

回复 10# buyiyang
我的理解是, “新建文档_” = 5    “.pdf” = 4
作者: buyiyang    时间: 2024-1-31 22:00

回复 13# ppll2030


    %%a已经是被"_"分割后的第二个数字字段了,findstr /o 通过偏移值获得%%a的长度,.pdf是4,另一个数字应该也是4(两个引号、回车、换行),
但(echo "%%a"&echo.)通过管道却多了一个空格,所以实际是5,我的问题是为什么会多空格。
作者: WHY    时间: 2024-1-31 22:51

回复 14# buyiyang


    这个可能跟 CMD 预处理机制本身有关。
  1. echo;a&echo;b
  2. pause
复制代码
保存为批处理,运行后可以看到预处理之后的样子:echo;a  & echo;b
echo;a后面为两个空格,多出了一个空格。CMD 它想这么解析,没办法。
作者: WHY    时间: 2024-1-31 22:59

本帖最后由 WHY 于 2024-2-2 11:30 编辑
  1. @if(0)==(0) echo off
  2. pushd "D:\Test"
  3. for /f "tokens=1*delims=|" %%i in ('dir /b *_*.pdf ^| cscript -nologo -e:jscript "%~f0"')do (
  4.     echo ren "%%i" "%%j"
  5. )
  6. pause & exit
  7. @end
  8. var max = 0, arr = [];
  9. var reg = /_([0-9]+)(?=\....$)/;
  10. while (!WSH.StdIn.AtEndOfStream) {
  11.     var file = WSH.StdIn.ReadLine();
  12.     var m = file.match(reg);
  13.     if (!m) continue;
  14.     if (max < m[1].length) max = m[1].length;
  15.     arr.push(file);
  16. }
  17. var zero = new Array(max).join('0');
  18. var reg1 = new RegExp('_[0-9]{' + max + '}(?=\....$)');
  19. for (var i = 0; i < arr.length; i++) {
  20.     if (arr[i].match(reg1)) continue;
  21.     var newFile = arr[i].replace(
  22.         reg,
  23.         function(s0,s1){return '_' + (zero + s1).slice(-max)}
  24.     )
  25.     WSH.Echo(arr[i] + '|' + newFile);
  26. }
复制代码

作者: buyiyang    时间: 2024-1-31 23:30

回复 15# WHY

看我12楼第四个例子,
  1. (echo,a&echo,b)>1.txt
  2. 61 0D 0A 62 0D 0A
复制代码
虽然控制台显示
  1. (echo,a & echo,b ) > 1.txt
复制代码
但输出并没有空格。
  1. @echo off >&3 3>1.txt
  2. echo,a&echo,b
复制代码
这样也是,只有通过管道才会有空格。
作者: WHY    时间: 2024-1-31 23:44

回复 17# buyiyang
多翻一翻老帖:
http://www.bathome.net/viewthread.php?tid=7629
示例:
(echo test)|findstr /c:" "
效果:显示test,findstr认为前面的命令输出含有空格
注释:CMD的预处理在分析语句时,会在)和|这些有特殊意义的转义字符前后插入空格,同时剪除多余的空格。
        而当)和|一同出现时,CMD的预处理没有把这些空格从命令行中全部过滤掉,而会遗漏一个空格到echo命令中。
        而echo命令则把这个空格连通之前文本一同输出到管道后命令findstr中,所以导致findstr匹配出含空格行。
链接:http://www.bathome.net/viewthrea ... amp;page=3#pid50356
        http://www.bathome.net/viewthrea ... amp;page=4#pid28166

作者: qixiaobin0715    时间: 2024-2-1 08:51

如果序号不连续也可以这样,不过要搜索2次文件,效率上稍差:
  1. @echo off
  2. set n=0
  3. setlocal enabledelayedexpansion
  4. for /f "tokens=2 delims=_" %%i in ('dir /b /a-d *_*.pdf') do (
  5.     if %%~ni gtr !n! set n=%%~ni
  6. )
  7. for /l %%i in (1,1,9) do set n=!n:%%i=0!
  8. set n=1!n!
  9. for /f "tokens=1* delims=_" %%i in ('dir /b /a-d *_*.pdf') do (
  10.     set m=%%~nj
  11.     set /a m+=n
  12.     ren "%%i_%%j" "%%i_!m:~1!%%~xj"
  13. )
  14. pause
复制代码
5楼代码有一处笔误,已修改。
作者: hfxiang    时间: 2024-2-1 11:11

回复 1# qd2024
可借助第3方工具gawk(http://bcn.bathome.net/tool/4.1.0/gawk.exe)来解决
  1. @rem 拆分文件补全序号位数,位数修改(-v"N=03")即可,如4位则改为(-v"N=04"),类推...
  2. @echo off&cd /d "%~dp0"
  3. dir /b *.pdf|gawk -v"N=03" -F"[_.]" "{o=$0;$2=sprintf(\"%%\"N\"d\",$2);print \"move /Y \\\"\" o \"\\\" \\\"\" $1 \"_\" $2 \".\" $3 \"\\\"\"}"|cmd.exe
复制代码

作者: qd2024    时间: 2024-2-1 11:22

本帖最后由 qd2024 于 2024-2-1 11:26 编辑

回复 9# aloha20200628


   

师兄 用第一种方法 怎样才能把文件名前面的序号补齐数位,文件名左端第1个“.”做为分界点,前后的序号,都依据文件总数在不足位的数的前面补“0” ,  使序号的位置与总数位数一致,最后删除“.”和前面的序号,如下图


谢谢
作者: newswan    时间: 2024-2-1 13:11

学习了大家的解法
批处理比较麻烦

不是从1到n情况下,
  1. $folder = "1"
  2. $file = Get-ChildItem -path $folder -Filter "*.pdf" | Sort-Object { ($_.BaseName -replace "^.+_") -as [int] }
  3. $lenMax = ( ( $file | ForEach-Object { ( ($_.BaseName -replace "^.+_").tostring() ).length } ) | Sort-Object )[-1]
  4. $file | ForEach-Object {
  5. $arr = $_.basename -split "_"
  6. if ( $arr[1].length -lt $lenMax ) {
  7. $newName = $arr[0] + "_" + ( "{0:d$lenmax}" -f [int]$arr[1] ) + $_.extension
  8. Rename-Item $_.fullname -newname $newName
  9. }
  10. }
复制代码

作者: newswan    时间: 2024-2-1 13:50

关于 获取字符串长度,还有一个高效方法:
http://www.bathome.net/viewthrea ... AF%CA%FD&page=1
作者: aloha20200628    时间: 2024-2-1 14:15

本帖最后由 aloha20200628 于 2024-2-1 19:20 编辑

回复 21# qd2024

增加一个子过程/函数 cutP 用于截取文件名首部小数点后两个目标字段,以小数点后序号为准匹配最大长度。
  1. @echo off &setlocal enabledelayedexpansion
  2. dir /b/s/a-d "%~1\*.mp4">0.0 & (call :getMax 0.0 m)
  3. (endlocal & set "max=%m%") & setlocal enabledelayedexpansion
  4. for /L %%n in (1,1,%max%) do set "p=0!p!"
  5. for /f "delims=" %%F in (0.0) do (
  6.    set "xF=%%~xF" & for /f "tokens=1-2 delims=." %%1 in ("%%~nF") do (
  7.      (call :cutP "%%~2" %max% s1 s2) & set "q=!p!!s1!" & ren "%%~F" "!q:~-%max%!!s2!!xF!")
  8. )
  9. del 0.0 & endlocal & exit/b
  10. :getMax // %1=listF %2=digits
  11.   :[loop]
  12.   set/a "k+=1" & set "n=!n![0-9]" & findstr "\.!n![^0-9]" "%~1">nul && goto[loop] || (set/a "%~2=k" & exit/b)
  13. :cutP // %1=nameF %2=max %3=s1 %4=s2
  14.   (set "s=%~1" & for /L %%n in (%~2,-1,0) do if "!s:~%%n,1!" geq "0" if "!s:~%%n,1!" leq "9" (
  15.      set/a "k=%%n+1" & for %%k in (!k!) do (set "%~3=!s:~,%%k!" & set "%~4=!s:~%%k!") & exit/b))
  16.   exit/b
复制代码

作者: aloha20200628    时间: 2024-2-1 14:55

本帖最后由 aloha20200628 于 2024-2-1 19:19 编辑


24楼代码已从调试版订正为实用版,其中目标文件类型根据21楼示例中的文件类型已调整为*.mp4

作者: WHY    时间: 2024-2-1 18:13

本帖最后由 WHY 于 2024-2-2 11:26 编辑

假设最大数字位数不超过30位,事实上,30位已经是天文数字。

用 Test-Path 速度太慢,改用正则判断。
  1. $srcPath = 'D:\Test\';
  2. $max = 0;
  3. $files = dir -Path $srcPath -Filter *_*.pdf;
  4. for ($i = 30; $i -ge 1; $i--) {
  5.     $reg = '_[0-9]{' + $i + '}$';
  6.     if (($files.BaseName -match $reg).Count -gt 0) {$max = $i; break;}
  7. }
  8. $reg = '_[0-9]{1,' + ($max-1) + '}$';
  9. $files | ?{$_.BaseName -match $reg} | ren -NewName {
  10.     $arr = $_.BaseName.Split('_');
  11.     $arr[-1] = $arr[-1].PadLeft($max, '0');
  12.     ($arr -join '_') + $_.Extension;
  13. } -whatIf
  14. pause
复制代码

作者: ppll2030    时间: 2024-2-1 20:52

本帖最后由 ppll2030 于 2024-2-2 13:33 编辑

回复 21# qd2024


    如果楼主的文件规律如此整齐可寻。那还是可以用五楼的代码修改一下变量即可得到想要的结果。
五楼楼主的代码我感觉很经典。执行效率也快。
我稍微改了一下。测试结果正确。楼主也可测试一下。
ps:根据大大的指导,再次修改,效果更佳。:lol
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. for %%i in (1 10 100 1000 10000) do if exist %%i.*.pdf set n=%%i
  4. echo %n%
  5. set /a n*=10
  6. for /f "tokens=1-3 delims=." %%i in ('dir /b /a-d *.*.pdf') do (
  7.     set m=%%i
  8.     set str=%%j&set str=!str:*%%i=!
  9.     set /a m+=n
  10.     REM 执行更名请去除echo
  11.     echo ren "%%i.%%j.%%k" "!m:~1!!str!.%%k"
  12. )
  13. pause&exit
复制代码

作者: qixiaobin0715    时间: 2024-2-2 09:37

回复 21# qd2024
前后数字都一样?不会出现“1.1 1.2 1.3...2.1 2.2...”等情况。
作者: qixiaobin0715    时间: 2024-2-2 11:14

回复 27# ppll2030
第8行代码有时会误伤。
  1. set str=!str:%%i=!
复制代码
比如文件名前面序号是1,而文件名某处也包含1,就会出现问题。不如修改为:
  1. set str=!str:*%%i=!
复制代码
这样更加严谨一些。
作者: ppll2030    时间: 2024-2-2 13:30

回复 29# qixiaobin0715


    感谢指导。又学到一招。




欢迎光临 批处理之家 (http://bbs.bathome.net/) Powered by Discuz! 7.2