美文网首页
批处理学习:查找文件夹下非指定拓展名的文件

批处理学习:查找文件夹下非指定拓展名的文件

作者: Azur_wxj | 来源:发表于2019-03-02 19:12 被阅读0次

需求

在Windows系统中,以命令行方式,找出当前目录下所有拓展名不是.md.js.bat的文件。

解决方法一

> dir /a-d /b | findstr /v ".*\.bat$ .*\.js$ .*\.md$"

分析

dir命令

首先使用dir命令,找到当前目录下所有文件:

> dir /a-d /b 

命令行中使用> help dir可以查看该命令的参数详解。
对上面这个命令,/a表示 显示具有指定属性(attribute)的文件,其后紧跟属性:-表示“否”,而d表示目录文件(即文件夹)。所以/a-d表示非文件夹的文件。
/b表示使用空格式(没有标题信息或摘要)。

C:\Users\Berlin\Desktop\demo>dir /a-d
 驱动器 C 中的卷没有标签。
 卷的序列号是 F2B9-76C6

  C:\Users\Berlin\Desktop\demo 的目录

2018/04/16  11:21               263 override.ts
2018/04/14  10:37           525,295 package-lock.json
2018/04/14  10:37             1,328 package.json
.....
              16 个文件        536,101 字节
               0 个目录 83,691,253,760 可用字节

C:\Users\Berlin\Desktop\demo>dir /a-d /b
override.ts
package-lock.json
package.json
...

findstr命令

基本用法就是findstr <模板字符串> <待查找字符串>。这里模板字符串默认是使用正则表达式的,可以使用通配符等,但是不能用圆括号的捕获组。
例如为了匹配以.md.js.bat为拓展名的文件,以JS风格的正则表达式可能会写成:.*\.(md|js|bat)$,注意第二个点字符要转义,第一个点字符表通配符。

然而在这里将无法使用上述风格的正则表达式,为了能够匹配,我们需要写成

".*\.bat$ .*\.js$ .*\.md$"

即一个字符串,使用空格作为分隔,那么它就会匹配以.md.js.bat为拓展名的文件。
假设有一个叫temp file.txtanother file.txt的文件,而只想匹配前者,则表达式"^te.* e\.txt$"会把两个文件都匹配到,而我们的初衷可能是希望匹配 以te开头、后跟任意多个字符、加一个空格、以e.txt 结尾的文件,anthore file.txt不符合这个规则,不应该被匹配到。
实际上,因为这个表达式有空格,所以实际上会被拆分为两个表达式:^te.*e\.txt$,显然后者表达式可以把another file.txt匹配到。
所以为了只匹配temp file.txt需要写成^te.*e\.txt,即点通配符也包含了空格。

另一种需求是,如果想把引号中的所有字符解释为一个整体,那么可以使用/c:选项,例如,/c:".txt .bat",这意味着检索时将".txt .bat"看作是整体,并且点字符就是点字符,不再具有正则表达式的通配符功效。也就是说,/c:选项后跟的字符串是一个普通字符串,并且是一个整体不分割

/c:选项可以使用多个。例如,想检索包含.js.md的文件名,可以使用命令dir /a-d /b | findstr /c:".js" /c:".md"完成。但问题在于,由于他解释为普通字符串而不是正则表达式,所以类似package.json的文件也会被匹配到,而我们本意是找到以它们结尾。此时$符也无法使用了。

最后,findstr命令有一个选项是/v,它表示“只打印不包含匹配的行。”。而这个功能正是我们需要的。

综上所述,使用dirfindstr,利用管道,我们在命令行中完成了需求。

解决方案二

这里使用bat文件来完成,并且使用另一种思路,即用FOR循环和IF语句完成。重点是理解setlocal enableDelayedExpansion的用途。

@echo off
setlocal enableDelayedExpansion
for %%I in (*.*) do (
    set /a res=0
    if "%%~xI" == ".md" set /a res+=1 
    if "%%~xI" == ".js" set /a res+=1 
    if "%%I" == "%~nx0" set /a res+=1 
    
    if !res! equ 0 echo %%I
)

endlocal

分析

注意循环变量I的写法:在命令行中是%I,而在批处理文件中是%%I。并且在这里,循环变量只能是一个大写或小写字母组成。
使用FOR循环,在当前目录下所有的文件中(*.*)中遍历。每次循环时,设置一个变量res并初始化为0.

