最近、二重引用符が含まれているときはいつでも、PowerShellからGnuWin32を使用するときに問題が発生しています。
さらに調査したところ、PowerShellは適切にエスケープされていても、コマンドライン引数から二重引用符を削除しているようです。
_PS C:\Documents and Settings\Nick> echo '"hello"'
"hello"
PS C:\Documents and Settings\Nick> echo.exe '"hello"'
hello
PS C:\Documents and Settings\Nick> echo.exe '\"hello\"'
"hello"
_
PowerShellのechoコマンドレットに渡されると二重引用符が存在しますが、echo.exeに引数として渡されると、二重引用符はバックスラッシュ( PowerShellのエスケープ文字はバックスラッシュではなくバックティックですが)。
これは私にはバグのようです。エスケープされた正しい文字列をPowerShellに渡す場合、PowerShellはコマンドを呼び出すために必要なエスケープをすべて処理する必要があります。
ここで何が起こっているのですか?
現時点での修正は、次のルールに従ってコマンドライン引数をエスケープすることです(これは、PowerShellが.exeファイルを呼び出すために使用するCreateProcess
API呼び出しで使用されているようです)。
\"
_-> _"
_\\\\\"
_-> _\\"
_\\
_-> _\\
_Windows APIエスケープ文字列の二重引用符をPowerShellにエスケープするために、二重引用符をさらにエスケープする必要がある場合があることに注意してください。
GnuWin32のecho.exeを使用した例をいくつか示します。
_PS C:\Documents and Settings\Nick> echo.exe "\`""
"
PS C:\Documents and Settings\Nick> echo.exe "\\\\\`""
\\"
PS C:\Documents and Settings\Nick> echo.exe "\\"
\\
_
複雑なコマンドラインパラメータを渡す必要がある場合、これはすぐに地獄になる可能性があると思います。もちろん、これはCreateProcess()
またはPowerShellのドキュメントに記載されていません。
また、これは、二重引用符付きの引数を.NET関数またはPowerShellコマンドレットに渡す必要がないことに注意してください。そのためには、二重引用符をPowerShellにエスケープするだけです。
既知のもの :
引用符で囲まれた文字列を必要とするアプリケーションにパラメータを渡すのは非常に難しいです。私はこの質問をIRCでPowerShellエキスパートの「余裕のある」人に依頼しました。誰かが方法を理解するのに1時間かかりました(私は最初、ここでは投稿することは単に不可能であるということでした)。これは、sqlcmdの実行などの単純なことを実行できないため、PowerShellの汎用シェルとしての機能を完全に破壊します。コマンドシェルの最大のジョブは、コマンドラインアプリケーションを実行する必要があります...例として、 SQL Server 2008のSqlCmdには、一連のname:valueパラメーターを取る-vパラメーターがあります。値にスペースが含まれている場合は、引用符で囲む必要があります...
...このアプリケーションを正しく呼び出すためのコマンドラインを作成する単一の方法はないため、物事を引用したりエスケープする4つまたは5つの異なる方法をすべて習得した後でも、どちらがいつ機能するかを推測しています...または、シェルでcmdを実行するだけで完了できます。
私は個人的には '\'を使用してPowerShellでエスケープすることを避けています。これは、技術的にはシェルのエスケープ文字ではないためです。私はそれで予測できない結果を得ました。二重引用符で囲まれた文字列では、""
を使用して埋め込み二重引用符を取得するか、バックティックでエスケープできます。
PS C:\Users\Droj> "string ""with`" quotes"
string "with" quotes
同じことが単一引用符にも当てはまります。
PS C:\Users\Droj> 'string ''with'' quotes'
string 'with' quotes
パラメータを外部プログラムに送信することの奇妙な点は、追加レベルの見積もり評価があることです。これがバグかどうかはわかりませんが、 Start-Process を使用して引数を渡した場合の動作は同じであるため、変更されることはないと思います。 Start-Processは、引数の配列を受け取ります。これにより、実際に送信される引数の数に関して少しわかりやすくなりますが、それらの引数は余分に評価されるようです。
したがって、配列がある場合は、引用符を埋め込むように引数値を設定できます。
PS C:\cygwin\home\Droj> $aa = 'arg="foo"', 'arg=""""bar""""'
PS C:\cygwin\home\Droj> echo $aa
arg="foo"
arg=""""bar""""
'bar'引数は、追加の隠された評価をカバーするのに十分です。その値を二重引用符でコマンドレットに送信し、その結果を二重引用符で再度送信するかのようです。
PS C:\cygwin\home\Droj> echo "arg=""""bar""""" # level one
arg=""bar""
PS C:\cygwin\home\Droj> echo "arg=""bar""" # hidden level
arg="bar"
これらの引数は、 'echo'/'write-output'のようなコマンドレットに対するのと同じように、外部コマンドにそのまま渡されることを期待しますが、その隠されたレベルのため、そうではありません。
PS C:\cygwin\home\Droj> $aa = 'arg="foo"', 'arg=""""bar""""'
PS C:\cygwin\home\Droj> start c:\cygwin\bin\echo $aa -nonew -wait
arg=foo arg="bar"
その正確な理由はわかりませんが、動作は、文字列を再解析するカバーの下で別の文書化されていない手順が実行されているかのようです。たとえば、配列をコマンドレットに送信しても同じ結果が得られますが、invoke-expression
を使用して解析レベルを追加します。
PS C:\cygwin\home\Droj> $aa = 'arg="foo"', 'arg=""""bar""""'
PS C:\cygwin\home\Droj> iex "echo $aa"
arg=foo
arg="bar"
...これらの引数を外部のCygwinインスタンスの 'echo.exe'に送信すると、まさにこれが得られます。
PS C:\cygwin\home\Droj> c:\cygwin\bin\echo 'arg="foo"' 'arg=""""bar""""'
arg=foo arg="bar"
受け入れられた回答 に示されているように、CMDを使用して問題を解決することはできませんでした。
私にとって良い解決策は、すべての引数を含む単一の完全な文字列ではなく、文字列の配列としてコマンドラインを構造化することでした。次に、その配列をバイナリ呼び出しの引数として渡すだけです。
$args = New-Object System.Collections.ArrayList
$args.Add("-U") | Out-Null
$args.Add($cred.UserName) | Out-Null
$args.Add("-P") | Out-Null
$args.Add("""$($cred.Password)""")
$args.Add("-i") | Out-Null
$args.Add("""$SqlScriptPath""") | Out-Null
& SQLCMD $args
その場合、引数を囲む二重引用符は、呼び出されたコマンドに適切に渡されます。
必要に応じて、 PowerShell Community Extensions からEchoArgsを使用してテストおよびデバッグできます。
この記事の執筆時点では、PowerShellの最近のバージョンで修正されているようで、心配する必要はありません。
それでもこの問題が発生すると思われる場合は、PowerShellを呼び出すプログラムなど、他の問題に関連している可能性があるため、コマンドプロンプトまたは [〜#〜 ] ise [〜#〜] 、別の場所でデバッグする必要があります。
たとえば、Process.Start
を使用してC#コードからPowerShellスクリプトを実行すると、引用符が消える問題を調査したときに、この質問が見つかりました。問題は実際にC#Process Startには二重引用符のある引数が必要です-それらは消えます。