Powershell jest odpowiednikiem zastępowania procesu przez Basha

14

Bash ma <(..)do zastąpienia procesu. Jaki jest odpowiednik Powershell?

Wiem, że istnieje $(...), ale zwraca ciąg znaków, podczas gdy <(..)zwraca plik, z którego zewnętrzne polecenie może odczytać, czego się spodziewa.

Nie szukam też rozwiązania opartego na potokach, ale coś, co mogę trzymać w środku wiersza poleceń.

ItdayD
źródło
3
afaik nie ma czegoś takiego, ale byłoby interesujące, gdyby udowodniono, że się myli.
Zoredache
4
Czy możesz podać przykładowy sposób, w jaki spodziewałbyś się, że zostanie wykorzystany? Zastanawiam się, czy $ (... | select -expandproperty objectyouwanttopass) może pasować do pojedynczego przypadku zastąpienia.
Andy,
2
W PowerShell $ () jest operatorem podwyrażenia, możesz go użyć w następujący sposób: Write-Output "The BITS service is $(Get-Service bits | select -ExpandProperty Stauts)"aby uzyskać status usługi BITS bez uprzedniego załadowania jej do zmiennej. Patrząc na proces substytucji, nie jest to dokładnie to samo, ale wciąż może rozwiązać problem, przed którym
stoisz
@Andy: Ta funkcja byłaby pomocna w przypadku zewnętrznych narzędzi wymagających operandów nazw plików . Przykładem jest psftp.exena transfery SFTP: jego -bopcja wymaga, aby zapewnić poleceń uruchamiane na serwerze poprzez plik , który jest niewygodny, jeśli po prostu chcesz uruchomić, powiedzmy mget *. Gdyby PowerShell miał podstawianie procesów, byłbyś w stanie zrobić coś takiego psftp.exe -l user -p password somehost -b <( "mget *" ).
mklement

Odpowiedzi:

4

Ta odpowiedź NIE jest dla Ciebie , jeśli:
- rzadko, jeśli w ogóle, potrzebujesz zewnętrznych interfejsów CLI (o co ogólnie warto starać się - komendy natywne PowerShell grają znacznie lepiej razem i nie potrzebują takiej funkcji).
- nie są zaznajomieni z podstawieniem procesu Basha.
Ta odpowiedź JEST dla Ciebie , jeśli:
- często korzystasz z zewnętrznych interfejsów CLI (czy to z przyzwyczajenia, czy z powodu braku (dobrych) alternatyw natywnych dla PowerShell), szczególnie podczas pisania skryptów.
- są przyzwyczajeni i doceniają to, co może zastąpić proces Bash.
- Aktualizacja : Teraz, gdy PowerShell jest także obsługiwany na platformach Unix, ta funkcja jest coraz bardziej interesująca - zobacz to żądanie funkcji na GitHub, co sugeruje, że PowerShell implementuje funkcję podobną do zastępowania procesów.

W świecie uniksowym, w Bash / Ksh / Zsh, podstawienie procesu oferuje traktowanie wyniku polecenia tak, jakby był to plik tymczasowy , który czyści się po sobie; np. cat <(echo 'hello')gdzie catwidzi wynik echopolecenia jako ścieżkę pliku tymczasowego zawierającego wynik polecenia .

Chociaż natywne polecenia programu PowerShell nie potrzebują takiej funkcji, może być przydatne podczas obsługi zewnętrznych interfejsów CLI .

Emulowanie funkcji w PowerShell jest uciążliwe , ale może być tego warte, jeśli często go potrzebujesz.

Wyobraź sobie funkcję o nazwie, cfktóra akceptuje blok skryptu, wykonuje blok i zapisuje dane wyjściowe w temp. plik tworzony na żądanie i zwraca temp. ścieżka pliku ; na przykład:

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

To prosty przykład, który nie ilustruje dobrze potrzeby takiej funkcji. Być może bardziej przekonującym scenariuszem jest użycie psftp.exedo przesyłania SFTP: jego użycie wsadowe (zautomatyzowane) wymaga udostępnienia pliku wejściowego zawierającego pożądane polecenia, podczas gdy takie polecenia można łatwo utworzyć w postaci ciągu w locie.

Aby być możliwie jak najbardziej kompatybilnym z zewnętrznymi narzędziami, temp. Plik powinien używać UTF-8 kodowanie bez BOM (znacznikiem kolejności bajtów) domyślnie, ale można poprosić o UTF-8 BOM się -BOM, w razie potrzeby.

Niestety aspekt automatycznego czyszczenia zastępowania procesów nie może być bezpośrednio emulowany, dlatego konieczne jest jawne wywołanie czyszczenia ; czyszczenie odbywa się przez wywołanie cf bez argumentów :

  • Do użytku interaktywnego można zautomatyzować czyszczenie, dodając wywołanie promptfunkcji czyszczenia do funkcji w następujący sposób ( promptfunkcja zwraca ciąg znaków zachęty , ale można jej także używać do wykonywania za sceną poleceń za każdym razem, gdy wyświetlany jest monit, podobnie jak polecenie Basha $PROMPT_COMMANDzmienna); w celu zapewnienia dostępności w dowolnej sesji interaktywnej dodaj następujące oraz definicję cfponiżej do swojego profilu PowerShell:

    "function prompt { cf 4>`$null; $((get-item function:prompt).definition) }" |
      Invoke-Expression
  • W przypadku użycia w skryptach , aby zapewnić wykonanie czyszczenia, blok, który używa cf- potencjalnie cały skrypt - musi być owinięty w try/ finallyblock, w którym cfbez argumentów wywoływane jest czyszczenie:

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

