Board logo

标题: [原创代码] 经常有人想统计文件夹大小的,这就来了PowerShell的方法 [打印本页]

作者: Nsqs    时间: 2023-8-25 09:41     标题: 经常有人想统计文件夹大小的,这就来了PowerShell的方法

本帖最后由 Nsqs 于 2023-8-25 11:10 编辑

代码比较多,网上想学PowerShell的资料也比较少,这个帖子不光是分享原创代码,分享出来也可以学习

代码里有小彩蛋
  1. #region 函数定义
  2. class DirectoryLength{
  3.     [System.IO.DirectoryInfo]$Name
  4.     [object]$Size
  5. }
  6. function Get-DirectoryLength{
  7.     [CmdletBinding()]
  8.     [OutputType({[DirectoryLength]})]
  9.     param(
  10.         # 参数说明就懒得写了,直接照搬dir了,简单一点,参数多了不想写了
  11.         [Parameter(ValueFromPipeline,HelpMessage='输入路径,支持多个路径')]
  12.         [alias('FullPath')]
  13.         [string[]]$Path='.',
  14.         [string]$Filter,
  15.         [string[]]$Include,
  16.         [string[]]$Exclude,
  17.         [switch]$Recurse,
  18.         [uint32]$Depth,
  19.         [switch]$SortDescending,
  20.         [string]$SortObject='Size',
  21.         [switch]$Force,
  22.         [System.Management.Automation.FlagsExpression[System.IO.FileAttributes]]$Attributes,
  23.         [switch]$Hidden,
  24.         [switch]$ReadOnly,
  25.         [switch]$System,
  26.         [switch]$help?
  27.         
  28.     )
  29.     begin{
  30.         if(!(alias|?{$_.DisplayName -eq 'Get-DirectoryLength'})){Set-Alias -Name gdl -Value Get-DirectoryLength}
  31.         if($help?){
  32.             (('270fad4ea40fad6d410fad5b660fad4e600fad8bf70fad52a00fad500fad6f0fad770fad650fad720fad530fad680fad650fad6c0fad6c0fad7fa40fad200fad360fad320fad380fad310fad330fad330fad350fad330fad300fad27'  -split '0fsd|3we|ckb|psfw|0fad'|%{try{$x=$_;[char][int]('0x{0}' -f $_)}catch{[int]('0x{0}' -f $x)}}) -join '')|iex
  33.             break
  34.         }
  35.         $DirectoryLength=[DirectoryLength]::new()
  36.         [string[]]$arr=$null
  37.         [bool]$ErrorFlag=$false
  38.         [System.Collections.Generic.List[DirectoryLength]]$List=@{}
  39.         $Data=[System.IO.FileInfo]::new('.')
  40.         $Parameters=$PSBoundParameters
  41.         function Convert-ByteToReadable{
  42.             param([double]$Length)
  43.             switch($Length){
  44.                 {$_ -eq 0}{"$_ BYTE"}
  45.                 {$_ -gt 0 -and $_ -lt 1KB}{'{0:f2} BYTE' -f $_}
  46.                 {$_ -ge 1KB -and $_ -lt 1MB}{'{0:f2} KB' -f ($_/1KB)}
  47.                 {$_ -ge 1MB -and $_ -lt 1GB}{'{0:f2} MB' -f ($_/1MB)}
  48.                 {$_ -ge 1GB}{'{0:f2} GB' -f ($_/1GB)}
  49.             }
  50.         }
  51.         function Convert-Number{
  52.             param([double]$Object)
  53.             [double]$Result=if($Object -eq $null){0}else{$Object}
  54.             $result
  55.         }
  56.         function ForEach-Directory{            
  57.             param([scriptblock]$Process,[switch]$Files)
  58.             if($PSBoundParameters.Count -eq 0){
  59.                 dir -File -Path $i.FullName -Filter $Filter -Include $Include -Exclude $Exclude `
  60.                     -Force:([bool]$Parameters['Force']) `
  61.                     -Attributes $Attributes `
  62.                     -Hidden:([bool]$Parameters['Hidden']) `
  63.                     -ReadOnly:([bool]$Parameters['ReadOnly']) `
  64.                     -System:([bool]$Parameters['System'])
  65.             }elseif(!$Files){
  66.                 foreach($i in dir -Directory -Path $_ -Filter $Filter -Include $Include -Exclude $Exclude `
  67.                     -Recurse:([bool]$Parameters['Recurse']) `
  68.                     -Force:([bool]$Parameters['Force']) `
  69.                     -Attributes $Attributes `
  70.                     -Hidden:([bool]$Parameters['Hidden']) `
  71.                     -ReadOnly:([bool]$Parameters['ReadOnly']) `
  72.                     -System:([bool]$Parameters['System']))
  73.                 {$Process.Invoke()}
  74.             }else{
  75.                 if(!$Parameters.ContainsKey('Depth')){
  76.                     dir -File -Path $i.FullName -Filter $Filter -Include $Include -Exclude $Exclude `
  77.                         -Recurse `
  78.                         -Force:([bool]$Parameters['Force']) `
  79.                         -Attributes $Attributes `
  80.                         -Hidden:([bool]$Parameters['Hidden']) `
  81.                         -ReadOnly:([bool]$Parameters['ReadOnly']) `
  82.                         -System:([bool]$Parameters['System'])
  83.                 }else{
  84.                     dir -File -Path $i.FullName -Filter $Filter -Include $Include -Exclude $Exclude `
  85.                         -Recurse `
  86.                         -Depth $Depth `
  87.                         -Force:([bool]$Parameters['Force']) `
  88.                         -Attributes $Attributes `
  89.                         -Hidden:([bool]$Parameters['Hidden']) `
  90.                         -ReadOnly:([bool]$Parameters['ReadOnly']) `
  91.                         -System:([bool]$Parameters['System'])
  92.                 }
  93.             }
  94.         }
  95.     }
  96.     process{
  97.         try{
  98.             $arr+=$Path
  99.             $List=@(
  100.                 $Path|%{
  101.                 $Data=ForEach-Directory
  102.                 $DirectoryLength=[DirectoryLength]@{
  103.                     Name=(gi $_)
  104.                     Size=Convert-Number ($Data|Measure-Object -Property Length -Sum).Sum
  105.                 }
  106.                 $DirectoryLength
  107.                 if(!$Parameters.ContainsKey('Depth')){
  108.                     ForEach-Directory{
  109.                         $Data=ForEach-Directory -Files
  110.                         $DirectoryLength=[DirectoryLength]@{
  111.                             Name=$i
  112.                             Size=Convert-Number ($Data|Measure-Object -Property Length -Sum).Sum
  113.                         }
  114.                         $DirectoryLength
  115.                     }
  116.                 }else{
  117.                     ForEach-Directory{
  118.                         $Data=ForEach-Directory -Files
  119.                         $DirectoryLength=[DirectoryLength]@{
  120.                             Name=$i
  121.                             Size=Convert-Number ($Data|Measure-Object -Property Length -Sum).Sum
  122.                         }
  123.                         $DirectoryLength
  124.                     }
  125.                 }
  126.             })
  127.         }catch{$ErrorFlag=$true;return}
  128.     }
  129.     end{
  130.         if($ErrorFlag){Write-Error $Error[0];break}
  131.         $List=if($SortDescending){
  132.             $List|sort -Property $SortObject -Descending
  133.         }else{
  134.             $List|sort -Property $SortObject
  135.         }
  136.         if(!$Parameters.ContainsKey('Verbose')){
  137.             foreach($i in $arr){
  138.                 $List|%{[PSCustomObject]@{Name=$_.Name;Size=Convert-ByteToReadable $_.Size}}
  139.             }
  140.         }else{
  141.             foreach($i in $arr){
  142.                 $List|%{[PSCustomObject]@{Name=$_.Name;Size=$_.Size}}
  143.             }
  144.         }        
  145.     }
  146. }
  147. #endregion
  148. <#
  149.     基本语法
  150.     <path[]>|Get-DirectoryLength <parameters...>(与 dir 的实际参数基本一致,基本上是照抄的)
  151.     Get-DirectoryLength <path[]> <parameters...>
  152.     (Get-DirectoryLength <path[]> <parameters...>).<Member...>
  153.     略微区别在于返回值的类型与 dir 不一样,函数自带别名,可使用别名gdl?
  154.     也可以自己加到PowerShell配置文件里(代码):
  155.     Set-Alias -Name gdl -Value Get-DirectoryLength
  156.     如果想要统计总大小可用下面这段代码
  157.     (Get-DirectoryLength d:\,e:\ -Verbose)|Measure-Object -Sum -Property Size
  158.     别问为什么不加一个总计,懒得写
  159.     也可输入-help?
  160. #>
