这是我在CCF上帮别人解答的一个求助帖(原帖在此:https://bbs.et8.net/bbs/showthread.php?t=989473),感觉这个案例会对很多人有用,于是做了这个教程,详细讲解其中的代码。
以下是楼主的需求(有删节合并排版,只保留与本教程相关之处):
发这个贴是因为最近买了一个礼光歌库, 1TB, 3万多首歌, 歌库文件形式是: 每一首歌都有"歌手"-"歌名".mkv文件和同名的.ksc文件(索引文件). 可是这个歌库目录分类比较混乱, 都是以数字来命名目录, 而且同一个歌手的歌不一定在同一个目录里等. 虽然暂时不影响使用(因为礼光是根据ksc文件来索引歌曲文件的, 不受歌曲在磁盘上目录情况的影响), 但是不利于将来手工更新歌曲. 于是开始尝试重新整理这个歌库. 基本的文件目录结构是参考礼光系统里的分类:
1. 先分语种, 有以下目录: 国语, 粤语, 台语, 闽南话, 韩语, 日语, 英语
2. 每个语种下再分以下目录: 流行,戏曲,民歌,儿歌,生日歌,革命歌,怀旧歌,迪斯科,情歌对唱,影视金曲
3. "流行"目录是大头, 下面再分以下目录: 男歌手, 女歌手, 乐队组合.
计划第一步是把所有的文件复制到同一个目录下, 按照歌手名生成新文件夹, 并把那些mkv, ksc文件依照歌手名移到相应的歌手目录里, 同时删除文件名里面的歌手名和分隔符-.
计划第二步是希望把歌手目录分类整理到下面三个目录里:
"男歌手", "女歌手" 和 "乐队组合".
我初步的想法是先准备三个文本文件, 分别是上面三个分类的名单, 然后通过批处理对歌手目录和名单进行比较, 符合的就move到相应的目录.
对于第一步的解决方案:
根据楼主的描述,这些.mkv或.ksc文件的名称都很有规律,都是按照"歌手-歌名"的格式命名的,因此,只需要以"-"做分隔符号,分别提取它前后的字符串即可;而获取.mkv和.ksc文件名,使用 dir /a-d /b *.mkv *.ksc即可,于是,就有了如下代码(第一个“-”之前必须为歌手名,歌曲名中允许带“-”号):- @echo off
- for /f "tokens=1* delims=-" %%i in ('dir /a-d /b *.mkv *.ksc') do (
- md "%%i" 2>nul
- ren "%%i-%%j" "%%j"
- move "%%j" "%%i"
- )
- pause
复制代码 有一些细节需要注意:
1、使用 tokens=1* 截取第一个短横杠之后的所有字符串,而不是用 tokens=1,2来截取以短横杠分隔的第一节和第二节字符串 ,主要是为了防止歌名中出现短横杠;
2、创建文件夹的时候,无需事先判断文件夹是否已经存在,直接使用 md "%%i" 2>nul 即可,它可以创建不存在的文件夹,并可以保持已经存在的文件夹不发生任何改变;当文件夹已经存在的时候,需要使用 2>nul 来屏蔽出错信息;
当我的代码发出来之后,楼主又增加了一条需求:
忘记了一个地方, 能不能加一个功能, 不然歌曲文件不能被索引:在移动文件之前, 先修改ksc文件的内容. ksc文件是一个文本文件, 作为mkv歌曲文件的索引文件. 需要将里面的一句
karaoke.CommonVideo := '周杰伦-稻香.MKV';
修改为
karaoke.CommonVideo := '稻香.MKV';
ksc文件的内容一般是这样的
karaoke.tag('歌名', '稻香');
karaoke.tag('缩写', 'DX');
karaoke.tag('歌手', '周杰伦');
karaoke.tag('字数', '2');
karaoke.tag('语种', '国语');
karaoke.tag('歌类', '男人');
karaoke.tag('电影', 'False');
karaoke.tag('风格', '流行');
karaoke.tag('流行', 'True');
karaoke.tag('音量', '200');
karaoke.tag('声道', '2');
karaoke.tag('语音', '0');
karaoke.tag('介质', '10');
karaoke.tag('时间', '2008-9-5');
karaoke.tag('歌星拼音', 'ZJL');
karaoke.mtvmode :=true;
karaoke.videofilename := '';
karaoke.audiofilename := '*.wav';
karaoke.XSDVideoMode := 4;
karaoke.CommonVideo := '周杰伦-稻香.mkv';
由于楼主当时的举例比较特殊,需要修改的那一行正好位于最后一行上,因此,我的第一反应就是用 findstr "$" 来过滤掉最后一行内容,再追加修改后的内容;后来,根据楼主的说明,发现这个 karaoke.CommonVideo := '周杰伦-稻香.MKV'; 可能出现在任意行上,这样一来,过滤掉最后一行内容的方案行不通了,只能另辟蹊径。
既然能位于任意一行,那么,把它过滤掉之后,再追加到最后一行上,不就可以行得通么?
于是,新代码就出炉了:- @echo off
- for /f "tokens=1* delims=-" %%i in ('dir /a-d /b *.mkv') do (
- md "%%i" 2>nul
- ren "%%i-%%j" "%%j"
- move "%%j" "%%i"
- findstr /ivc:"karaoke.CommonVideo" "%%i-%%~nj.ksc">"%%i\%%~nj.ksc"
- echo karaoke.CommonVideo := '%%j';>>"%%i\%%~nj.ksc"
- del "%%i-%%~nj.ksc"
- )
- pause
复制代码 几点说明:
1、由于*.mkv与*.ksc成对出现,找到了*.mkv文件名,*.ksc文件名也就手到擒来了,因此,for语句的第一对括号中,只需 dir /a-d /b *.mkv 即可;如果还像上一次那样使用 dir /a-d /b *.mkv *.ksc 语句,那么,在接下来的代码中,还得判断获取到的字符串是*.mkv还是*.ksc,势必会使用变量延迟语句;而变量延迟会带来一些其他问题:要么会丢弃感叹号,要么会因为频繁地开启和关闭变量延迟而导致效率低下;
2、findstr /ivc:"karaoke.CommonVideo" "%%i-%%~nj.ksc">"%%i\%%~nj.ksc" 语句中,/i表示兼容大小写,/v表示过滤掉含有特定字符串的行,而/c:则表示它之后的字符串是普通的字符串,其中的特殊字符将被视为普通字符——因为字符串"karaoke.CommonVideo"中含有findstr命令下的特殊字符点号;findstr语句会把匹配的内容全部读取到内存中后,一次性地输出,所以,导出到"%%i\%%~nj.ksc"的时候,只需使用>即可,而无需使用追加命令>>,这样一来,就可以多次运行这个代码,而无需担心后面几次运行的结果会追加到"%%i\%%~nj.ksc"中去——如果在这里使用了>>,则它的上一句还得使用 cd.>"%%i\%%~nj.ksc" 语句来先行清空前一次运行产生的结果,这样的代码显得不够简洁;
第二步的解决方案是这样的:
假设已经用前面的代码按照歌手名整理了文件,那么,可以按照如下步骤来分类歌手文件夹:
1、把歌手名分别存入 男歌手.txt、女歌手.txt、乐队组合.txt 中,一行一条记录,前后不能有多余的字符,把这些文件放入歌手名文件夹同级的目录下;
2、保证文件夹下没有其他txt文件;
3、运行如下代码:- @echo off
- for %%i in (*.txt) do (
- md %%~ni 2>nul
- for /f "delims=" %%j in (%%i) do move "%%j" %%~ni
- )
复制代码 几点说明:
1、获取当前文件夹下的某类文件,可以用 dir /a-d /b *.txt 这样的代码,也可以使用 for %%i in (*.txt) do echo %%i 这样的语句,主要的区别在于:前者可以获取任意属性的文件,后者不能得到带隐藏属性的文件;由于前者还需要放入for语句中解析,不如后者用for语句来得直接,所以效率要稍逊于后者;
2、md %%~ni 2>nul ,可千万别忘了其中的~n!因为 %%i 获取的是形如 男歌手.txt 之类带后缀名的字符串,还需要用 ~n 来去掉其中的后缀名;
3、由于歌手的名字中,有可能出现空格、&等特殊字符,所以,在用 for /f 语句解析文本内容的时候,一定要使用 "delims=" 来获取整行字符串,以防获取到的字符串在空格或&字符处断开;
把以上代码综合一下,就可以得到这样的代码:- @echo off
- for /f "tokens=1* delims=-" %%i in ('dir /a-d /b *.mkv') do (
- md "%%i" 2>nul
- ren "%%i-%%j" "%%j"
- move "%%j" "%%i"
- findstr /ivc:"karaoke.CommonVideo" "%%i-%%~nj.ksc">"%%i\%%~nj.ksc"
- echo karaoke.CommonVideo := '%%j';>>"%%i\%%~nj.ksc"
- del "%%i-%%~nj.ksc"
- )
- for %%i in (*.txt) do (
- md %%~ni 2>nul
- for /f "delims=" %%j in (%%i) do move "%%j" %%~ni
- )
复制代码 运行一下,楼主的所有需求就统统搞定了。 |