Powershell เทียบเท่ากับการทดแทนกระบวนการของ Bash


14

Bash มี<(..)ไว้สำหรับการทดแทนกระบวนการ อะไรที่เทียบเท่า Powershell

ฉันรู้ว่ามี$(...)แต่มันกลับสตริงในขณะที่<(..)ส่งกลับไฟล์คำสั่งด้านนอกสามารถอ่านได้จากซึ่งเป็นสิ่งที่มันคาดหวัง

ฉันยังไม่ได้มองหาวิธีแก้ปัญหาแบบไปป์ แต่สิ่งที่ฉันสามารถติดอยู่ตรงกลางของบรรทัดคำสั่ง


3
afaik ไม่มีสิ่งนั้น แต่น่าสนใจที่จะพิสูจน์ว่าผิด
Zoredache

4
คุณสามารถยกตัวอย่างการเยาะเย้ยของวิธีการที่คุณคาดว่าจะใช้? ฉันสงสัยว่า $ (... | select -expandproperty objectyouwanttopass) อาจเหมาะกับกรณีทดแทนเดียว
Andy

2
ใน PowerShell $ () เป็นตัวดำเนินการ subexpression คุณสามารถใช้มันดังนี้: Write-Output "The BITS service is $(Get-Service bits | select -ExpandProperty Stauts)"เพื่อรับสถานะของบริการ BITS โดยไม่โหลดลงในตัวแปรก่อน ดูการทดแทนกระบวนการนี้ไม่เหมือนกัน แต่อาจยังคงแก้ปัญหาที่คุณกำลังเผชิญอยู่
mortenya

@Andy: คุณลักษณะนี้จะช่วยให้มีสาธารณูปโภคภายนอกที่ต้องใช้ชื่อไฟล์ที่ถูกดำเนินการ ตัวอย่างคือpsftp.exeสำหรับการถ่ายโอน SFTP: ของ-bตัวเลือกที่คุณจะต้องให้คำสั่งไปทำงานบนเซิร์ฟเวอร์ผ่านทางไฟล์mget *ซึ่งไม่สะดวกถ้าคุณเพียงต้องการที่จะทำงานกล่าวว่า หาก PowerShell มีการทดแทนกระบวนการคุณสามารถทำเช่นpsftp.exe -l user -p password somehost -b <( "mget *" )นั้นได้
mklement

คำตอบ:


4

คำตอบนี้ไม่เหมาะสำหรับคุณหากคุณ:
- ไม่ค่อยบ่อยนักที่จะต้องใช้ CLI ภายนอก (ซึ่งโดยทั่วไปแล้วควรค่าแก่การพยายาม - คำสั่ง PowerShell-native เล่นได้ดีกว่ากันมากและไม่จำเป็นต้องใช้คุณสมบัติดังกล่าว)
- ไม่คุ้นเคยกับการทดแทนกระบวนการของ Bash
คำตอบนี้สำหรับคุณถ้าคุณ:
- ใช้ CLI ภายนอกบ่อยครั้ง (ไม่ว่าจะเป็นนิสัยหรือเนื่องมาจากการขาด (ดี) ทางเลือกของ PowerShell-native) โดยเฉพาะในขณะที่เขียนสคริปต์
- คุ้นเคยและชื่นชมสิ่งที่การสับเปลี่ยนกระบวนการของ Bash สามารถทำได้
- อัปเดต : ตอนนี้ PowerShell ได้รับการสนับสนุนบนแพลตฟอร์ม Unix ด้วยคุณสมบัตินี้เพิ่มความสนใจ - ดูคำขอคุณลักษณะนี้ใน GitHubซึ่งแสดงให้เห็นว่า PowerShell ใช้คุณสมบัติคล้ายกับการประมวลผลการทดแทน

ในโลกของ Unix ใน Bash / Ksh / Zsh การทดแทนโปรเซสเป็นการเสนอเอาท์พุทคำสั่งราวกับว่ามันเป็นไฟล์ชั่วคราวที่ทำความสะอาดหลังจากตัวมันเอง เช่นcat <(echo 'hello')ที่catเห็นผลลัพธ์จากechoคำสั่งเป็นเส้นทางของไฟล์ชั่วคราวที่มีออกคำสั่ง

ในขณะที่คำสั่ง PowerShell พื้นเมืองไม่จำเป็นจริงสำหรับคุณลักษณะดังกล่าวก็จะมีประโยชน์เมื่อต้องรับมือกับCLIs ภายนอก

การเลียนแบบคุณลักษณะใน PowerShell นั้นยุ่งยากแต่อาจคุ้มค่าถ้าคุณพบว่าตัวเองต้องการมันบ่อยๆ