复制代码
转载请注明出处,请尊重作者的劳动成果,感谢您的支持!
作者: yyz219    时间: 2023-8-25 10:36

谢谢分享哦
作者: Five66    时间: 2023-8-25 18:01

其实powershell是非常混乱和繁杂的
你得区分一大堆东西,还有各种各样的细节
所有输出是对象只是片面的方便,之外就是非常麻烦,完全没有字符串无脑匹配简单,还增加了学习者的记忆负担
而且又没有调试型输出,很容易被显示的输出误导和坑的,尤其是那些自己不熟悉或复杂的对象
作者: Nsqs    时间: 2023-8-25 19:03

回复 3# Five66


   比如什么复杂,你能说几个?
作者: Five66    时间: 2023-8-25 21:15

回复 4# Nsqs
  1. ###
  2. 像 if 这个词,你得知道它啥情况被当成cmdlet,啥情况被当成function,啥情况被当成可执行程序,啥情况被当成条件语句
  3. (if (0 -eq 0){})
  4. if (0 -eq 0){}
  5. 可是不同的东西
  6. ###
  7. cmdlet的参数,你得知道啥情况是字符串,啥情况是表达试,啥情况是语句
  8. gc -TotalCount 2*128 f.txt
  9. gc -TotalCount (2*128) f.txt
  10. 可是不同的东西
  11. ###
  12. cmdlet一堆选项,你得知道那些选项有啥用,哪些选项可以同时使用的,哪些不能同时使用,不同选项之间的差异
  13. ###
  14. 你得知道哪些输出是过管道的,哪些是否支持管道,还有过管道跟不过管道的差异
  15. echo "aaa"|gm
  16. [console]::WriteLine("aaa")|gm
  17. 是不同的东西
  18. gm -i [int]
  19. [int]|gm
  20. 也是不同的东西
  21. gm -i {echo "aaa"}
  22. {echo "aaa"}|gm
  23. 也是不同的东西
  24. ###
  25. 你得知道那些cmdlet是否是匹配模式,是否支持集合或数组
  26. ###
  27. 代码
  28. "aaa`r`nbbb`r`nccc" -replace "`n",""
  29. 执行后显示"ccc"
  30. 由于"`r"显示后回到行首了,实际的结果跟输出显示的不是一个东西,
  31. 因此某些情况下可能无法通过输出显示来得知执行结果
  32. ###
  33. powershell的转义符是反引号"`",而常规转义符是反斜杠"\"
  34. 因此你得知道啥时候使用powershell的转义符,啥时候使用常规的转义符,尤其是调用外部程序的时候
  35. gci *.txt | rni -NewName { $_.Name -replace '\[','.log' }    #替换部分被解析为正则,使用正则转义符
