ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PowerShell查找文件:where.exe与Where-Object的区别与实战

PowerShell查找文件:where.exe与Where-Object的区别与实战 1. 先搞清你说的where是哪个where同名不同命我先问你一个问题你在PowerShell里敲where到底想调用哪个命令这个问题的答案恰恰是很多人第一步就踩坑的地方。Windows里叫where的东西至少有三种身份一个是cmd时代就有的where.exe外部程序一个是PowerShell内置的Where-Object别名还有一个是SQL之类查询语言里的WHERE关键字这个跟文件查找无关不展开。这三种东西在PowerShell环境里共用一个名字你敲下去的那一刻PowerShell会按照自己的命令优先级去解析——结果就是你想要的跟实际发生的常常是两码事。拿我自己举例早年间在PowerShell窗口里执行where /r C:\ test.txt啪的一下直接给我报错Redirection、参数解析乱七八糟地弹了一屏。我当时还以为是命令写错了后来才明白PowerShell把它解析成了Where-Object /r C:\ test.txt/r这个参数被当成脚本块传入Where-Object根本不认这玩意。这就是典型的“同名命令解析错位”问题。所以在开始用where查找文件之前第一件要做的事不是背参数而是搞清楚你的PowerShell会话里where到底指向谁。1.1 一条命令验证PowerShell里的where真身在PowerShell里执行下面这条命令Get-Command where你会看到类似这样的输出CommandType Name Version Source ----------- ---- ------- ------ Alias where - Where-Object看到Alias这个单词了吗这就是关键。PowerShell默认把where定义成了Where-Object的别名也就是说你敲where实际上跑的是Where-Object这个cmdlet。它根本就不是用来做“按路径查找文件”那个事情的。那where.exe去哪里了它确实躺在C:\Windows\System32\where.exe里但因为别名在PowerShell的命令解析优先级里排在外部命令之前所以当你只敲where的时候永远轮不到where.exe上场。这就是为什么很多人说“PowerShell里where没法用”的根本原因。1.2 别急着否定任何一个where它们各有用途看到这里你可能会觉得“那我在PowerShell里就别用where.exe了全用Where-Object不就行了”别急着下结论。这两个东西的定位完全不同where.exe核心能力是“按文件名匹配在指定路径或PATH环境变量里搜索文件位置”它解决的是“文件在哪个目录”这种定位问题。Where-Object核心能力是“对管道里传进来的对象做条件过滤”它解决的是“一堆结果里挑出符合条件的那部分”这种筛选问题。举个生活化的例子where.exe相当于你去图书馆问管理员“这本书放在哪个书架”Where-Object相当于你在已经拿到的一摞书里挑出“2023年以后出版、价格在50块以内”的那几本。一个是定位一个是筛选两个没法互相替代。想通了这一点你在PowerShell里用where查找文件时就有了两条明确的路线继续用where.exe但必须显式加上.exe后缀或者用where.exe全称调用用PowerShell自家的Get-ChildItem配合Where-Object来做更灵活的文件查找。两条路线我都会展开讲而且会告诉你什么场景选哪条。2. where.exe在PowerShell里的正确打开方式搜索PATH与递归扫盘既然where被Where-Object的别名占用了那在PowerShell里想用原本的where.exe最直接的办法就是敲全称where.exe。注意不是敲where是敲where.exe把这个外部程序的扩展名显式写出来。这招在大多数Windows 10和Windows 11机器上都管用。不过where.exe本身的能力边界你得先摸清楚。它不是万能的文件搜索工具它主要做两件事搜索PATH环境变量、按指定目录递归搜索。下面我一个一个说。2.1 where.exe的基本语法与PATH搜索不带任何路径参数时where.exe会在当前用户的PATH环境变量里搜索指定程序或文件。这个功能在排查“命令为什么找不到”的时候特别好用。where.exe python如果系统里装了Python并且PATH配好了你会看到类似这样的输出C:\Users\用户名\AppData\Local\Programs\Python\Python311\python.exe如果PATH里同时有多个版本它会把所有匹配到的路径都列出来。这个特性用来排查环境变量冲突非常有用比如你装了多个Python版本想知道系统实际会先命中哪一个直接看输出的第一行就行。这里提一个重要细节where.exe搜索PATH时是严格按照PATH环境变量里的目录顺序逐个查找的找到第一个匹配的就继续接着找把所有匹配的都列出来。所以输出顺序其实变相告诉了你“命令行解析时哪个版本优先”。如果用系统自带的where不带参数它默认是搜索当前目录和PATH但注意前面说的在PowerShell里必须先输入where.exe才能确保调对程序。2.2 用/r参数递归扫盘找文件where.exe最实用的一个参数是/r它允许你指定一个起始目录然后递归搜索该目录下的所有子目录匹配文件名。where.exe /r C:\Users\用户名\Desktop *.pdf这条命令会扫描桌面下所有子目录找出所有PDF文件。/r后面必须跟一个目录路径这个是硬性要求再后面可以跟一个文件名或通配符模式。使用/r时有三个需要注意的点通配符要加引号如果模式里包含*或?建议用双引号包起来避免某些情况下被命令行解释器二次解析。虽然大多数时候PowerShell不会多事但养成加引号的习惯没有坏处。/r只能匹配文件名不能匹配文件内容也不能按文件大小、修改日期等属性过滤。这一点决定了它只适合做“按名找文件”这种简单任务。搜索大目录比如整个C盘时速度不算快因为它本质上就是一个递归遍历没有索引缓存。跟Everthing这类基于NTFS索引的工具比起来where.exe /r算是“乌龟速度”。我实测过一个案例在一台文件数量超过80万的开发机上用where.exe /r C:\搜索一个特定DLL文件耗时大概在40秒到1分钟之间。能用但不适合频繁执行。2.3 在PowerShell里调用where.exe要避开的坑既然在PowerShell里where已经被别名占用了那除了敲where.exe之外还有没有别的办法有两个我一起说了第一种用调用操作符配合完整路径 $env:SystemRoot\System32\where.exe /r D:\ *.log第二种用Get-Command拿到真实路径再调用$where (Get-Command where.exe).Source $where /r C:\ temp.txt这两种方式本质上是绕过了别名解析强制调用外部程序。第一种更直白第二种在脚本里更稳妥——因为$where变量会动态获取where.exe的真实路径万一系统目录不在默认位置也不影响。还有一个更省事的思路如果你平时经常要用where.exe可以直接在PowerShell配置文件$PROFILE里给where.exe起个新别名比如叫whereexe或者wfindSet-Alias -Name wfind -Value where.exe这样以后你敲wfind就是where.exe不会被Where-Object的别名挡住。不过这个方案有个副作用你会在自己的PowerShell环境里多记住一个自定义别名换到别的机器上还得重新配置。我个人不太推荐在核心工作流里依赖自定义别名但在个人开发机上确实能省不少事。3. PowerShell原生查找组合拳Get-ChildItem Where-Object这才是主角如果只是“找一个文件在哪”where.exe够用了。但如果你想把文件查找变成一套可复用的筛选流程比如“找大小超过100MB的临时文件”“找最近7天修改过的配置文件”那where.exe就完全不够用了。这时候就该PowerShell自家的两个核心成员出场了Get-ChildItem负责“遍历目录拿文件对象”Where-Object负责“按条件过滤对象”。这一套组合拳才是PowerShell里真正意义上的文件查找方案。3.1 Get-ChildItem目录遍历的基础设施先认识一下Get-ChildItem它有很多别名常见的是gci和ls。它做的事情很简单列出指定目录下的子项子目录和文件并返回成PowerShell对象。Get-ChildItem -Path C:\Users\用户名\Desktop这条命令会把桌面上所有文件和文件夹列出来。注意默认情况下它只看当前目录不递归子目录。想在子目录里搜索必须加-Recurse参数Get-ChildItem -Path C:\Users\用户名\Desktop -Recurse-Recurse一旦加上就会把指定目录下所有层级的子目录都翻一遍。这个参数是后面一切查找工作的基础。还有一个容易被忽略但很重要的参数是-Filter它可以让你在遍历阶段就根据文件名模式做粗筛。它的执行效率比遍历完再过滤高很多因为它是在文件系统层面直接应用模式匹配Get-ChildItem -Path D:\ -Recurse -Filter *.log上面这条命令只返回D:\下所有扩展名为.log的文件速度比不加-Filter再拿Where-Object过滤快很多。这个参数我建议你刻在脑子里能用-Filter就先上-Filter剩下的再交给Where-Object做精细筛选。3.2 Where-Object的基本用法脚本块与条件表达式拿到Get-ChildItem的输出后Where-Object就派上用场了。它的核心用法是传一个脚本块脚本块里的表达式会针对管道里的每一个对象求值返回True的保留返回False的丢弃。Get-ChildItem -Path C:\ -Recurse -Filter *.dll | Where-Object { $_.Length -gt 50MB }这条命令的意思是在C盘下递归找所有DLL文件然后只保留大于50MB的。$_代表管道中当前正在处理的那个文件对象$_.Length是文件大小属性-gt是PowerShell里“大于”的运算符50MB是PowerShell自带的大小常量等同于50 * 1024 * 1024字节。Where-Object支持的比较运算符很多常用的有这几个运算符含义示例-eq等于$_.Extension -eq .dll-ne不等于$_.Extension -ne .tmp-like通配符匹配$_.Name -like *report*-notlike非通配符匹配$_.Name -notlike *.bak-gt / -ge大于 / 大于等于$_.Length -gt 100MB-lt / -le小于 / 小于等于$_.Length -lt 1KB-and / -or与 / 或$_.Length -gt 1MB -and $_.LastWriteTime -gt (Get-Date).AddDays(-7)我列这些运算符想表达的其实是Where-Object最大的优势不是单个条件而是多个条件自由组合。文件名、扩展名、大小、最后修改时间、文件属性等全部都能筛。3.3 把管道串起来一次典型的文件查找实践空谈概念没意思我拿一个真实场景串一遍完整的命令。场景是这样的我在排查一个生产环境的临时目录发现磁盘空间被大量临时文件占满了。我需要找出位置在C:\Windows\Temp下文件大小超过10MB最后写入时间超过30天扩展名是.log或.tmp。命令如下Get-ChildItem -Path C:\Windows\Temp -Recurse -File | Where-Object { $_.Length -gt 10MB -and $_.LastWriteTime -lt (Get-Date).AddDays(-30) -and ($_.Extension -eq .log -or $_.Extension -eq .tmp) }来逐段拆解-File参数只返回文件不返回目录。这个参数能避免后面把文件夹的Length属性拿来比大小——目录的Length通常不是它的内容总大小容易产生误导。$_.LastWriteTime文件最后一次被修改的时间。跟(Get-Date).AddDays(-30)对比表示“30天之前最后写入”。括号包裹的多个-or条件由于-and和-or优先级不同这里用括号明确逻辑否则PowerShell会按从左到右结合结果可能不符合预期。这套命令跑完之后返回的是文件对象还能接着往下接管道比如直接统计总大小... | Measure-Object -Property Length -Sum或者直接把结果输出到一个清单文件... | Select-Object FullName, Length, LastWriteTime | Export-Csv C:\temp\old_temp_files.csv -NoTypeInformation这就是PowerShell管道的美妙之处查找不只是查找它可以是整套工作流的起点。文件筛选出来之后要复制、要删除、要生成报告直接在后面接对应命令就行。3.4 -File和-Directory参数把“找文件”和“找目录”分开还有一个细节值得单独说Get-ChildItem默认会把文件和目录混在一起返回。如果你只想知道“有没有这个文件”不加-File时目录也会混进来。尤其当你用Where-Object过滤Extension属性时目录的Extension是空的实际上是空字符串你可能会得到一堆不在预期内的结果。建议规则很简单找文件就加-File找目录就加-Directory永远不要指望靠Where-Object去把类型筛干净因为那会在文件系统遍历阶段就白白浪费性能。4. 用实际场景串起来查找不是目的解决问题才是命令讲得再多不落到真实问题上就全是纸上谈兵。我拿三个实际工作中经常遇到的场景把where.exe和Get-ChildItem Where-Object这两套方案串起来演示一遍。你会发现同一个“查找文件”的需求在不同条件下选用的工具截然不同。4.1 场景一排查PATH环境变量里的同名人程序这个场景是做开发环境迁移时最常碰到的明明刚装了一个新版本的工具但命令行里跑命令版本号还是旧的。这时候你需要确定“当前命令行环境到底会从哪个路径加载这个工具”。用where.exe一步到位where.exe java假设输出C:\Program Files\Common Files\Oracle\Java\javapath\java.exe C:\Program Files\Java\jdk-17\bin\java.exe看到这两行问题就很清楚了系统先命中Oracle的公共路径旧版Java在那里后面的JDK 17反而排第二。要把新版JDK设为优先需要调整PATH环境变量的顺序把C:\Program Files\Java\jdk-17\bin提到Oracle路径之前。这个场景里Get-ChildItem Where-Object反而不好使因为你肉眼根本不知道PATH里到底有哪些路径还要先解析环境变量再去遍历绕了一大圈。只有where.exe这个专门干“找命令所在路径”的工具最合适。4.2 场景二批量找出近期被修改过的配置文件假设你在排查一个程序行为异常的问题怀疑是某个配置文件被改动过。你要在程序的安装目录下找出最近24小时内修改过的.xml和.config文件。用PowerShell原生的组合命令Get-ChildItem -Path D:\Apps\SomeService -Recurse -File -Include *.xml, *.config | Where-Object { $_.LastWriteTime -gt (Get-Date).AddHours(-24) }注意这里我用了-Include而不是-Filter。-Filter只接受单个字符串模式而-Include可以接受数组支持写多个模式。但-Include有个脾气单独使用时容易失效必须配合-Recurse参数或者路径末尾带通配符*。这也是个经典的PowerShell坑# 这样可能查不到东西 Get-ChildItem -Path D:\Apps\SomeService -Include *.xml # 这样才是对的 Get-ChildItem -Path D:\Apps\SomeService -Recurse -Include *.xml顺带一提-Include的匹配是基于文件名的完整路径还是文件名历来有争议。实测下来-Include对路径的处理更贴近“在递归模式下的文件名通配”建议总是配合-Recurse使用别单独用。4.3 场景三清理磁盘空间前统计大文件的分布磁盘满了想找出所有超过500MB的大文件。这不仅是一次“查找”你还需要知道它们总共占了多少空间分布在哪些目录。$largeFiles Get-ChildItem -Path E:\Data -Recurse -File | Where-Object { $_.Length -gt 500MB } $largeFiles | Sort-Object Length -Descending | Select-Object FullName, {NameSizeGB; Expression{[math]::Round($_.Length / 1GB, 2)}}第一条命令把大文件对象存到变量里第二条命令按大小降序排列并计算每个文件的大小以GB为单位保留两位小数。注意Select-Object里那个{NameSizeGB; Expression{...}}是计算属性它的作用是生成一列不存在的属性这在做报告时非常实用。如果还想按目录汇总大小可以再加一层Group-Object$largeFiles | Group-Object DirectoryName | ForEach-Object { [PSCustomObject]{ Folder $_.Name FileCount $_.Count TotalSizeGB [math]::Round(($_.Group | Measure-Object Length -Sum).Sum / 1GB, 2) } } | Sort-Object TotalSizeGB -Descending这个命令会按目录分组统计每个目录下大文件的数量和总大小排序列出来。一眼就能看出哪个目录最该清理。4.4 场景选择什么情况下用哪个把前面三个场景总结一下我个人的选择逻辑是这样的需求首选方案备选方案查找命令/程序在PATH里的位置where.exe手动解析PATH环境变量按文件名简单递归搜索Get-ChildItem -Recurse -Filterwhere.exe /r按大小/时间/扩展名等多属性筛选Get-ChildItem Where-Object先-Filter粗筛再Where-Object查找结果需要后续处理统计、导出、删除Get-ChildItem Where-Object管道组合无判断标准很简单如果只是“这个文件放哪了”用where.exe如果是要“按一堆条件把文件挑出来做批量处理”用PowerShell原生组合。后者的扩展性和表达能力完胜。5. 性能优化与避坑心得大目录搜索和编码陷阱我最后想分享几个在实际操作中踩出来、或者说反复验证过的经验。这些细节很少出现在官方文档的醒目位置上但对你的日常工作效率影响不小。5.1 大目录搜索先-Filter后Where-Object在一台文件几十万甚至上百万的开发机上做递归搜索时命令写得好不好执行时间能差出几倍甚至几十倍。核心原则能下沉到文件系统层面的过滤不要交给Where-Object。Get-ChildItem -Filter *.dll是在枚举文件时就让文件系统做模式匹配而Get-ChildItem | Where-Object { $_.Extension -eq .dll }是把所有文件对象全部拉回来再逐个判断。前者在对象产生之前就把不符合条件的排除了内存占用和执行时间都低很多。实测过的一个例子在含45万个文件的目录下递归搜索所有.log文件-Filter方式耗时大约10秒先全量枚举再Where-Object过滤的方式耗时大约45秒。差距就是这么明显。-Filter做不到的事情比如大小过滤、时间过滤、多个扩展名匹配再交给Where-Object处理尽量形成“粗筛精细筛选”两级流水线。5.2 执行策略Execution Policy阻拦脚本的问题很多人在写第一个PowerShell脚本时会遇到“无法加载文件xxx.ps1因为在此系统上禁止运行脚本”的报错。这属于PowerShell执行策略Execution Policy的默认限制不是where命令本身的问题但网上查“PowerShell找不到where”时经常连带着查出这个。如果这是在个人开发机上可以这样放开当前用户的限制Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUserRemoteSigned表示本地创建的脚本可以直接运行从网络下载的脚本需要有可信签名。比直接设成Unrestricted要稳妥一些。需要注意如果你的脚本是别人从网上下载的运行前最好先看一眼内容确认无异常再执行。执行策略本身不是安全边界它只是防止你无意中运行不受信任的脚本。5.3 输出乱码文件名的编码问题有时候用where.exe或Get-ChildItem搜索到中文文件名控制台输出一堆乱码。这通常是控制台代码页和文件系统编码不一致造成的。Windows PowerShell 5.1默认使用系统ANSI代码页中文简体系统一般是GBK代码页936。如果文件名里有系统不支持的特殊字符就会显示成乱码。一个务实的解决办法是把输出结果直接写入文件然后用VS Code打开查看而不是直接看控制台Get-ChildItem -Path D:\ -Recurse -Filter *.ps1 | Select-Object FullName | Out-File -FilePath C:\temp\ps1_list.txt -Encoding utf8-Encoding utf8参数确保输出的文本文件是UTF-8编码用VS Code或新版记事本打开就不会乱码。这个技巧在文件名含有奇葩字符时特别管用。5.4 路径太长导致的搜索中断Windows系统默认路径长度限制是260个字符MAX_PATH。如果目录层级很深Get-ChildItem -Recurse可能会碰到“路径太长”的错误而中断遍历。在较新的PowerShell 5.1及更高版本里部分cmdlet已经支持长路径但前提是要开启系统策略和应用程序清单。开发机上最简单粗暴的做法是用到长路径的场景改用PowerShell 7pwsh它对长路径和操作系统API的封装好很多。不过说实话对于绝大多数日常查找任务260字符限制很少会触发。真要是遇到了优先考虑是不是目录结构设计有问题而不是硬刚长路径。5.5 综合心得把最常用的查找封装成函数我在实际工作中会把最常用的一类查找封装成PowerShell函数放在$PROFILE里调用起来很方便。比如查找“在指定目录下比指定大小更大的文件”function Find-LargeFiles { param( [string]$Path ., [long]$MinSizeMB 100 ) Get-ChildItem -Path $Path -Recurse -File | Where-Object { $_.Length -gt ($MinSizeMB * 1MB) } | Sort-Object Length -Descending | Select-Object FullName, {NameSizeMB; Expression{[math]::Round($_.Length / 1MB, 2)}} }这样以后你在命令行里只需要敲Find-LargeFiles -Path D:\Data -MinSizeMB 200说白了查找文件这个需求本身不复杂但把它放进自己的工具思维里你就可以少重复调试命令把注意力放在真正要解决的问题上。我个人在长时间使用PowerShell的过程中最大的体会就是命令是手段对文件系统的理解才是底层能力。where.exe和Get-ChildItem Where-Object这两套工具覆盖的场景各有侧重搞清楚它们的分工与配合比记住任何一个单独的命令都有用得多。
返回列表