PowerShell에 권장되는 코딩 스타일은 무엇입니까?


79

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
  )...

또는 (제이 쿨의 코드에서 본 다른 형식)

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 {
  } ....

여기서 들여 쓰기는 중요하며 새 줄에 어떤 요소가 추가되는지도 중요합니다.


나는 매우 자주 떠오르는 질문들만 다루었습니다. 다른 것들이 있지만이 스택 오버플로 질문을 '짧게'유지하고 싶습니다.

다른 제안은 환영합니다.


2
powershell 스크립트에 대한 일반적인 코딩 스타일이없는 것은 "실제"코딩이 아닌 관리자 사용과 더 관련이 있기 때문이라고 생각합니다.
Filburt 2010 년

5
당신이 옳습니다. 그러나 imho 관리자는 읽기 쉬운 스크립트가 필요합니다. 예를 들어 저는 백틱이 마음에 들지 않으므로 피하려고합니다.
stej

2
이것은 좋은 질문입니다.
Steve Rathbone 2013

많은 경멸에도 불구하고 여전히 코드라는 것은 이상합니다. 따라서 표준적이고 친숙한 들여 쓰기 방식으로 더 읽기 쉽게 만들 수 있습니다.
user2066657 14:52에

답변:


89

몇 년 동안 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
포괄적 인 답변에 감사드립니다. 나는 아마도 그것을 받아 들여진 대답으로 표시 할 것이다. 다른 누구도 Posh 코딩 스타일에 관심이없는 것 같다 : | 도우미 함수 (??,? :,? +, im, ...)를 어딘가에 게시 했습니까? -내 생각에 많은 사람들에게 가치가있을 것입니다;)
stej

아뇨 .. 그래요 ... 요즘 ...!
Richard Berg

3
좋아, 공개 된 곳에서 v0.1을 커밋했습니다. tfstoys.codeplex.com/SourceControl/changeset/view/33350#605701로 이동하여 Modules \ RichardBerg-Misc로 이동합니다.
Richard Berg

이 훌륭한 가이드에 추가 할 요점 : 필요한 곳에 유효성 검사기를 사용하십시오! 그들은 코드를 절약하고 사용성을 향상시킵니다.
JasonMArcher

내 프로필에 사용자 지정 'ls'별칭이 있었기 때문에 동료의 배포 스크립트가 한 번 고장났습니다. 이제까지 내 연습하고 "스크립트에서 별칭을 사용하지 않는"했다, 이후
존 포우 히

15

PowerShell에 대한 가장 포괄적 인 코딩 스타일 리소스는 여전히 PowerShell 모범 사례 및 스타일 가이드 라고 생각합니다 .

그들의 소개에서 :

영어 철자 및 문법 규칙과 마찬가지로 PowerShell 프로그래밍 모범 사례 및 스타일 규칙에는 거의 항상 예외가 있지만, 일반적인 문제를 피하고 도움을 줄 수있는 코드 구조, 명령 디자인, 프로그래밍, 서식 및 스타일에 대한 기준선을 문서화하고 있습니다. 재사용 가능하고 읽기 쉬운 코드를 작성할 수 있습니다. 재사용 가능한 코드를 다시 작성할 필요가없고 읽기 쉬운 코드를 유지할 수 있기 때문입니다.

또한 다음 GitBook 링크를 사용할 수 있도록했습니다.


404 : 링크가 끊어졌습니다.
Ashish Singh

결정된. 이 새 가이드는 Carlos Perez (원래 연결) 의 이전 PowerShell 스타일 가이드 와 Don Jones 및 Matt Penny 의 The Community Book of PowerShell Practices 를 병합하여 작성되었습니다 .
rsenna

4
이 대답은 지금 더 높아야합니다.
Bacon Bits

8

최근 에 PowerShell의 들여 쓰기 스타일에 대한 훌륭한 점을 발견 했습니다 . 링크 된 주석에서 언급했듯이 다음과 같은 동일한 구문의 차이점을 관찰하십시오.

1..10 | Sort-Object
{
    -$_
}

1..10 | Sort-Object {
    -$_
}

내 경향은 "로마인처럼 수행"하고 표준 C # 들여 쓰기 스타일 ( Allman , 다소간)을 ,이 예외 및 이와 유사한 다른 문제가 발생합니다.

이것은 개인적으로 내가 선호하는 1TBS 를 사용하는 경향 이 있지만 그렇지 않으면 확신 할 수 있습니다. 호기심 때문에 어떻게 정착 했습니까?


2
나는 포쉬에 꽤 익숙하다. 충고 고맙습니다! 처음에는 별도의 줄이 마음에 들었지만 이제는 설정 줄에서 곱슬을 여는 것과 같습니다.
AnneTheAgile

C #의 표준을 사용하는 .NET 코더로부터 적대감을 느낄 수 있지만 들여 쓰기가 기능을 변경하면 언제든지 종교적 선호도에 대해 기대되는 것을 일관되게 수행 할 것입니다. 나는 모든 것에 대해 1TBS를 선호하는 경향이 있지만, 위의 예가 반대 동작을 나타내면 모든 PoSh는 하트 비트로 Allman-ized가 될 것입니다. :)
Tohuw

1
@KeithSGarner는 오히려 Behavior가 Style을 지시해야 함을 암시하고 있습니다. 또는 더 나은 언어는 스타일에 구애받지 않아야합니다.
Tohuw

1
if (& lt; test & gt;) {StatementBlock} 예제를 다룰 때 언어는 두 스타일 (1TBS 또는 Allman)을 허용하지만 Behavior 문제가 아닙니다. (나는 Allman을 선호하지만 "When in Rome ...") 위의 Sort-Object 예제 는 스타일 문제 가 아니며 필요한 동작에 따라 하나의 정답 만 있습니다. Style! = Behavior
Keith S Garner

1
"필요한 행동에 따라 정답은 하나뿐입니다." 아마도 제가 뭔가를 놓치고있는 것 같습니다. 그게 바로 제가 의미하는 바입니다.이 작업을 수행하는 올바른 방법은 단 하나뿐입니다. 따라서 명령을 구성하는 방법은 필요한 출력에 따라 결정됩니다. 이것은 스타일 선택 (즉, 줄 바꿈이있는 위치)에 영향을 미칠 수있는 바로 그 종류에 영향을 미치기 때문에, 제가 선호하는 스타일 선택은 예기치 않은 동작을 일으킬 가능성이없는 것을 선택하는 것입니다. 원하지 않는 언어 동작을 피하기 위해 선택한 다른 스타일과 다르지 않다고 생각합니다.
Tohuw 2015
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.