L'équivalent de Powershell à la substitution de processus de Bash

14

Bash a <(..)pour la substitution de processus. Quel est l'équivalent de Powershell?

Je sais qu'il existe $(...), mais il renvoie une chaîne, tandis que <(..)renvoie un fichier à partir duquel la commande externe peut lire, ce à quoi il s'attend.

Je ne recherche pas non plus une solution basée sur les tuyaux, mais quelque chose que je peux coller au milieu de la ligne de commande.

IttayD
la source
3
afaik il n'y a rien de tel, mais il serait intéressant de se tromper.
Zoredache
4
Pouvez-vous donner un exemple de maquette de la façon dont vous vous attendez à ce qu'il soit utilisé? Je me demande si $ (... | select -expandproperty objectyouwanttopass) pourrait convenir à un seul cas de substitution.
Andy
2
Dans PowerShell $ () est l'opérateur de sous-expression, vous pouvez l'utiliser comme ceci: Write-Output "The BITS service is $(Get-Service bits | select -ExpandProperty Stauts)"pour obtenir le statut du service BITS sans le charger d'abord dans une variable. En ce qui concerne la substitution de processus, ce n'est pas exactement la même chose, mais cela pourrait toujours résoudre le problème auquel vous êtes confronté
mortenya
@Andy: La fonctionnalité aiderait avec les utilitaires externes qui nécessitent des opérandes de nom de fichier . Un exemple est psftp.exepour les transferts SFTP: son -boption vous oblige à fournir des commandes à exécuter sur le serveur via un fichier , ce qui n'est pas pratique, si vous voulez simplement l'exécuter, disons mget *. Si PowerShell avait une substitution de processus, vous seriez en mesure de faire quelque chose comme psftp.exe -l user -p password somehost -b <( "mget *" ).
mklement le

Réponses:

4

Cette réponse n'est PAS pour vous , si vous:
- rarement, voire jamais, avez besoin d'utiliser des CLI externes (ce qui vaut généralement la peine d'être recherché - les commandes natives de PowerShell jouent beaucoup mieux ensemble et n'ont pas besoin d'une telle fonctionnalité).
- ne connaissent pas la substitution de processus de Bash.
Cette réponse EST pour vous , si vous:
- utilisez fréquemment des CLI externes (que ce soit par habitude ou par manque de (bonnes) alternatives natives PowerShell), en particulier lors de l'écriture de scripts.
- sont habitués et apprécient ce que la substitution de processus de Bash peut faire.
- Mise à jour : Maintenant que PowerShell est également pris en charge sur les plates-formes Unix, cette fonctionnalité présente un intérêt croissant - voir cette demande de fonctionnalité sur GitHub, ce qui suggère que PowerShell implémente une fonctionnalité similaire à la substitution de processus.

Dans le monde Unix, dans Bash / Ksh / Zsh, une substitution de processus propose de traiter la sortie de commande comme s'il s'agissait d'un fichier temporaire qui se nettoie après lui-même; par exemple cat <(echo 'hello'), où catvoit la sortie de la echocommande comme le chemin d'un fichier temporaire contenant la sortie de la commande .

Bien que les commandes natives de PowerShell n'aient pas vraiment besoin d'une telle fonctionnalité, cela peut être pratique lorsque vous traitez avec des CLI externes .

Émuler la fonctionnalité dans PowerShell est lourd , mais peut en valoir la peine, si vous en avez souvent besoin.

Imaginez une fonction nommée cfqui accepte un bloc de script, exécute le bloc et écrit sa sortie dans un temp. fichier créé à la demande et retourne le temp. chemin du fichier ; par exemple:

 findstr.exe "Windows" (cf { Get-ChildItem c:\ }) # findstr sees the temp. file's path.

Il s'agit d'un exemple simple qui n'illustre pas bien la nécessité d'une telle fonctionnalité. Un scénario plus convaincant est peut-être l'utilisation des psftp.exetransferts SFTP: son utilisation par lots (automatisée) nécessite de fournir un fichier d' entrée contenant les commandes souhaitées, tandis que ces commandes peuvent facilement être créées sous forme de chaîne à la volée.

Afin d'être aussi largement compatible avec les utilitaires externes que possible, le temp. fichier doit utiliser UTF-8 codage sans une nomenclature (marque d'ordre d' octet) par défaut, mais vous pouvez demander une nomenclature UTF-8 avec -BOM, le cas échéant.