Oto implementacja : funkcja zaawansowana ConvertTo-TempFilei jej zwięzły alias cf:

Uwaga : Zastosowanie New-Module, które wymaga PSv3 +, do zdefiniowania funkcji za pomocą modułu dynamicznego zapewnia, że ​​nie może wystąpić konflikt zmiennych między parametrami funkcji a zmiennymi, do których odwołuje się wewnątrz bloku skryptu.

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

Zwróć uwagę na możliwość opcjonalnego określenia ścieżki wyjściowej [pliku] i / lub rozszerzenia nazwy pliku.

mklement
źródło
Pomysł, że kiedykolwiek będziesz musiał to zrobić, jest co najmniej wątpliwy i po prostu utrudniłby to, po prostu nie chcąc korzystać z PowerShell.
Jim B
1
@JimB: Używam go osobiście psftp.exe, co skłoniło mnie do napisania tego. Mimo że lepiej jest robić wszystko natywnie w PowerShell, nie zawsze jest to możliwe; wywoływanie zewnętrznych interfejsów CLI z programu PowerShell powoduje i będzie miało miejsce; jeśli często masz do czynienia z interfejsami CLI, które wymagają wprowadzania plików, które można (bardziej) łatwo zbudować w pamięci / za pomocą innego polecenia, funkcja w tej odpowiedzi może ułatwić ci życie.
mielement
Żartujesz? nic z tego nie jest wymagane. Muszę jeszcze znaleźć polecenie, które akceptuje tylko pliki z poleceniami parametrów. Jeśli chodzi o SFTP, proste wyszukiwanie pokazało mi 2 proste zestawy dodatków do natywnego wykonywania FTP w PowerShell.
Jim B
1
@JimB: Jeśli chcesz konstruktywnie kontynuować tę rozmowę, zmień ton.
mielement
2
@JimB GNU Diffutils diff działa tylko na plikach, na wypadek gdybyś był zainteresowany.
Pavel
2

Jeśli nie są ujęte w podwójny cudzysłów, $(...)zwraca obiekt PowerShell (a raczej to, co jest zwracane w dołączonym kodzie), najpierw oceniając załączony kod. Powinno to być odpowiednie do twoich celów („coś [I] może utknąć w środku wiersza poleceń”), zakładając, że wiersz poleceń to PowerShell.

Możesz to przetestować Get-Member, przesyłając różne wersje , a nawet wysyłając bezpośrednio.

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

Jak zauważyłeś, ujęte w podwójne cudzysłowy, „$ (...)” po prostu zwróci ciąg znaków.

W ten sposób, jeśli chcesz wstawić, powiedzmy, zawartość pliku bezpośrednio w linii, możesz użyć czegoś takiego:

Invoke-Command -ComputerName (Get-Content C:\Temp\Files\new1.txt) -ScriptBlock {<# something #>}
James Ruskin
źródło
To fantastyczna odpowiedź !!
GregL
To, co opisujesz, nie jest odpowiednikiem zastępowania procesu przez Basha. Podstawianie procesów jest przeznaczone do użycia z poleceniami, które wymagają operandów nazw plików ; to znaczy, wynik polecenia zawartego w podstawieniu procesu jest, luźno mówiąc, zapisywany do pliku tymczasowego i zwracana jest ścieżka tego pliku ; dodatkowo istnienie pliku jest ograniczone do polecenia, którego częścią jest podstawienie procesu. Gdyby PowerShell miał taką funkcję, można by oczekiwać, że zadziała coś takiego:Get-Content <(Get-ChildItem)
mklement
Popraw mnie, jeśli się mylę, a nie tego szukasz, ale nie Get-ChildItem | Get-Contentdziała idealnie? A może możesz spróbować Get-Content (Get-ChildItem).FullNametego samego efektu? Być może podchodzisz do tego z perspektywy, na którą wpływa inne podejście skryptowe.
James Ruskin
1
Tak, w dziedzinie PowerShell ta funkcja nie jest potrzebna; jest interesujący tylko do użytku z zewnętrznymi interfejsami CLI, które wymagają wprowadzania plików , i gdzie zawartość takich plików można łatwo zbudować za pomocą polecenia (PowerShell). Zobacz mój komentarz do pytania, aby zobaczyć prawdziwy przykład. Ty nigdy nie potrzebuje tej funkcji jest, ale dla ludzi, którzy muszą często nazywają CLIS zewnętrznych jest przedmiotem zainteresowania. Powinieneś przynajmniej przedłożyć odpowiedź, mówiąc, że demonstrujesz sposób działania programu PowerShell - w przeciwieństwie do tego, o co konkretnie poprosił PO - i dlaczego to robisz.
2016