Powershell setara dengan proses substitusi Bash

14

Bash memiliki <(..)proses substitusi. Apa yang setara dengan Powershell?

Saya tahu ada $(...), tetapi mengembalikan sebuah string, sementara <(..)mengembalikan file yang bisa dibaca oleh perintah luar, seperti yang diharapkan.

Saya juga tidak mencari solusi berbasis pipa, tetapi sesuatu yang bisa saya tempel di tengah baris perintah.

IttayD
sumber
3
afaik tidak ada hal seperti itu, tetapi akan menarik untuk dibuktikan salah.
Zoredache
4
Bisakah Anda memberikan contoh mock-up tentang bagaimana Anda mengharapkannya digunakan? Saya ingin tahu apakah $ (... | select -expandproperty objectyouwanttopass) mungkin cocok untuk satu kasus substitusi.
Andy
2
Di PowerShell $ () adalah operator subekspresi, Anda bisa menggunakannya seperti ini: Write-Output "The BITS service is $(Get-Service bits | select -ExpandProperty Stauts)"untuk mendapatkan status layanan BITS tanpa memuatnya ke dalam variabel terlebih dahulu. Melihat Substitusi Proses, ini tidak persis sama, tetapi mungkin masih memecahkan masalah yang Anda hadapi
mortenya
@Andy: Fitur ini akan membantu dengan utilitas eksternal yang memerlukan operan nama file . Contohnya adalah psftp.exeuntuk transfer SFTP: -bopsinya mengharuskan Anda untuk memberikan perintah untuk dijalankan di server melalui file , yang tidak nyaman, jika Anda hanya ingin menjalankan, katakanlah mget *,. Jika PowerShell memiliki proses substitusi, Anda akan dapat melakukan sesuatu seperti psftp.exe -l user -p password somehost -b <( "mget *" ).
mklement

Jawaban:

4

Jawaban ini BUKAN untuk Anda , jika Anda:
- jarang, jika pernah, perlu menggunakan CLI eksternal (yang umumnya layak diperjuangkan - perintah PowerShell-asli bermain lebih baik bersama-sama dan tidak memerlukan fitur seperti itu).
- tidak terbiasa dengan proses substitusi Bash.
Jawaban ini ADALAH untuk Anda , jika Anda:
- sering menggunakan CLI eksternal (apakah karena kebiasaan atau karena kurangnya (baik) alternatif PowerShell-asli), terutama saat menulis skrip.
- Digunakan untuk dan menghargai apa yang bisa dilakukan oleh proses substitusi Bash.
- Pembaruan : Sekarang PowerShell didukung pada platform Unix juga, fitur ini semakin menarik - lihat permintaan fitur ini di GitHub, yang menunjukkan bahwa PowerShell mengimplementasikan fitur yang mirip dengan proses substitusi.

Di dunia Unix, di Bash / Ksh / Zsh, subtitusi proses menawarkan memperlakukan output perintah seolah-olah itu adalah file sementara yang membersihkan setelah itu sendiri; misalnya cat <(echo 'hello'), di mana catmelihat output dari echoperintah sebagai jalur file sementara yang berisi output perintah .

Sementara perintah PowerShell-asli tidak membutuhkan fitur seperti itu, itu bisa berguna ketika berhadapan dengan CLI eksternal .

Meniru fitur di PowerShell rumit , tetapi mungkin sepadan, jika Anda sering membutuhkannya.

Bayangkan sebuah fungsi bernama cfyang menerima blok skrip, menjalankan blok dan menulis hasilnya ke temp. file dibuat sesuai permintaan, dan mengembalikan temp. jalur file ; misalnya:

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

Ini adalah contoh sederhana yang tidak menggambarkan perlunya fitur tersebut dengan baik. Mungkin skenario yang lebih meyakinkan adalah penggunaan psftp.exeuntuk transfer SFTP: penggunaan batch-nya (otomatis) mengharuskan menyediakan file input yang berisi perintah yang diinginkan, sedangkan perintah seperti itu dapat dengan mudah dibuat sebagai string dengan cepat.

Agar kompatibel dengan utilitas eksternal sebanyak mungkin, temp. file harus menggunakan UTF-8 encoding tanpa BOM (byte-order mark) secara default, meskipun Anda dapat meminta BOM UTF-8 dengan -BOM, jika diperlukan.

Sayangnya, aspek pembersihan otomatis dari substitusi proses tidak dapat ditiru secara langsung , sehingga diperlukan panggilan pembersihan secara eksplisit ; pembersihan dilakukan dengan memanggil cf tanpa argumen :

  • Untuk penggunaan interaktif , Anda dapat mengotomatiskan pembersihan dengan menambahkan panggilan pembersihan ke promptfungsi Anda sebagai berikut ( promptfungsi mengembalikan string prompt , tetapi juga dapat digunakan untuk melakukan perintah di belakang layar setiap kali prompt ditampilkan, mirip dengan Bash's $PROMPT_COMMANDvariabel); untuk ketersediaan dalam sesi interaktif apa pun, tambahkan berikut ini serta definisi di cfbawah ini untuk profil PowerShell Anda:

    "function prompt { cf 4>`$null; $((get-item function:prompt).definition) }" |
      Invoke-Expression
  • Untuk digunakan dalam skrip , untuk memastikan bahwa pembersihan dilakukan, blok yang menggunakan cf- berpotensi keseluruhan skrip - perlu dibungkus dengan try/ finallyblok, di mana cftanpa argumen dipanggil untuk pembersihan:

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

Inilah implementasinya : fungsi lanjutan ConvertTo-TempFiledan alias ringkasnya cf:

Catatan : Penggunaan New-Module, yang membutuhkan PSv3 +, untuk mendefinisikan fungsi melalui modul dinamis memastikan bahwa tidak ada konflik variabel antara parameter fungsi dan variabel yang dirujuk di dalam blok skrip yang diteruskan.

$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
    }
  }
}

