¿Cuál es el estilo de codificación recomendado para PowerShell?

79

¿Existe algún estilo de codificación recomendado sobre cómo escribir scripts de PowerShell?

No se trata de cómo estructurar el código (cuántas funciones, si usar el módulo, ...). Se trata de ' cómo escribir el código para que sea legible '.

En los lenguajes de programación hay algunos estilos de codificación recomendados (qué sangrar , cómo sangrar: espacios / tabulaciones, dónde hacer una nueva línea , dónde poner llaves , ...), pero no he visto ninguna sugerencia para PowerShell.

Me interesa particularmente:


Cómo escribir parámetros

function New-XYZItem
  ( [string] $ItemName
  , [scriptblock] $definition
  ) { ...

(Veo que es más como sintaxis 'V1')

o

function New-PSClass  {
  param([string] $ClassName
       ,[scriptblock] $definition
  )...

o (¿por qué agregar un atributo vacío?)

function New-PSClass  {
  param([Parameter()][string] $ClassName
       ,[Parameter()][scriptblock] $definition
  )...

o (otro formato que vi tal vez en el código de Jaykul)

function New-PSClass {
  param(
        [Parameter()]
        [string]
        $ClassName
        ,
        [Parameter()]
        [scriptblock]
        $definition
  )...

o ...?


Cómo escribir una canalización compleja

Get-SomeData -param1 abc -param2 xyz | % {
    $temp1 = $_
    1..100 | % {
      Process-somehow $temp1 $_
    }
  } | % {
    Process-Again $_
  } |
  Sort-Object -desc

o (nombre del cmdlet en la nueva línea)

Get-SomeData -param1 abc -param2 xyz |
  % {
    $temp1 = $_
    1..100 |
      % {
        Process-somehow $temp1 $_
      }
  } |
  % {
    Process-Again $_
  } |
  Sort-Object -desc |

¿Y si existen -begin, -processy -endlos parámetros? ¿Cómo lo hago más legible?

Get-SomeData -param1 abc -param2 xyz |
  % -begin {
     init
  } -process {
     Process-somehow2 ...
  } -end {
     Process-somehow3 ...
  } |
  % -begin {
  } ....

o

Get-SomeData -param1 abc -param2 xyz |
  %  `
    -begin {
      init
    } `
    -process {
      Process-somehow2 ...
    } `
    -end {
      Process-somehow3 ...
    } |
  % -begin {
  } ....

La sangría es importante aquí y también qué elemento se coloca en la nueva línea.


He cubierto solo las preguntas que me vienen a la mente con mucha frecuencia. Hay algunos otros, pero me gustaría que esta pregunta de Stack Overflow sea 'breve'.

Cualquier otra sugerencia es bienvenida.

stej
fuente
1
Supongo que la falta de un estilo de codificación común para los scripts de PowerShell se debe al hecho de que está más relacionado con el uso administrativo que con la codificación "real".
Filburt
4
Tienes razón. Sin embargo, en mi humilde opinión, los administradores necesitan scripts que sean fáciles de leer. Por ejemplo, no me gustan las comillas invertidas, así que trato de evitarlas.
stej
2
Esta es una gran pregunta.
Steve Rathbone
Es extraño que, a pesar de los montones de desprecio, siga siendo un código; y por lo tanto, se puede hacer más legible con un esquema de sangría estándar y familiar.
user2066657

Respuestas:

88

Después de pasar un par de años sumergiéndome bastante en PowerShell v2.0, esto es lo que me decidí:

<#
.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 {
        $_
    }
}

El resaltador de sintaxis de Stack Overflow me está abandonando por completo. Péguelo en el ISE.

Richard Berg
fuente
Gracias por la respuesta integral; Probablemente lo marcaré como respuesta aceptada, parece que nadie más está interesado en el estilo de codificación Posh: | ¿Ha publicado en algún lugar sus funciones de ayuda (??,?:,? +, Im, ...)? - Creo que sería valioso para muchas personas;)
stej
No, no lo he hecho ... sí, debería ... ¡uno de estos días ...!
Richard Berg
3
Ok, comprometido v0.1 en algún lugar público. Vaya a tfstoys.codeplex.com/SourceControl/changeset/view/33350#605701 y busque Módulos \ RichardBerg-Misc
Richard Berg
Un punto para agregar a esa gran guía: ¡use validadores donde sea necesario! Ahorran en código y mejoran la usabilidad.
JasonMArcher
El script de implementación de un colega se me rompió una vez porque tenía un alias 'ls' personalizado en mi perfil. Desde entonces, mi práctica ha sido "no usar alias en los guiones"
John Fouhy
14

Creo que el recurso de estilo de codificación más completo para PowerShell sigue siendo la Guía de estilo y prácticas recomendadas de PowerShell .

De su introducción:

Al igual que las reglas de ortografía y gramática en inglés, las mejores prácticas de programación de PowerShell y las reglas de estilo casi siempre tienen excepciones, pero estamos documentando una línea de base para la estructura del código, el diseño de comandos, la programación, el formato e incluso el estilo que lo ayudará a evitar problemas comunes y ayuda. escribe un código más legible y reutilizable, porque el código reutilizable no tiene que ser reescrito y se puede mantener el código legible.

También pusieron a disposición estos enlaces de GitBook :

rsenna
fuente
404: Los enlaces están rotos.
Ashish Singh
Fijo. Esta nueva guía se creó fusionando la antigua Guía de estilo de PowerShell , de Carlos Perez (que originalmente vinculé) y el Libro de la comunidad de prácticas de PowerShell , de Don Jones y Matt Penny.
rsenna
4
Esta respuesta realmente debería ser más alta ahora.
Bocados de tocino
8

Recientemente encontré un punto excelente sobre el estilo de sangría en PowerShell . Como dice el comentario vinculado, observe la diferencia entre estas mismas sintaxis:

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

y

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

Si bien mi inclinación es "hacer lo que hacen los romanos" y usar el estilo de sangría estándar de C # ( Allman , más o menos), discrepo con esta excepción y otras similares.

Esto me inclina personalmente a usar mi 1TBS favorito , pero podría estar convencido de lo contrario. ¿Cómo te conformaste, por curiosidad?

Tohuw
fuente
2
Soy bastante nuevo en pijo. ¡Gracias por el aviso! Al principio me gustó la línea separada, pero ahora me gusta abrir rizado en la línea de configuración.
AnneTheAgile
Puede encontrar animosidad por parte de los codificadores de .NET que usan el estándar de C #, pero cuando la sangría cambia de función, prefiero lo que hará consistentemente lo que se espera sobre cualquier preferencia religiosa, en cualquier momento. Tiendo a preferir 1TBS para todo, pero si el ejemplo anterior exhibiera un comportamiento inverso, todos mis PoSh serían Allman-izados en un abrir y cerrar de ojos. :)
Tohuw
Cuidado, estás combinando estilo con comportamiento.
Keith S Garner
@KeithSGarner más bien, estoy insinuando que el comportamiento debería dictar el estilo. O mejor, el lenguaje debería ser independiente del estilo.
Tohuw
1
Cuando se trata del ejemplo if (& lt; test & gt;) {StatementBlock} , el lenguaje permite cualquiera de los estilos (1TBS o Allman), no es un problema de comportamiento. (Yo prefiero Allman mí mismo, pero "Cuando en Roma ...") En cuanto al ejemplo anterior Sort-Object, que es no una cuestión de estilo, sólo hay una respuesta correcta en función del comportamiento requerido. Style! = Behavior
Keith S Garner