Powershell Test-Path 可以用来测试指定路径的文件是否存在,文件夹也是一种特殊文件,还可以额外测试这个指定路径是普通文件还是文件夹,文件路径还可以包含通配符。
- Test-Path -Path C:\Users -PathType Container
复制代码 当文件路径包含通配符时,Test-Path 命令的行为,可能会超出正常人的理解范围。- Test-Path -Path C:\Windows\* -PathType Container
复制代码 以上命令运行后会返回 $False,嗯,Powershell 的布尔值 true 和 false 有美元符号,大小写无所谓,一般人按照人类的预期会理解为在 C:\Windows 中找不到子文件夹,但 Powershell 的设计者,它们不是一般人,它们的想法高深莫测。我,吴先成,被这种高深莫测的想法坑过的人,经过长达无数个小时非常多次的实际测试,找到了背后真正的逻辑。
Test-Path 的测试逻辑,是,它只会拿符合 -Path 表达式的第一个子项来做测试,而且,如果这个表达式同时匹配普通叶项(leaf)和容器项(Container),叶项会比容易项优先匹配,因为 Powershell 除了支持文件系统还支持注册表等多种容器体系,容器项就是包含叶项的容器,叶项就是被容器包含的非容器项,对应到文件系统中来,叶项就是普通文件,容器项就是文件夹,也叫目录。
也就是说,Test-Path -Path .\foo\* 实际上匹配的是 .\foo 文件夹中第一个匹配这个表达式的对象,如果 .\foo 文件夹中有至少一个普通文件,Test-Path 会拿这一个普通文件或多个普通文件中的第一个普通文件来做测试,如果指定了 -PathType Container,很显然这个普通文件它不是一个容器,也就是不是一个文件夹,Test-Path 直接返回 $False,它不会再拿第二个匹配 .\foo\* 这个表达式的对象去测试,而如果 .\foo 文件夹中没有普通文件,而有至少一个文件夹,Test-Path 会拿这一个文件夹或多个文件夹中的第一个文件夹去做测试,很显然,它是一个文件夹,也就是说它是一个容器,Test-Path 直接就返回了 $True,而不会测试其它对象。指定了 -PathType Leaf 时也是一样的道理,如果 .\foo 文件夹中有普通文件,Test-Path 直接返回 $True,如果 .\foo 文件夹只有文件夹,没有普通文件,Test-Path 返回 $False.
总结起来,就是一句话,Test-Path 带通配符时,只拿第一个被通配符“捕捉”到的对象做测试,而且普通文件比文件夹更容易被捕捉到,常规逻辑下 * 有扩展到无限层次的能力,但在 Test-Path 的 -Path 参数中,实际上 * 和 ? 被局限在它们自己所在的第一层,并不会向下层递进。这是非常反常规反直觉的,普通人会不自觉地认为 .\foo\* 应该匹配 .\foo 文件夹中的所有文件和子文件夹,甚至包括子子孙孙无穷后代,天生地产生两种错误的想法,一种是 .\foo\* 要求每一个符合筛选条件的对象都满足 -PathType 的要求,一种是 .\foo\* 要求任何一个符合筛选条件的对象满足 -PathType 的要求,而实际上,它考查的是第一个符合筛选条件的对象。
问号的匹配逻辑同理,唯一不同的是,一个问号只匹配一个字符,而一个星号匹配的字符不受数量限制。
同样支持通配符而且和文件路径有关的命令还有 Resolve-Path。- Resolve-Path -Path .\foo\*
复制代码- Resolve-Path -Path .\foo\???
复制代码 它遇到通配符时会解析并返回所有符合筛选条件的对象,* 和 ? 也一样被限制在它们所在的本层,并不能通配目录分隔符 \ 或 / ,如果要表示层级,可以使用 *\*,一个 \ 一层,在 Windows 上 / 和 \ 都是目录分隔符,但官方推荐使用 \。
- php -r "print_r(scandir('.\foo'));"
复制代码 以上分别是 Powershell CMD 和 PHP 列出文件夹中一级子项的命令,它们都把文件夹排在普通文件前面,这说明了什么? |
|