有没有推荐的编码风格如何编写PowerShell脚本?
不关于如何构造代码(多少函数,如果使用模块,......)。它是关于'如何编写代码以便它可读'。
在编程语言中有一些推荐的编码样式(缩进的内容,如何缩进 - 空格/制表符,在哪里制作新行 >,在哪里放大括号,...),但我还没有看到任何有关PowerShell的建议。
我特别感兴趣:
如何编写参数
function New-XYZItem
( [string] $ItemName
, [scriptblock] $definition
) { ...
(我发现它更像是'V1'语法)
或
function New-PSClass {
param([string] $ClassName
,[scriptblock] $definition
)...
或(为什么要添加空属性?)
function New-PSClass {
param([Parameter()][string] $ClassName
,[Parameter()][scriptblock] $definition
)...
或(我在Jaykul的代码中看到的其他格式)
function New-PSClass {
param(
[Parameter()]
[string]
$ClassName
,
[Parameter()]
[scriptblock]
$definition
)...
或......?
如何编写复杂的管道
Get-SomeData -param1 abc -param2 xyz | % {
$temp1 = $_
1..100 | % {
Process-somehow $temp1 $_
}
} | % {
Process-Again $_
} |
Sort-Object -desc
或(新行上的cmdlet名称)
Get-SomeData -param1 abc -param2 xyz |
% {
$temp1 = $_
1..100 |
% {
Process-somehow $temp1 $_
}
} |
% {
Process-Again $_
} |
Sort-Object -desc |
如果有-begin
,-process
和-end
参数怎么办?如何使其最具可读性?
Get-SomeData -param1 abc -param2 xyz |
% -begin {
init
} -process {
Process-somehow2 ...
} -end {
Process-somehow3 ...
} |
% -begin {
} ....
或
Get-SomeData -param1 abc -param2 xyz |
% `
-begin {
init
} `
-process {
Process-somehow2 ...
} `
-end {
Process-somehow3 ...
} |
% -begin {
} ....
缩进在这里很重要,并且在新行上放置了什么元素。
我只是经常提到我脑海中浮现的问题。还有其他一些,但我想保持这个Stack Overflow问题“简短”。
欢迎任何其他建议。
答案 0 :(得分:86)
在花了几年时间深入研究PowerShell v2.0之后,我已经解决了这个问题:
<#
.SYNOPSIS
Cmdlet help is awesome. Autogenerate via a template so I never forget.
.DESCRIPTION
.PARAMETER
.PARAMETER
.INPUTS
.OUTPUTS
.EXAMPLE
.EXAMPLE
.LINK
#>
function Get-Widget
{
[CmdletBinding()]
param (
# Think about which parameters users might loop over. If there is a clear
# favorite (80/20 rule), make it ValueFromPipeline and name it InputObject.
[parameter(ValueFromPipeline=$True)]
[alias("Server")]
[string]$InputObject,
# All other loop candidates are marked pipeline-able by property name. Use Aliases to ensure the most
# common objects users want to feed in will "just work".
[parameter(Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$True)]
[alias("FullName")]
[alias("Path")]
[string[]]$Name,
# Provide and document defaults for optional parameters whenever possible.
[parameter(Position=1)]
[int]$Minimum = 0,
[parameter(Position=2)]
[int]$ComputerName = "localhost",
# Stick to standardized parameter names when possible. *Especially* with switches. Use Aliases to support
# domain-specific terminology and/or when you want to expose the parameter name of the .Net API you're wrapping.
[parameter()]
[Alias("IncludeFlibbles")]
[switch]$All,
)
# The three main function blocks use this format if & only if they are short one-liners
begin { $buf = new-list string }
# Otherwise they use spacing comparable to a C# method
process
{
# Likewise, control flow statements have a special style for one-liners
try
{
# Side Note: internal variables (which may be inherited from a parent scope)
# are lowerCamelCase. Direct parameters are UpperCamelCase.
if ($All)
{ $flibbles = $Name | Get-Flibble }
elseif ($Minimum -eq 0)
{ $flibbles = @() }
else
{ return }
$path = $Name |
? { $_.Length -gt $Minimum } |
% { $InputObject.InvokeGetAPI($_, $flibbles) } |
ConvertTo-FullPath
}
finally { Cleanup }
# In general, though, control flow statements also stick to the C# style guidelines
while($true)
{
Do-Something
if ($true)
{
try
{
Do-Something
Do-Something
$buf.Add("abc")
}
catch
{
Do-Something
Do-Something
}
}
}
}
}
<#
Pipelines are a form of control flow, of course, and in my opinion the most important. Let's go
into more detail.
I find my code looks more consistent when I use the pipeline to nudge all of PowerShell's supported
language constructs (within reason) toward an "infix" style, regardless of their legacy origin. At the
same time, I get really strict about avoiding complexity within each line. My style encourages a long,
consistent "flow" of command-to-command-to-command, so we can ensure ample whitespace while remaining
quite compact for a .NET language.
Note - from here on out I use aliases for the most common pipeline-aware cmdlets in my stable of
tools. Quick extract from my "meta-script" module definition:
sal ?? Invoke-Coalescing
sal ?: Invoke-Ternary
sal im Invoke-Method
sal gpv Get-PropertyValue
sal spv Set-PropertyValue
sal tp Test-Path2
sal so Select-Object2
sal eo Expand-Object
% and ? are your familiar friends.
Anything else that begins with a ? is a pseudo-infix operator autogenerated from the Posh syntax reference.
#>
function PipelineExamples
{
# Only the very simplest pipes get to be one-liners:
$profileInfo = dir $profile | so @{Path="fullname"; KBs={$_.length/1kb}}
$notNull = $someString | ?? ""
$type = $InputObject -is [Type] | ?: $InputObject $InputObject.GetType()
$ComObject | spv Enabled $true
$foo | im PrivateAPI($param1, $param2)
if ($path | tp -Unc)
{ Do-Something }
# Any time the LHS is a collection (i.e. we're going to loop), the pipe character ends the line, even
# when the expression looks simple.
$verySlowConcat = ""
$buf |
% { $verySlowConcat += $_ }
# Always put a comment on pipelines that have uncaptured output [destined for the caller's pipeline]
$buf |
? { $_ -like "*a*" }
# Multi-line blocks inside a pipeline:
$orders |
? {
$_.SaleDate -gt $thisQuarter -and
($_ | Get-Customer | Test-Profitable) -and
$_.TastesGreat -and
$_.LessFilling
} |
so Widgets |
% {
if ($ReviewCompetition)
{
$otherFirms |
Get-Factory |
Get-ManufactureHistory -Filter $_ |
so HistoryEntry.Items.Widgets
}
else
{
$_
}
} |
Publish-WidgetReport -Format HTML
# Mix COM, reflection, native commands, etc. seamlessly
$flibble = Get-WmiObject SomethingReallyOpaque |
spv AuthFlags 0xf -PassThru |
im Put() -PassThru |
gpv Flibbles |
select -first 1
# The coalescing operator is particularly well suited to this sort of thing
$initializeMe = $OptionalParam |
?? $MandatoryParam.PropertyThatMightBeNullOrEmpty |
?? { pwd | Get-Something -Mode Expensive } |
?? { throw "Unable to determine your blahblah" }
$uncFolderPath = $someInput |
Convert-Path -ea 0 |
?? $fallback { tp -Unc -Folder }
# String manipulation
$myName = "First{0} Last{1} " |
?+ "Suffix{2}" |
?replace "{", ": {" |
?f {eo richard berg jr | im ToUpper}
# Math algorithms written in this style start to approach the elegance of functional languages
$weightedAvg = $values |
Linq-Zip $weights {$args[0] * $args[1]} |
Linq-Sum |
?/ ($weights | Linq-Sum)
}
# Don't be afraid to define helper functions. Thanks to the script:Name syntax, you don't have to cram them into
# the begin{} block or anything like that. Name, parameters, etc don't always need to follow the cmdlet guidelines.
# Note that variables from outer scopes are automatically available. (even if we're in another file!)
function script:Cleanup { $buf.Clear() }
# In these small helpers where the logic is straightforward and the correct behavior well known, I occasionally
# condense the indentation to something in between the "one liner" and "Microsoft C# guideline" styles
filter script:FixComputerName
{
if ($ComputerName -and $_) {
# Handle UNC paths
if ($_[1] -eq "\") {
$uncHost = ($_ -split "\\")[2]
$_.Replace($uncHost, $ComputerName)
} else {
$drive = $_[0]
$pathUnderDrive = $_.Remove(0,3)
"\\$ComputerName\$drive`$\$pathUnderDrive"
}
} else {
$_
}
}
Stack Overflow的语法高亮显示器完全放弃了我。将其粘贴到ISE中。
答案 1 :(得分:13)
我相信PowerShell最全面的编码风格资源仍然是The PowerShell Best Practices and Style Guide。
从他们的介绍:
与英语拼写和语法规则一样,PowerShell编程最佳实践和样式规则几乎总是有例外,但我们正在记录代码结构,命令设计,编程,格式甚至样式的基线,这将有助于您避免常见问题,并帮助您编写更多可重用,可读的代码 - 因为不需要重写可重用代码,并且可以维护可读代码。
他们还提供了这些GitBook链接:
答案 2 :(得分:8)
我最近遇到了an excellent point about indent style in PowerShell。如链接注释所述,请观察这些相同语法之间的区别:
1..10 | Sort-Object
{
-$_
}
和
1..10 | Sort-Object {
-$_
}
虽然我倾向于“像罗马人那样做”并且使用标准的C#缩进样式(Allman,或多或少),但我对这个例外以及与之类似的其他例子有疑问。
这使我个人倾向于使用我喜欢的1TBS,但我可以说服不然。出于好奇,你是怎么解决的?