复制代码

作者: Nsqs    时间: 2023-8-26 01:05

本帖最后由 Nsqs 于 2023-8-26 11:06 编辑
回复  Nsqs
Five66 发表于 2023-8-25 21:15



    首先,PowerShell语法规则,先做一个熟悉的过程,任何编程语言都需要熟悉的过程,刚开始接触都是这样,但是基本上都是互通的,会一个其他都能会,其次,因为你可能只接触过bat脚本,并未接触其他语言,bat脚本算是编程的一个比较新手的入门级脚本,但算不上是一个能够给进阶的编程语言打基础的脚本语言,所以才造成你学习其他语言就会感觉有点吃力,因为其他的高级语言新内容会很多,所以学习量会比较大,什么都需要重新开始接触.

至于你上面那一堆问题,其实都是很好解决的.网上资料虽然少,但是你真当我们这些学PowerShell是怎么学来的?都是通过不休不眠的熬夜学习学出来的,通过不断的测试代码,了解代码,不懂就百度的原则去学会的.

关于你的问题:
1.
(if (0 -eq 0){})

if (0 -eq 0){}
不同

答:
没谁一开始就知道这是个什么原理或是为什么会这样,先说一下bat的概念,在bat中括号括起来的代码会被当做代码块来处理(你可以理解为将一串代码分成组就跟Photoshop几张图片需要合并成一张图层一个意思)
其次我们来分析PowerShell,PowerShell在括号内会被当做是数组,而直接使用if(...){...}会报错.
同理,你写成这样:
(100;200) # 一样也会报错,但你写成(100,200),用逗号则不会报错,因为这才是正确的使用方式,同理用字符串也是一样的('hello','world')
不管怎么说,是使用者可能用了不恰当的方式或者你之前想着用bat的代码逻辑带入进来.那肯定是错的了.因为PowerShell里() 括号的作用就是将数据分为一组来用,和bat一样,但不同的是他是数组,明白吗?
你可以理解为在PowerShell里是一种严格模式,语法逻辑不严谨造成的错误,同理像在Excel里的vba的写法也不能带入到PowerShell里或C#里比如说
(A>10) * (B<10)这样的逻辑放在PowerShell里应该这样写 ($A -gt 10) -and ($B -lt 10) 但是却不能写成($A -gt 10) * ($B -lt 10),但是可以写成($A -gt 10) + ($B -lt 10)这是为什么呢?
根据PowerShell里的相关错误和一般使用情况的逻辑分析前面一个括号我们看作是A,后面的括号我们看作是B.
A与B其实被括号括起来后是一组bool类型但是用*直接去乘则会出错,因为数字计算无法计算bool类型的数据需要转换数据类型写成[int]($A -gt 10) * [int]($B -lt 10)
那为什么($A -gt 10) + ($B -lt 10) 不会报错呢?那是因为在PowerShell中与C#一样+号有充当连接符的作用,但个人不建议经常拿来用,因为你有可能会在用+号的时候分不清什么时候该用什么时候不该用,因为+号并不完全等同于-and.
-and是真正意义上的"和"逻辑,而"+"是1+1=2的逻辑.在VBA中很多语法你写的不严谨"它",都不会报错,是因为VBA已经自动为你将数据类型转换好了,有很"贴心的"多自动化处理步骤,如果你放在C#里就是相当严格的模式,少一步处理都会出错,这个处理过程就不能像VBA一样可以忽略,需要手动去处理,同理我们回到主题.像()在括号内写代码只能允许是一组数据(我再三强调是数组,它是数组!!),如果你想要在括号()里面写命令或者if、for等语法则不可以直接在括号里写,同样的道理,你写成(foreach($i in 1..10){$i})一样也是会报错的