Perhatikan kemampuan untuk secara opsional menentukan jalur [file] keluaran dan / atau ekstensi nama file.

mklement
sumber
Gagasan bahwa Anda perlu melakukan ini meragukan, dan hanya akan membuat hal-hal lebih sulit demi hanya tidak ingin menggunakan PowerShell.
Jim B
1
@ JimB: Saya pribadi menggunakannya psftp.exe, itulah yang mendorong saya untuk menulisnya. Meskipun lebih baik melakukan semuanya secara asli di PowerShell, itu tidak selalu mungkin; menjalankan CLI eksternal dari PowerShell akan dan akan terus terjadi; jika Anda menemukan diri Anda berulang kali berurusan dengan CLI yang memerlukan input file yang (lebih) mudah dibangun di memori / dengan perintah lain, fungsi dalam jawaban ini dapat membuat hidup Anda lebih mudah.
mklement
Apakah kamu bercanda? tidak ada yang diperlukan. Saya belum menemukan perintah yang hanya menerima file dengan perintah untuk parameter. Sejauh SFTP pergi pencarian sederhana menunjukkan kepada saya 2 rakitan addin sederhana untuk secara asli melakukan FTP di PowerShell.
Jim B
1
@ JimB: Jika Anda ingin melanjutkan percakapan ini dengan cara yang konstruktif, ubah nada bicara Anda.
mklement
2
@JimB, GNU Diffutils diff hanya beroperasi pada file, jika Anda tertarik.
Pavel
2

Ketika tidak dilampirkan dalam tanda kutip ganda, $(...)mengembalikan Objek PowerShell (atau lebih tepatnya, apa pun yang dikembalikan oleh kode terlampir), mengevaluasi kode terlampir terlebih dahulu. Ini harus sesuai untuk keperluan Anda ("sesuatu [saya] dapat tetap di tengah-tengah baris perintah"), dengan asumsi bahwa baris perintah adalah PowerShell.

Anda dapat mengujinya dengan memiparkan berbagai versi ke Get-Member, atau bahkan langsung mengeluarkannya.

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 #>

Ketika dilampirkan dalam tanda kutip ganda, seperti yang Anda perhatikan, `" $ (...) "hanya akan mengembalikan sebuah string.

Dengan cara ini, jika Anda ingin menyisipkan, katakanlah, isi file langsung pada sebuah baris, Anda bisa menggunakan sesuatu seperti:

Invoke-Command -ComputerName (Get-Content C:\Temp\Files\new1.txt) -ScriptBlock {<# something #>}
James Ruskin
sumber
Ini jawaban yang fantastis !!
GregL
Apa yang Anda jelaskan tidak setara dengan substitusi proses Bash. Substitusi proses dirancang untuk digunakan dengan perintah yang memerlukan operan nama file ; yaitu, output dari perintah yang dilampirkan dalam substitusi proses, secara longgar, ditulis ke file sementara, dan path file itu dikembalikan; Selain itu, keberadaan file dibatasi untuk perintah bahwa proses substitusi adalah bagian dari. Jika PowerShell memiliki fitur seperti itu, Anda akan mengharapkan sesuatu seperti yang berikut ini berfungsi:Get-Content <(Get-ChildItem)
mklement
Harap perbaiki saya jika saya salah, dan ini bukan yang Anda cari, tetapi tidak Get-ChildItem | Get-Contentberfungsi dengan baik? Atau Anda dapat mencoba Get-Content (Get-ChildItem).FullNameefek yang sama? Anda mungkin mendekati ini dari pandangan yang sepenuhnya dipengaruhi oleh pendekatan skrip lain.
James Ruskin
1
Ya, di ranah PowerShell tidak perlu fitur ini; itu hanya menarik untuk digunakan dengan CLI eksternal yang memerlukan input file , dan di mana konten file tersebut mudah dibangun dengan perintah (PowerShell). Lihat komentar saya pada pertanyaan untuk contoh dunia nyata. Anda mungkin tidak pernah memerlukan fitur seperti itu, tetapi bagi orang-orang yang sering perlu memanggil CLI eksternal itu menarik. Anda setidaknya harus mengawali jawaban Anda dengan mengatakan bahwa Anda menunjukkan cara PowerShell dalam melakukan sesuatu - yang bertentangan dengan apa yang secara khusus diminta OP - dan mengapa Anda melakukannya.
mklement