Bagaimana Anda menjalankan perintah asli sewenang-wenang dari sebuah string?

208

Saya dapat menyatakan kebutuhan saya dengan skenario berikut: Tulis fungsi yang menerima string untuk dijalankan sebagai perintah asli.

Itu tidak terlalu jauh dari sebuah ide: jika Anda berinteraksi dengan utilitas baris perintah lainnya dari tempat lain di perusahaan yang memasok Anda dengan perintah untuk menjalankan kata demi kata. Karena Anda tidak mengontrol perintah, Anda harus menerima perintah yang valid sebagai masukan . Ini adalah masalah utama yang tidak bisa saya atasi dengan mudah:

  1. Perintah mungkin menjalankan program yang hidup di jalur dengan spasi di dalamnya:

    $command = '"C:\Program Files\TheProg\Runit.exe" Hello';
  2. Perintah mungkin memiliki parameter dengan spasi di dalamnya:

    $command = 'echo "hello world!"';
  3. Perintah ini mungkin memiliki kutu tunggal dan ganda:

    $command = "echo `"it`'s`"";

Apakah ada setiap cara yang bersih untuk mencapai ini? Saya hanya bisa menyusun solusi yang boros dan jelek, tetapi untuk bahasa scripting saya merasa ini harus sederhana.

Johnny Kauffman
sumber

Jawaban:

318

Invoke-Expression, juga disebut sebagai iex. Berikut ini akan bekerja pada contoh Anda # 2 dan # 3:

iex $command

Beberapa string tidak akan berjalan apa adanya, seperti contoh Anda # 1 karena exe dalam tanda kutip. Ini akan berfungsi apa adanya, karena isi string persis seperti yang Anda jalankan langsung dari command prompt Powershell:

$command = 'C:\somepath\someexe.exe somearg'
iex $command

Namun, jika exe dalam tanda kutip, Anda perlu bantuan &untuk membuatnya berjalan, seperti dalam contoh ini, seperti dijalankan dari commandline:

>> &"C:\Program Files\Some Product\SomeExe.exe" "C:\some other path\file.ext"

Dan kemudian dalam naskah:

$command = '"C:\Program Files\Some Product\SomeExe.exe" "C:\some other path\file.ext"'
iex "& $command"

Kemungkinan, Anda bisa menangani hampir semua kasus dengan mendeteksi apakah karakter pertama dari string perintah ", seperti dalam implementasi naif ini:

function myeval($command) {
    if ($command[0] -eq '"') { iex "& $command" }
    else { iex $command }
}

Tetapi Anda mungkin menemukan beberapa kasus lain yang harus dipanggil dengan cara yang berbeda. Dalam hal ini, Anda harus menggunakan keduanyatry{}catch{} , mungkin untuk tipe / pesan pengecualian tertentu, atau memeriksa string perintah.

Jika Anda selalu menerima jalur absolut alih-alih jalur relatif, Anda seharusnya tidak memiliki banyak kasus khusus, jika ada, di luar 2 jalur di atas.

Joel B Fant
sumber
3
Aliasing itu hebat. Ingat, jika Anda pindah ke komputer lain atau mengirim skrip itu ke orang lain, alias itu mungkin tidak akan diatur. Lebih suka nama lengkap fungsi PowerShell.
Doug Finke
1
@ Doug: Sebagian besar waktu saya melakukan itu atau menggunakan alias bawaan (terutama untuk singkatnya pada commandline). Hal- evalhal setengah bercanda karena itulah yang disebut dalam begitu banyak bahasa scripting lainnya, dan ini bukan pertanyaan pertama yang saya lihat di mana seseorang tidak tahu invoke-expression. Dan kasus OP terdengar seperti skrip in-house saja.
Joel B Fant
Saya sudah mencoba ini, tetapi tidak bekerja dengan: $ command = '"C: \ Program Files \ Windows Media Player \ mplayer2.exe" "H: \ Audio \ Music \ Stevie Wonder \ Stevie Wonder - Superstition.mp3" '
Johnny Kauffman
3
Anda mungkin harus meletakkan "&" atau "." tandatangani sebelum perintah aktual jika itu bukan powershell-asli, misalnyaInvoke-Expression "& $command"
Torbjörn Bergstedt
Torbjörn Bergstedt menang! Ekstra ajaib "&" telah menyelesaikan masalah saya! Hasilnya, saya senang dan bingung.
Johnny Kauffman
19

Silakan lihat juga laporan Microsoft Connect ini pada dasarnya, betapa sulitnya menggunakan PowerShell untuk menjalankan perintah shell (oh, ironi).

http://connect.microsoft.com/PowerShell/feedback/details/376207/

Mereka menyarankan menggunakan --%sebagai cara untuk memaksa PowerShell untuk berhenti mencoba menafsirkan teks ke kanan.

Sebagai contoh:

MSBuild /t:Publish --% /p:TargetDatabaseName="MyDatabase";TargetConnectionString="Data Source=.\;Integrated Security=True" /p:SqlPublishProfilePath="Deploy.publish.xml" Database.sqlproj
Luke Puplett
sumber
1
Ah, dan Microsoft memecahkan internet dan tautannya tidak berlaku lagi.
Johan Boulé
1
Tautan di atas ada di archive.org di web.archive.org/web/20131122050220/http://…
mwfearnley
4

Jawaban yang diterima tidak berfungsi untuk saya ketika mencoba mem-parsing registri untuk mencopot pemasangan string, dan menjalankannya. Ternyata saya tidak membutuhkan panggilan untuk Invoke-Expressionsemua.

Saya akhirnya menemukan template yang bagus ini untuk melihat cara menjalankan string uninstall:

$path = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
$app = 'MyApp'
$apps= @{}
Get-ChildItem $path | 
    Where-Object -FilterScript {$_.getvalue('DisplayName') -like $app} | 
    ForEach-Object -process {$apps.Set_Item(
        $_.getvalue('UninstallString'),
        $_.getvalue('DisplayName'))
    }

foreach ($uninstall_string in $apps.GetEnumerator()) {
    $uninstall_app, $uninstall_arg = $uninstall_string.name.split(' ')
    & $uninstall_app $uninstall_arg
}

Ini berfungsi untuk saya, yaitu karena $appaplikasi in-house yang saya tahu hanya akan memiliki dua argumen. Untuk string uninstall yang lebih kompleks, Anda mungkin ingin menggunakan operator gabungan . Juga, saya baru saja menggunakan hash-map, tapi sungguh, Anda mungkin ingin menggunakan array.

Juga, jika Anda memiliki banyak versi dari aplikasi yang sama diinstal, uninstaller ini akan menggilir semuanya sekaligus, yang membingungkan MsiExec.exe, jadi ada itu juga.

Droogan
sumber
1

Jika Anda ingin menggunakan operator panggilan, argumen bisa berupa array yang disimpan dalam variabel:

$prog = 'c:\windows\system32\cmd.exe'
$myargs = '/c','dir','/x'
& $prog $myargs

Operator panggilan juga berfungsi dengan objek ApplicationInfo.

$prog = get-command cmd
$myargs = -split '/c dir /x'
& $prog $myargs
js2010
sumber