Malheureusement, l' aspect de nettoyage automatique des substitutions de processus ne peut pas être directement émulé, donc un appel de nettoyage explicite est nécessaire ; le nettoyage est effectué en appelant cf sans arguments :

  • Pour une utilisation interactive , vous pouvez automatiser le nettoyage en ajoutant l'appel de nettoyage à votre promptfonction comme suit (la promptfonction renvoie la chaîne d' invite , mais peut également être utilisée pour exécuter des commandes en arrière-plan chaque fois que l'invite est affichée, semblable à Bash's $PROMPT_COMMANDvariable); pour la disponibilité dans toute session interactive, ajoutez les éléments suivants ainsi que la définition de cfci - dessous à votre profil PowerShell:

    "function prompt { cf 4>`$null; $((get-item function:prompt).definition) }" |
      Invoke-Expression
  • Pour une utilisation dans les scripts , pour garantir que le nettoyage est effectué, le bloc qui utilise cf- potentiellement le script entier - doit être encapsulé dans un bloc try/ finally, dans lequel cfsans arguments est appelé pour le nettoyage:

# Example
try {

  # Pass the output from `Get-ChildItem` via a temporary file.
  findstr.exe "Windows" (cf { Get-ChildItem c:\ })

  # cf() will reuse the existing temp. file for additional invocations.
  # Invoking it without parameters will delete the temp. file.

} finally {
  cf  # Clean up the temp. file.
}

Voici la mise en œuvre : fonctions avancées ConvertTo-TempFileet son alias succinct, cf:

Remarque : L'utilisation de New-Module, qui nécessite PSv3 +, pour définir la fonction via un module dynamique garantit qu'il ne peut y avoir de conflits de variables entre les paramètres de fonction et les variables référencées à l'intérieur du bloc de script passé.

$null = New-Module {  # Load as dynamic module
  # Define a succinct alias.
  set-alias cf ConvertTo-TempFile
  function ConvertTo-TempFile {
    [CmdletBinding(DefaultParameterSetName='Cleanup')]
    param(
        [Parameter(ParameterSetName='Standard', Mandatory=$true, Position=0)]
        [ScriptBlock] $ScriptBlock
      , [Parameter(ParameterSetName='Standard', Position=1)]
        [string] $LiteralPath
      , [Parameter(ParameterSetName='Standard')]
        [string] $Extension
      , [Parameter(ParameterSetName='Standard')]
        [switch] $BOM
    )

    $prevFilePath = Test-Path variable:__cttfFilePath
    if ($PSCmdlet.ParameterSetName -eq 'Cleanup') {
      if ($prevFilePath) { 
        Write-Verbose "Removing temp. file: $__cttfFilePath"
        Remove-Item -ErrorAction SilentlyContinue $__cttfFilePath
        Remove-Variable -Scope Script  __cttfFilePath
      } else {
        Write-Verbose "Nothing to clean up."
      }
    } else { # script block specified
      if ($Extension -and $Extension -notlike '.*') { $Extension = ".$Extension" }
      if ($LiteralPath) {
        # Since we'll be using a .NET framework classes directly, 
        # we must sync .NET's notion of the current dir. with PowerShell's.
        [Environment]::CurrentDirectory = $pwd
        if ([System.IO.Directory]::Exists($LiteralPath)) { 
          $script:__cttfFilePath = [IO.Path]::Combine($LiteralPath, [IO.Path]::GetRandomFileName() + $Extension)
          Write-Verbose "Creating file with random name in specified folder: '$__cttfFilePath'."
        } else { # presumptive path to a *file* specified
          if (-not [System.IO.Directory]::Exists((Split-Path $LiteralPath))) {
            Throw "Output folder '$(Split-Path $LiteralPath)' must exist."
          }
          $script:__cttfFilePath = $LiteralPath
          Write-Verbose "Using explicitly specified file path: '$__cttfFilePath'."
        }
      } else { # Create temp. file in the user's temporary folder.
        if (-not $prevFilePath) { 
          if ($Extension) {
            $script:__cttfFilePath = [IO.Path]::Combine([IO.Path]::GetTempPath(), [IO.Path]::GetRandomFileName() + $Extension)
          } else {
            $script:__cttfFilePath = [IO.Path]::GetTempFilename() 
          }
          Write-Verbose "Creating temp. file: $__cttfFilePath"
        } else {
          Write-Verbose "Reusing temp. file: $__cttfFilePath"      
        }
      }
      if (-not $BOM) { # UTF8 file *without* BOM
        # Note: Out-File, sadly, doesn't support creating UTF8-encoded files 
        #       *without a BOM*, so we must use the .NET framework.
        #       [IO.StreamWriter] by default writes UTF-8 files without a BOM.
        $sw = New-Object IO.StreamWriter $__cttfFilePath
        try {
            . $ScriptBlock | Out-String -Stream | % { $sw.WriteLine($_) }
        } finally { $sw.Close() }
      } else { # UTF8 file *with* BOM
        . $ScriptBlock | Out-File -Encoding utf8 $__cttfFilePath
      }
      return $__cttfFilePath
    }
  }
}