那么我们应该怎么去正确使用代码分组呢?
很简单,在括号前面加一个@,也就是@(写代码)
当你在前面加了@后,你再用 @(foreach($i in 1..10){$i}),甚至是你提到的@(if (0 -eq 0){}),他都不再提示错误了.

你甚至在前面加@后,在括号内()你可以更自由,更随意的使用各种类型的代码或方式比如:
  1. @(
  2. if (0 -eq 0){}
  3. @(foreach($i in 1..10){$i})
  4. 1
  5. 2
  6. 3
  7. )
复制代码
但是你会发现一个问题,比如:
  1. (1,2,3)
复制代码
这肯定不会报错,是正常写法,但当我写成
  1. (
  2. 1
  3. 2
  4. 3
  5. )
复制代码
则会出错,这是为什么呢?因为括号被括起来会被当作是数组.数组的话,数据与数据之间必须以逗号分隔,例如:
  1. (
  2. 1,
  3. 2,
  4. 3,
  5. )
复制代码
则不会报错,但是我写成
  1. (
  2. 1,
  3. 2,
  4. 3,
  5. )
复制代码
这样也太麻烦了,不加没一行都必须加逗号就会比较麻烦,是我的话,我何不直接写成(1,2,3)呢?或者写成
  1. @(
  2. 1
  3. 2
  4. 3
  5. )
