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

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

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


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

다른 제안은 환영합니다.



답변

몇 년 동안 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에 붙여 넣습니다.


답변

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

그들의 소개에서 :

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

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


답변

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

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

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

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

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


답변