web-dev-qa-db-fra.com

Canaliser des objets de tableau complets au lieu d'éléments de tableau un par un?

Comment envoyez-vous la sortie d'un CmdLet au suivant dans un pipeline en tant qu'objet tableau complet au lieu des éléments individuels du tableau un par un?

Le problème - Description générique
Comme on peut le voir dans l'aide pour about_pipelines (help pipeline) powershell envoie les objets un par un dans le pipeline¹. Donc Get-Process -Name notepad | Stop-Process envoie un processus à la fois dans le tuyau.

Disons que nous avons un CmdLet (Do-SomeStuff) tiers qui ne peut être modifié ou changé d'aucune façon. Do-SomeStuff fonctionne différemment s'il reçoit un tableau de chaînes ou s'il reçoit un objet chaîne unique.

Do-SomeStuff n'est qu'un exemple, il pourrait remplacer ForEach-Object, Select-Object, Write-Host (ou tout autre CmdLet acceptant une entrée de pipeline)

Dans cet exemple, Do-SomeStuff traitera les éléments individuels du tableau un par un.

$theArray = @("A", "B", "C")
$theArray | Do-SomeStuff

Si nous voulons envoyer le tableau complet en un seul objet à Do-SomeStuff, on peut essayer quelque chose comme ça

@($theArray) | Do-SomeStuff

Mais cela ne produit pas le résultat attendu car PowerShell "ignore" le nouveau tableau à un seul élément.

Alors, comment voulez-vous "forcer" $theArray à transmettre dans le canal en tant qu'objet tableau unique au lieu des éléments de contenu un à la fois?


Le problème - exemple pratique
Comme illustré ci-dessous, la sortie de Write-Host est différent si un tableau est passé ou s'il passe les éléments individuels du tableau un par un.

PS C:\> $theArray = @("A", "B", "C")
PS C:\> Write-Host $theArray
A B C
PS C:\> $theArray | foreach{Write-Host $_}
A
B
C
PS C:\> @($theArray) | foreach{Write-Host $_}
A
B
C

Comment faites-vous pour obtenir $theArray | foreach{Write-Host $_} pour produire la même sortie que Write-Host $theArray?




NOTES DE BAS DE PAGE

  1. Traitement des pipelines à Powershell

Un tableau normal de chaînes

PS C:\> @("A", "B", "C").GetType().FullName
System.Object[]


Un tableau normal de chaînes dirigées vers Foreach-Object

PS C:\> @("A", "B", "C") | foreach{$_.GetType().FullName}
System.String
System.String
System.String

Chaque chaîne du tableau est traitée une à la fois par le ForEach-Object CmdLet.


Un tableau de tableaux, où les tableaux "intérieurs" sont des tableaux de chaînes.

PS C:\> @(@("A", "B", "C"), @("D", "E", "F"), @("G", "H", "I")) | foreach{$_.GetType().FullName}
System.Object[]
System.Object[]
System.Object[]

Chaque tableau du tableau est traité un à la fois par le ForEach-Object CmdLet, et le contenu de chaque sous-tableau de l'entrée est traité comme un objet même s'il s'agit d'un tableau.

24
NoOneSpecial

Réponse courte: utilisez l'opérateur de tableau unaire ,:

,$theArray | foreach{Write-Host $_}

Réponse longue: il y a une chose que vous devez comprendre à propos de l'opérateur @(): il interprète toujours son contenu comme une instruction, même si le contenu n'est qu'une expression. Considérez ce code:

$a='A','B','C'
$b=@($a;)
$c=@($b;)

J'ajoute ici la marque explicite de fin de déclaration ;, Bien que PowerShell permette de l'omettre. $a Est un tableau de trois éléments. Quel résultat de l'instruction $a;? $a Est une collection, donc la collection doit être énumérée et chaque élément individuel doit être transmis par pipeline. Le résultat de l'instruction $a; Est donc trois éléments écrits dans le pipeline. @($a;) voit que trois éléments, mais pas le tableau d'origine, et crée un tableau à partir d'eux, donc $b est un tableau de trois éléments. De la même manière $c Est un tableau de trois mêmes éléments. Ainsi, lorsque vous écrivez @($collection) vous créez un tableau, qui copie les éléments de $collection, Au lieu du tableau d'un seul élément.