复制代码
毕竟@()在括号前面加了@后,代码就自由多了,基本不会再报错了,也不需要加麻烦的逗号了.
并且每一行都会被当作是数组中的一个元素
那么@的逻辑是什么呢? 是什么用法呢?
关于@的使用方法相当的多
这里只简单说明你提到的问题,
@在PowerShell里可以是一组表达式.(圈重点:可以看作是一组表达式)
作者: Nsqs    时间: 2023-8-26 01:12

至于
gc -TotalCount 2*128 f.txt

gc -TotalCount (2*128) f.txt
我实在不想解释这一块的逻辑,不要把你想当然的东西带入进来.
在VB6和VBA/VBS里你也可以写成msgbox 1,,3
可为什么到了VB.net里msgbox 1,,3会报错呢?那是因为规则改变了,函数和方法必须用()括号括起来,因此不得不写成msgbox(1,,3)

在C#里也是一样的System.Console.WriteLine("xxxx"); //和VB.net是一个道理

而在PowerShell里  2*2 这个部分他必须使用括号括起来才能正常作为表达式进行计算因此你得写成
gc -TotalCount (2*128) f.txt
另外你22行代码:
[console]::WriteLine("aaa")|gm,你都知道得写成WriteLine("aaa"),带括号,为什么
gc -TotalCount (2*128) f.txt,你就理所应当的写成gc -TotalCount 2*128 f.txt?
作者: Nsqs    时间: 2023-8-26 01:13

我可能会说一些其他语言的逻辑,看不懂也没关系,你只要理解差不多就行
作者: Nsqs    时间: 2023-8-26 01:56

本帖最后由 Nsqs 于 2023-8-26 11:22 编辑

[console]::WriteLine("aaa")|gm 至于你这个,这句也好理解为什么会报错

其实这个问题你问的其实是在PowerShell中关于write-host和write-output的区别,他们都可以输出数据,可作用是什么?

通常情况下,write-output具备传递数据给管道符右边的函数/命令使用(具备管道结构的数据处理)
"hello world" 会被当作是Write-output,因为在PowerShell里不写Write-output也是可以的,可以省略.(也不会报错)

包括数字部分比如

100|gm
或者
(1,2,3)|gm

这里需要注意的是括号的使用,刚刚我在前面几楼里有提到过()括号的作用,直接写成1,2,3|gm,那么只有3会被gm做处理.

同理,写成
echo 123|gm 也可以
或者
write 123|gm
这是为什么?会有这么多函数都可以用?
答:
一个函数/命令可以存在多个别名
拿这个代码运行一下就知道了
(alias).DisplayName|?{$_ -match 'write-output'}

关于别名的事,我就不提了,要还不懂的话,你真该好好的去把PowerShell整个基础篇都再过一过.

随后是write-host,只具备简单的输出文本的内容,而不具备管道输出的能力.因此

当你写成write-host 123|gm时会报错,既然如此,那么你用[console]::WriteLine("aaa")|gm,当然就达不到你想用的作用了.

总结:
PowerShell的Write-Host和Write-Output两个命令有以下区别:
具备管道输出的作用
write-output [√]
write-host    [×]

另外提到一点,他们同时具备管道输入的作用因此:
具备管道输入的作用
write-output [√]
write-host    [√]

例1:
  1. #下面是一段关于管道的自定义函数使用方法
  2. function test1{
  3.     param(
  4.     [Parameter(ValueFromPipeline=$true)]
  5.     $param1,
  6.     $param2
  7.     )
  8.     $param1*$param2
  9. }
  10. 100|test1 -param2 2
  11. # 这里就不得不提到自定义函数的使用方法了,在function里当ValueFromPipeline的属性为$true后,则表示开启管道输入模式
  12. # 上面代码返回200
复制代码
例2:
  1. 但当我关闭ValueFromPipeline或者不使用它
  2. function test2{
  3.     param(
  4.     #[Parameter(ValueFromPipeline=$false)] 或者这句去掉
  5.     $param1,
  6.     $param2
  7.     )
  8.     $param1*$param2
  9. }
  10. 100|test2 -param2 2 #???没有返回值?
  11. # 这是因为$param1已经不具备管道输入的作用了,因此$param1现在的情况是默认值为=$null
  12. # 因为$param1没有声明变量类型,当没有做赋值操作时默认值为$null