รูปภาพฟังก์ชั่นที่ตั้งชื่อcfที่รับสคริปต์บล็อกเรียกใช้งานบล็อกและเขียนเอาต์พุตไปยัง temp ไฟล์ที่สร้างขึ้นตามความต้องการและส่งกลับ temp เส้นทางของแฟ้ม ; เช่น:

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

นี่เป็นตัวอย่างง่ายๆที่ไม่ได้แสดงให้เห็นถึงความต้องการคุณลักษณะดังกล่าวได้เป็นอย่างดี บางทีสถานการณ์สมมติที่น่าเชื่อถือมากขึ้นก็คือการใช้psftp.exeSFTP ในการถ่ายโอน: การใช้แบตช์ (อัตโนมัติ) ต้องมีการเตรียมไฟล์อินพุตที่มีคำสั่งที่ต้องการในขณะที่คำสั่งดังกล่าวสามารถสร้างเป็นสตริงได้ทันที

เพื่อที่จะเข้ากันได้อย่างกว้างขวางกับโปรแกรมอรรถประโยชน์ภายนอกที่เป็นไปได้อุณหภูมิ ไฟล์ควรใช้UTF 8เข้ารหัสโดยไม่ต้อง BOM (เครื่องหมายไบต์ตามลำดับ) โดยค่าเริ่มต้นถึงแม้ว่าคุณจะสามารถขอ UTF-8 BOM ด้วย-BOMถ้าจำเป็น

แต่น่าเสียดายที่โดยอัตโนมัติทำความสะอาดทุกแง่มุมของการแทนกระบวนการไม่สามารถโดยตรงเทิดทูนดังนั้นการเรียกร้องการทำความสะอาดอย่างชัดเจนเป็นสิ่งจำเป็น ; การล้างข้อมูลจะดำเนินการโดยการโทรcf โดยไม่มีข้อโต้แย้ง :

  • สำหรับการใช้แบบอินเตอร์แอคทีฟคุณสามารถทำให้การล้างข้อมูลเป็นไปโดยอัตโนมัติโดยการเพิ่มการเรียก cleanup ไปยังpromptฟังก์ชันของคุณดังต่อไปนี้ ( promptฟังก์ชั่นส่งกลับสตริงพรอมต์แต่ยังสามารถใช้เพื่อดำเนินการคำสั่งเบื้องหลัง$PROMPT_COMMANDตัวแปร); สำหรับความพร้อมใช้งานในเซสชันแบบโต้ตอบใด ๆ ให้เพิ่มข้อมูลต่อไปนี้รวมทั้งคำจำกัดความcfด้านล่างไว้ในโปรไฟล์ PowerShell ของคุณ:

    "function prompt { cf 4>`$null; $((get-item function:prompt).definition) }" |
      Invoke-Expression
  • สำหรับใช้ในสคริปต์เพื่อให้แน่ใจว่ามีการดำเนินการล้างข้อมูลบล็อกที่ใช้cf- อาจเป็นสคริปต์ทั้งหมด - ต้องถูกล้อมรอบด้วย a try/ finallyblock ซึ่งcfไม่มีข้อโต้แย้งที่เรียกว่าการล้างข้อมูล:

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

นี่คือการใช้งาน : ฟังก์ชั่นขั้นสูงConvertTo-TempFileและนามแฝงย่อcf:

หมายเหตุ : การใช้งานNew-Moduleซึ่งต้องการ PSv3 + เพื่อกำหนดฟังก์ชั่นผ่านโมดูลแบบไดนามิกช่วยให้มั่นใจได้ว่าจะไม่มีความขัดแย้งของตัวแปรระหว่างพารามิเตอร์ฟังก์ชันและตัวแปรที่อ้างอิงภายในบล็อกสคริปต์ที่ส่งผ่าน

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

หมายเหตุความสามารถในการระบุทางเลือกเส้นทาง [ไฟล์] และ / หรือชื่อไฟล์นามสกุล


ความคิดที่คุณจะต้องทำคือน่าสงสัยที่สุดและจะทำให้สิ่งต่าง ๆ ยากขึ้นเพราะไม่ต้องการใช้ PowerShell
Jim B

1
@JimB: ฉันใช้มันเป็นการส่วนตัวpsftp.exeซึ่งเป็นสิ่งที่กระตุ้นให้ฉันเขียน แม้ว่ามันจะดีกว่าที่จะทำทุกอย่างใน PowerShell แต่ก็ไม่สามารถทำได้เสมอไป การเรียก CLIs ภายนอกจาก PowerShell ทำและจะเกิดขึ้นต่อไป หากคุณพบว่าตัวเองต้องรับมือกับ CLIs ซ้ำ ๆ ที่ต้องการอินพุตไฟล์ที่สามารถสร้างได้มากขึ้นในหน่วยความจำ / โดยคำสั่งอื่นฟังก์ชันในคำตอบนี้จะทำให้ชีวิตของคุณง่ายขึ้น
mklement