29
PetSerAl

Le caractère virgule fait des données un tableau. Pour que le pipeline traite votre tableau en tant que tableau, au lieu d'opérer sur chaque élément du tableau individuellement, vous devrez peut-être également encapsuler les données entre parenthèses.

Ceci est utile si vous devez évaluer le statut de plusieurs éléments dans le tableau.

en utilisant la fonction suivante

function funTest {
    param (
        [parameter(Position=1, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
        [alias ("Target")]
        [array]$Targets 
        ) # end param
    begin {}
    process {
        $RandomSeed = $( 1..1000 | Get-Random )
        foreach ($Target in $Targets) {
            Write-Host "$RandomSeed - $Target"
            } # next target
        } # end process
    end {}
    } # end function 

Considérez les exemples suivants:

Le simple fait de mettre votre tableau entre parenthèses ne garantit pas que la fonction traitera le tableau de valeurs en un seul appel de processus. Dans cet exemple, nous voyons les changements de nombres aléatoires pour chaque élément du tableau.

PS C:\> @(1,2,3,4,5) | funTest
153 - 1
87 - 2
96 - 3
96 - 4
986 - 5

Le simple fait d'ajouter la virgule principale ne garantit pas non plus que la fonction traitera le tableau de valeurs en un seul appel de processus. Dans cet exemple, nous voyons les changements de nombres aléatoires pour chaque élément du tableau.

PS C:\> , 1,2,3,4,5 | funTest
1000 - 1
84 - 2
813 - 3
156 - 4
928 - 5

Avec la virgule de tête et le tableau de valeurs entre parenthèses, nous pouvons voir que le nombre aléatoire reste le même car la commande foreach de la fonction est exploitée.

PS C:\> , @( 1,2,3,4,5) | funTest
883 - 1
883 - 2
883 - 3
883 - 4
883 - 5
4
Ro Yo Mi

Il existe une solution à l'ancienne, si cela ne vous dérange pas que votre processus soit une fonction.

Exemple: vous voulez qu'un tableau soit copié dans le presse-papiers d'une manière qui vous permette de le reconstruire sur un autre système sans aucune connectivité PSRemoting. Vous voulez donc qu'un tableau contenant "A", "B" et "C" se transmute en une chaîne: @ ("A", "B", "C") ... au lieu d'un tableau littéral.

Donc, vous construisez ceci (ce qui n'est pas optimal pour d'autres raisons, mais restez sur le sujet):

# Serialize-List

param 
(
    [Parameter(Mandatory, ValueFromPipeline)]
    [string[]]$list
)
    $output = "@(";

    foreach ($element in $list)
    {
        $output += "`"$element`","
    }

    $output = $output.Substring(0, $output.Length - 1)
    $output += ")"
    $output

et cela fonctionne lorsque vous spécifiez directement le tableau en tant que paramètre:

Serialize-List $ list

@("ABC")

... mais pas tellement quand vous le faites passer par le pipeline:

$ list | Serialize-List

@ ("C")

Mais refactorisez votre fonction avec des blocs de début, de processus et de fin:

# Serialize-List

param 
(
    [Parameter(Mandatory, ValueFromPipeline)]
    [string[]]$list
)

begin
{
    $output = "@(";
}

process
{
    foreach ($element in $list)
    {
        $output += "`"$element`","
    }
}

end
{
    $output = $output.Substring(0, $output.Length - 1)
    $output += ")"
    $output
}

... et vous obtenez la sortie souhaitée dans les deux sens.

Serialize-List $ list

@("ABC")

$ list | Serialize-List

@("ABC")

2
Niali