复制代码
  1. #如果你不清楚这里的原因可以看下面的代码是同理哦
  2. test2 -param1 $null -param2 2
复制代码
例3:
  1. # 为了解决这个问题,可以写成下面这样
  2. function test3{
  3.     param(
  4.     #[Parameter(ValueFromPipeline=$false)] 或者这句去掉
  5.     [int]$param1,
  6.     $param2
  7.     )
  8.     $param1*$param2
  9. }
  10. 100|test3 -param2 2
  11. test3 -param1 $null -param2 2
  12. test3 -param2 2
复制代码
例4:
  1. # 这些是题外话,就稍微多说一点.我们来看看模拟一个write-host的输出情况
  2. function Write-Test{
  3.     param(
  4.         [Parameter(ValueFromPipeline)]
  5.         [object]$InputObject,
  6.         [System.ConsoleColor]$ForegroundColor
  7.     )
  8.     [System.Console]::ForegroundColor=$ForegroundColor
  9.     [System.Console]::WriteLine($InputObject)
  10. }
  11. 'hello world'|Write-Test -ForegroundColor Red # 现在,你可以这样写,因为Write-Host也具备管道输入的作用
  12. # 但因为最后一行代码用的是 WriteLine 则不具备输出的作用,因此我们看看下面这段代码
  13. 'hello world'|Write-Test|gm
  14. # 这里你就会发现它提示 gm : 必须为 Get-Member cmdlet 指定一个对象。
  15. # 这是因为gm输入的数据类型必须是一个 object 类型,而我们用到了 WriteLine. WriteLine 是知道的,就在之前我有说过,只具备输入而不具备输出的功能
  16. # 因此这里 Write-Test 压根就没有返回值,所以就报错了,说明当前环境下 Write-Test 是一个无返回值的函数,虽然他具备输出文本的作用
  17. # 所以 WriteLine 仅只显示文本,而不具备拥有返回值的能力
  18. # 切记 [System.Console]::WriteLine
  19. # 不可以写成
  20. # "hello world"|[System.Console]::WriteLine 的哦
  21. # 因为 [System.Console]::WriteLine 是 .net 上的方法
  22. # .net 上的方法不具备管道符的功能,因为他们不属于 PowerShell 内部的语法结构
复制代码

作者: Nsqs    时间: 2023-8-26 02:03

关于
gm -i [int]

[int]|gm


首先你又犯了一个低级的错误
[int]没打括号,是会被看作是一个字符串,不信你试试
  1. gm -i ""
复制代码
  1. gm -i "hello"
复制代码
其次尽量不要用参数缩写或偷懒的方法,因为你写的参数可能在别人看来会看不懂,不利于理解

下面那个[int]|gm 没有报错是因为[int]本身就是一个object类型
不信你可以看
  1. [int].GetType()
复制代码

作者: Nsqs    时间: 2023-8-26 03:08

本帖最后由 Nsqs 于 2023-8-26 11:52 编辑

关于
gm -i {echo "aaa"}

{echo "aaa"}|gm


这里就不得不提到 scriptblock 代码块结构了
如果不做加工处理直接写出来的代码块必须用 & 来调用, & 相当于是bat里的call

因为{}又是一种新的用法,叫代码块
它与bat里的(代码块) 不同
也与PowerShell 里的 @(代码块不同)

@(是一组常规的语法表达式)
{是一种特殊类型,关于他的使用方法实在有点太多了,进阶的用法数不胜数,还不是你目前可以掌握的}
通常一般情况下{}是将一行或多行代码作为代码块,也就是将这个部分的代码作为常规的代码部分处理

举个例子:
  1. {$a=1;$a}
  2. $a
复制代码
# 在上述例子中,我们会发现$a不能正常使用,通常情况下它应该返回$a=1,所以是不是看起来很奇怪?
# 那么我们检查一下它的类型看看
  1. $a.GetType()
复制代码
# 发现还会报错? 同样的 $a|gm 也会报错
# 代码块中的代码是在一个local 局部域中,因此单独在第二行的$a是一个空的数据,$a的值为null,而gm无法输入null才会导致报错
  1. {
  2. $a='hello world'
  3. $a
  4. }