set/a选项表示将等号右边的字符解释为数学表达式。如果没有/a,那么就会把字符0赋给res。使用/a选项可以完成后续的+=等数学运算。具体使用help set查看帮助。

如果当前文件的拓展名等于.md,则res自增1。

~x表示显示拓展名,~n表示文件名,~nx表示文件名和拓展名。例如,对于遍历到package.json时,相关输出如下:

  • echo %%I => package.json
  • echo %%~xI => .json
  • echo %%~nI => package
  • echo %%~nxI => package.json

%0表示批处理文件本身(即其值为字符串"foo.bat"

做字符串比较时,例如"%%~xI" == ".md",要把变量I放到双引号中,否则可能无法比较

当三个if都完成后,如果拓展名不是.md.js.bat,那么res的值就为0,则输出。

启用延迟环境变量扩展

语句

setlocal enableDelayedExpansion

将启用延迟环境变量扩展。为什么要这样做呢?
详细讲解见参考资料[1][3]。简单来说,批处理程序是逐条执行批处理脚本的,它会先读入一条语句,然后对该语句里的变量作替换,然后开始解释这个语句。
例如:

set a=4 
set a=5&echo %a% 
echo %a%
  1. 第一条语句执行完后,局部变量a的值为4。
  2. 接下来,处理程序先读入第二条语句(set a=5&echo %a%),然后对变量进行替换,此后就变成了set a=5&echo 4,替换完成后才开始执行这个语句,因此最终结果是输出4而不是5.
  3. 第二条执行完后,变量a变成了5,所以第三句输出5

这里的关键在于,语句执行之前,变量被替换为当前的值。因此在本条语句执行过程中,该变量的变化是不会被检测到的(它会在之后的语句中生效,例如例子中的第三句)

为了能使得局部变量的变化能被检测到,那么在执行之前就不能对其进行值替换,也就是延迟赋值。这就是为什么要使用setlocal enableDelayedExpansion的原因:

setlocal enableDelayedExpansion
set a=4 
set a=5&echo !a!
echo %a%

上面的批处理运行结果是输出两个5. 我们注意到第三句中的变量a使用感叹号进行取值而不是百分号。

由于启动了变量延迟,所以批处理能够感知到动态变化,即不是先给该行变量赋值,而是在运行过程中给变量赋值,因此此时a的值就是5了

启用延迟赋值后,应该在适当地方使用endlocal命令进行关闭。

没有使用endlocal语句停用延迟环境变量扩展功能,则MS-DOS解释器会在程序的末尾自动调用endlocal命令重置MS-DOS环境默认值。

回到本方案代码中去,在最后一句IF判断中,使用了

if !res! equ 0 echo %%I

res使用了延迟赋值(两个感叹号)。因此在之前的IF中如果res发生了变化,则此处也会被检测到并使用。

作为一个反例,我们来看看下面的代码:

@ECHO OFF
set res=Bonjour
for %%I in (A B C) do (
    set /a res=99
    echo %res%
)

在FOR循环开始之前对局部变量res赋值为Bonjour(因为没有使用/a,故认为是字符串)。在FOR循环中,每一轮都将res赋值为数值99,我们可能认为会连续输出三次99,但事实是,连续输出三次字符串“Bonjour”:

C:\Users\Berlin\Desktop\demo>set_local.bat
Bonjour
Bonjour
Bonjour

这是因为,FOR循环的循环体使用括号括起来的,因此整个循环体语句实际上是一条语句!!。因此在执行FOR循环之前res已经是“Bonjour”了,在执行FOR的时候,自然会做变量替换,因此它等价于下面的程序

@ECHO OFF
set res=Bonjour
for %%I in (A B C) do (
    set /a res=99
    echo Bonjour
)

解决办法:

@ECHO OFF
setlocal enableDelayedExpansion
set res=Bonjour
for %%I in (A B C) do (
    set /a res=99
    echo !res!
)
endlocal

输出:

C:\Users\Berlin\Desktop\demo>set_local.bat
99
99
99

【要点】

  1. 使用setlocal enableDelayedExpansion开启延迟赋值
  2. 局部变量使用!foo!进行引用,而不是%foo%

参考资料

  1. bat中if语句的用法
  2. 请问批处理中符号%nx0和!num!%%xm表示什么意思呢?
  3. 批处理中setlocal enabledelayedexpansion的作用详细整理

相关文章

网友评论

      本文标题:批处理学习:查找文件夹下非指定拓展名的文件

      本文链接:https://www.haomeiwen.com/subject/qfzeuqtx.html