นี่มุกหรือเปล่าเนี่ย? ไม่จำเป็นต้องมี ฉันยังไม่พบคำสั่งที่ยอมรับเฉพาะไฟล์ที่มีคำสั่งสำหรับพารามิเตอร์ เท่าที่ SFTP ทำการค้นหาง่าย ๆ แสดงให้ฉันเห็น 2 แอสเซมบลี addin ง่าย ๆ เพื่อกำเนิด FTP ใน PowerShell
Jim B

1
@JimB: หากคุณต้องการสนทนาต่อไปอย่างสร้างสรรค์ให้เปลี่ยนเสียงของคุณ
mklement

2
@JimB GNU Diffutils ทำงานได้กับไฟล์ต่าง ๆ เท่านั้นในกรณีที่คุณมีปัญหา
Pavel

2

เมื่อไม่อยู่ในเครื่องหมายคำพูดคู่$(...)จะส่งคืน PowerShell Object (หรือมากกว่าสิ่งที่ส่งคืนโดยรหัสที่ล้อมรอบ) ประเมินรหัสที่ถูกปิดล้อมก่อน สิ่งนี้ควรเหมาะสำหรับจุดประสงค์ของคุณ ("บางอย่าง [I] สามารถอยู่ตรงกลางของบรรทัดคำสั่ง") โดยสมมติว่าบรรทัดคำสั่งคือ PowerShell

คุณสามารถทดสอบสิ่งนี้ได้โดยการวางท่อเวอร์ชั่นต่าง ๆGet-Memberหรือแม้แต่แค่เอาท์พุทโดยตรง

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

เมื่อใส่เครื่องหมายอัญประกาศคู่ตามที่คุณสังเกตเห็น `` $ (... ) 'จะส่งคืนสตริง

ด้วยวิธีนี้หากคุณต้องการแทรกพูดเนื้อหาของไฟล์ลงบนบรรทัดโดยตรงคุณสามารถใช้สิ่งต่อไปนี้:

Invoke-Command -ComputerName (Get-Content C:\Temp\Files\new1.txt) -ScriptBlock {<# something #>}

นี่คือคำตอบที่ยอดเยี่ยม !!
GregL

สิ่งที่คุณอธิบายไม่เท่ากับการทดแทนกระบวนการของ Bash การทดแทนกระบวนการออกแบบมาสำหรับใช้กับคำสั่งที่ต้องการตัวถูกดำเนินการชื่อไฟล์ นั่นคือผลลัพธ์จากคำสั่งที่อยู่ในการทดแทนกระบวนการคือการพูดอย่างหลวม ๆ เขียนไปยังไฟล์ชั่วคราวและเส้นทางของไฟล์นั้นจะถูกส่งกลับ นอกจากนี้การดำรงอยู่ของไฟล์จะถูกกำหนดขอบเขตให้กับคำสั่งว่าการทดแทนกระบวนการเป็นส่วนหนึ่งของ หาก PowerShell มีคุณสมบัติเช่นนี้คุณคาดหวังว่าสิ่งต่อไปนี้จะสามารถใช้งานได้:Get-Content <(Get-ChildItem)
mklement

โปรดแก้ไขให้ฉันถ้าฉันผิดและนี่ไม่ใช่สิ่งที่คุณกำลังมองหา แต่Get-ChildItem | Get-Contentทำงานได้ไม่ดีนัก หรือคุณสามารถลองGet-Content (Get-ChildItem).FullNameใช้เอฟเฟกต์แบบเดียวกันได้ คุณอาจเข้าใกล้สิ่งนี้จากมุมมองที่ได้รับอิทธิพลอย่างละเอียดจากวิธีการเขียนสคริปต์อื่น
James Ruskin

1
ใช่ในขอบเขตของ PowerShellไม่จำเป็นต้องใช้คุณสมบัตินี้ มันเป็นเรื่องที่น่าสนใจสำหรับใช้กับCLI ภายนอกที่ต้องการอินพุตไฟล์และเนื้อหาของไฟล์นั้นถูกสร้างขึ้นอย่างง่ายดายด้วยคำสั่ง (PowerShell) ดูความคิดเห็นของฉันเกี่ยวกับคำถามเพื่อเป็นตัวอย่างในโลกแห่งความจริง คุณอาจไม่จำเป็นต้องใช้คุณสมบัติดังกล่าว แต่สำหรับคนที่ต้องการโทรหา CLIs ภายนอกเป็นสิ่งที่น่าสนใจ อย่างน้อยคุณควรนำคำตอบของคุณโดยบอกว่าคุณแสดงให้เห็นถึงวิธีการPowerShellในการทำสิ่งต่าง ๆ ซึ่งตรงข้ามกับสิ่งที่ OP ร้องขอมาโดยเฉพาะ - และทำไมคุณถึงทำเช่นนั้น
mklement
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.