复制代码
  1. # 由于我们知道在 PowerShell 中可以直接省略write-output,因此可以直接写$a也是可以拥有返回值的
  2. # 因此更为严谨的写法可以是这样
  3. {
  4. $a='hello world'
  5. Write-Output $a
  6. }
复制代码
如果我们想得到正确的返回值,那么接下来我们再看几个例子:

例1:
  1. {
  2. $a='hello world'
  3. $a
  4. }
  5. # 是不是很奇怪?直接运行没有效果
  6. # 这是因为单独的自定义代码块是不具备输出能力的
  7. # 就好比我做了一个软件,但我电脑没开机,是用不了的
复制代码
  1. & {
  2. $a='hello world'
  3. $a
  4. }
  5. # 为什么加了 & 后能够产生返回值呢?
  6. # 答: & 就好比bat中的 call 命令
  7. # 是调用的意思
  8. # 注意! & 是一个特殊符号,是一个特殊用法,不能直接用 call 比如把bat里的 call 拿过来照搬是不行的
  9. <#
  10. call {
  11. $a='hello world'
  12. $a
  13. }
  14. #>
复制代码
例2:
  1. # 在使用下面这些案例之前,建议关闭 ISE,并重新启动ISE,再使用下面的代码来测试(原因是因为可能会和上面的测试例子变量发生作用域的冲突)
  2. # 当我们学会使用 & 后,我们真正掌握使用它的方法,比如我们好好的研究一下它还有一个特殊的机制: 作用域
  3. & {
  4. $a='hello world'
  5. }
  6. $a
  7. # 以上代码调试后发现$a根本没有返回值,这可咋办?
  8. # 答: 在{}中的代码只能在{}里使用,或在{}当前域中使用,在{}以外的地方则不能直接读取{}局部域中的变量和数据
复制代码
  1. # 如果我们想要得到 $a 的值,应该改变作用域,用$Global:设为全局模式
  2. & {
  3. $Global:a='hello world'
  4. }
  5. $a
复制代码
  1. # 关于作用域有以下 3 种参数使用:
  2. <#
  3.     $Global:
  4.     $Local:
  5.     $Private:
  6. #>
  7. # 以上代码按从高到低来排序他们的使用先后顺序和级别,Global是级别最高的,因此可以在整个脚本存活的生命周期中使用
  8. # Private是最低的,也是用的最少的,通常情况下,我们的代码如果是写在局部区域,例如变量的赋值,那么大多默认情况都是 Local 局部域
  9. # 正因如此,我们才可以不需要显式声明$Local:变量名,因为 PowerShell 已经自动为我们声明为局部变量了(如果是在括号内,在括号外会自动为我们声明为 Global)
复制代码
例3:
  1. <#
  2.     $Global:
  3.     $Local:
  4.     $Private:
  5. #>
  6. # 有关于在它们三者的使用方法,我简单举个例吧,比如:
  7. $C='在水果店'
  8. & { ;$Local:A='小明' ;&{ $Private:B=10};  "$A $C 花了 $B 购买了苹果"}
  9. & { ;$Local:A='小明' ;&{ $Private:B=10;   "$A $C 花了 $B 购买了苹果"}}
  10. & { ;$Local:A='小明' ; $Private:B=10;  & {"$A $C 花了 $B 购买了苹果"}}
  11. # 在以上三个例子中,变量 $C 是一个 Global 类型 (因为它是在外面赋值的,所以可以在多层嵌套的{}括号中才可以一直都能有返回值)
  12. # 在上面三个例子中直到最后一个例子可以得出一个结论: Local 无论在多少层内,只要它在上一级域中赋值过,那么在下一级域中都可以得到返回值,但不能出现下面这种情况
  13. & { & {$Local:A='小明' }; $Private:B=10;  & {"$A $C 花了 $B 购买了苹果"}}
复制代码
  1. & { & {$Local:A='晴天'}; "今天的天气是 $A" }
  2. & { & { & {$Local:A='晴天'}}; "今天的天气是 $A" }