Notez la possibilité de spécifier éventuellement un chemin de sortie [fichier] et / ou une extension de nom de fichier.

mklement
la source
L'idée que vous auriez besoin de le faire est au mieux douteuse, et rendrait simplement les choses plus difficiles pour le simple fait de ne pas vouloir utiliser PowerShell.
Jim B
1
@JimB: Je l'utilise personnellement avec psftp.exe, c'est ce qui m'a poussé à l'écrire. Même s'il est préférable de tout faire en natif dans PowerShell, ce n'est pas toujours possible; l'appel de CLI externes à partir de PowerShell se produit et continuera de se produire; si vous vous retrouvez à plusieurs reprises avec des CLI qui nécessitent une entrée de fichier qui peut (plus) facilement être construite en mémoire / par une autre commande, la fonction de cette réponse peut vous faciliter la vie.
mklement
Est-ce que tu plaisantes? rien de tout cela n'est requis. Je n'ai pas encore trouvé de commande qui n'accepte que les fichiers avec des commandes de paramètres. En ce qui concerne SFTP, une simple recherche m'a montré 2 assemblys de complément simples pour exécuter nativement FTP dans PowerShell.
Jim B
1
@JimB: Si vous souhaitez poursuivre cette conversation de manière constructive, changez de ton.
mklement
2
@JimB GNU Diffutils diff ne fonctionne que sur les fichiers, au cas où vous seriez intéressé.
Pavel
2

Lorsqu'il n'est pas placé entre guillemets doubles, $(...)renvoie un objet PowerShell (ou plutôt, tout ce qui est retourné par le code inclus), évaluant d'abord le code inclus. Cela devrait convenir à vos besoins ("quelque chose que [je] peux coller au milieu de la ligne de commande"), en supposant que la ligne de commande est PowerShell.

Vous pouvez le tester en canalisant différentes versions Get-Member, ou même en le sortant directement.

PS> "$(ls C:\Temp\Files)"
new1.txt new2.txt

PS> $(ls C:\Temp\Files)


    Directory: C:\Temp\Files


Mode                LastWriteTime         Length Name                                                                      
----                -------------         ------ ----                                                                      
-a----       02/06/2015     14:58              0 new1.txt                                                                  
-a----       02/06/2015     14:58              0 new2.txt   

PS> "$(ls C:\Temp\Files)" | gm


   TypeName: System.String
<# snip #>

PS> $(ls C:\Temp\Files) | gm


   TypeName: System.IO.FileInfo
<# snip #>

Lorsqu'il est placé entre guillemets, comme vous l'avez remarqué, `" $ (...) "renverra simplement une chaîne.

De cette façon, si vous vouliez insérer, disons, le contenu d'un fichier directement sur une ligne, vous pourriez utiliser quelque chose comme:

Invoke-Command -ComputerName (Get-Content C:\Temp\Files\new1.txt) -ScriptBlock {<# something #>}
James Ruskin
la source
C'est une réponse fantastique !!
GregL
Ce que vous décrivez n'est pas l'équivalent de la substitution de processus de Bash. La substitution de processus est conçue pour être utilisée avec des commandes qui nécessitent des opérandes de nom de fichier ; c'est-à-dire que la sortie d'une commande incluse dans une substitution de processus est, en gros, écrite dans un fichier temporaire, et le chemin de ce fichier est retourné; en outre, l'existence du fichier est limitée à la commande dont fait partie la substitution de processus. Si PowerShell avait une telle fonctionnalité, vous vous attendriez à ce que quelque chose comme ce qui suit fonctionne:Get-Content <(Get-ChildItem)
mklement
Veuillez me corriger si je me trompe, et ce n'est pas ce que vous cherchez, mais ne Get-ChildItem | Get-Contentfonctionne pas parfaitement bien? Ou vous pourriez autrement essayer Get-Content (Get-ChildItem).FullNamepour le même effet? Vous pouvez aborder cela à partir d'une vue complètement influencée par une autre approche de script.
James Ruskin
1
Oui, dans le domaine de PowerShell , cette fonctionnalité n'est pas nécessaire; il n'est intéressant que pour une utilisation avec des CLI externes qui nécessitent une entrée de fichier et où le contenu de ces fichiers est facilement construit avec une commande (PowerShell). Voir mon commentaire sur la question pour un exemple du monde réel. Vous n'aurez peut-être jamais besoin d'une telle fonctionnalité, mais pour les personnes qui ont fréquemment besoin d'appeler des CLI externes, cela est intéressant. Vous devriez au moins faire précéder votre réponse en disant que vous démontrez la façon de faire PowerShell - par opposition à ce que le PO a spécifiquement demandé - et pourquoi vous le faites.
mklement