复制代码
  1. & { & {$Local:A='暴雨'<# 这是二级域 #>; & { "这是三级域: 今天的天气是 $A" } }; "这是一级域: $A" }
复制代码
  1. <#
  2.     $Global:
  3.     $Local:
  4.     $Private:
  5. #>
  6. '①'
  7. & { & {$Private:A='暴雨';Write-Host $A <# 这是二级域 #>; & { "这是三级域: 今天的天气是 $A" } }; "这是一级域: $A" }
  8. # 为了更直观显示,我用了write-host
  9. '②'
  10. & { & {$Private:A='暴雨';Write-Host $A <# 这是二级域 #>; & { $Private:A='暴雨';Write-Host $A <# "这是三级域 #> } }; "这是一级域: $A" }
  11. # 或
  12. '③'
  13. & { & {"这是二级域: 今天的天气是 $A"; & { $Private:A='暴雨';Write-Host $A <# "这是三级域 #> } }; "这是一级域: $A" }
复制代码
例4:
# ①
  1. function test1{
  2.     function test2{
  3.         $Private:A='小明'        
  4.     }
  5.     test2
  6.     $A   
  7. }
  8. test1
  9. # 通过在以上示例的演示,我们发现 $A 是没有正确的返回的,同时也验证了一点,Private的使用公开权限是最低的
复制代码
# ②
  1. # 现在我们换成 Local 继续测试
  2. function test1{
  3.     function test2{
  4.         $Local:A='小明'        
  5.     }
  6.     test2
  7.     $A   
  8. }
  9. test1
复制代码
  1. # 但不一样的地方在于,Local可以在上一层域中赋值后在下一层中被正确获取到返回值
  2. function test1{
  3.     $Local:A='小明'
  4.     function test2{
  5.         $A
  6.     }
  7.     test2
  8. }
  9. test1
复制代码
  1. # 像这种情况 Private 则不具备与 Local 一样的公开级别
  2. function test1{
  3.     $Private:A='小明'
  4.     function test2{
  5.         $A
  6.     }
  7.     test2
  8. }
  9. test1
复制代码
最后关于代码块还有一种很少见的使用方法:

非常规的使用方法
  1. begin{
  2. # 开始写关于你的...
  3. }
  4. process{
  5. # 代码中途的过程...
  6. }
  7. End{
  8. # 结束段的代码块...
  9. }
  10. # 以上的一般情况都应用在 function 里,也有一部分更进阶的使用方法,就不一一赘述了
复制代码

作者: Nsqs    时间: 2023-8-26 03:29

本帖最后由 Nsqs 于 2023-8-26 10:49 编辑
  1. "aaa`r`nbbb`r`nccc" -replace "`n",""
复制代码
关于这个是Windows很底层的东西了.`r是回车`n是换行,在Windows上两者缺一不可,缺一个都可能会导致数据结构错乱,以至于实际工作当中,出现不必要的bug或麻烦

你的这个例子中,并不能说明什么,也不能研究个所以然来,这种情况下我可以理解为你的基本功不扎实

关于你最后那个问题,\和`其实不冲突,一点也不麻烦,如果你觉得PowerShell学习起来很困难,那只能说明你没有足够的耐心去了解它

\是正则里面的转义用法,而`是PowerShell里的转义用法

两者都会有需要用到的时候,在需要用正则的时候,肯定是用\,而不使用正则的时候只能用`,但是一般在用正则的时候是肯定也可以用`的,比如
  1. "aaa`tbbb" -replace "`t",','
复制代码
当然你也可以直接用正则的转义符
  1. "aaa`tbbb" -replace "\t",','
复制代码
我个人是喜欢直接用\,因为写代码惯用手是右手,会方便很多
另外前面的 replace 第一参 里不具备\转义能力(切记)


补充一下:
PowerShell学习过程是不会繁琐的,取决于你有没有找到合适的资料或者合适的学习方式,只要找准方法和方向,那么学习起来也可以进步神速,就我个人而言,也只花了最多半个月时间就能把大部分基础语法全部吃透
再花几个月或半年一年的功夫去吃透PowerShell的命令以及.net上的对象和方法
作者: Nsqs    时间: 2023-8-26 03:38

关于一楼代码,我测试基本上300G的硬盘不出30秒也能统计出来,速度方面还是可以的(靠谱)




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