22

次の内容のバッチファイルがあります。

echo %~dp0
CD Arvind
echo %~dp0

のディレクトリ値を変更した後も%~dp0同じです。ただし、このバッチファイルをCSharpプログラムから実行すると、CD%~dp0の後で値が変わります。新しいディレクトリを指すようになりました。以下は私が使用するコードです:

Directory.SetCurrentDirectory(//Dir where batch file resides);
ProcessStartInfo ProcessInfo;
Process process = new Process();
ProcessInfo = new ProcessStartInfo("mybatfile.bat");
ProcessInfo.UseShellExecute = false;
ProcessInfo.RedirectStandardOutput = true;
process = Process.Start(ProcessInfo);
process.WaitForExit();
ExitCode = process.ExitCode;
process.Close();

同じスクリプトを異なる方法で実行した場合の出力に違いがあるのはなぜですか?

ここで何かが恋しいですか?

4

6 に答える 6

15

この質問はこの点に関する議論を開始し、その理由を特定するためにいくつかのテストが行​​われました。そのため、内部でいくつかのデバッグを行った後cmd.exe... (これは32 ビット Windows XP cmd.exe 用ですが、動作は新しいシステム バージョンでも一貫しているため、おそらく同じまたは類似のコードが使用されます)

内部ジェブの答えが記載されています

It's a problem with the quotes and %~0.
cmd.exe handles %~0 in a special way

ここでジェブは正しいです。

実行中のバッチ ファイルの現在のコンテキスト内には、現在のバッチ ファイルへの参照があります。これは、実行中のバッチ ファイルのフル パスとファイル名を含む「変数」です。

変数にアクセスすると、その値は使用可能な変数のリストから取得されますが、要求された変数が%0であり、何らかの修飾子が要求されている (~が使用されている) 場合は、実行中のバッチ参照 "変数" のデータが使用されます。

しかし、 を使用~すると、変数に別の効果があります。値が引用符で囲まれている場合、引用符は削除されます。そして、コードにバグがあります。次のようにコーディングされています(ここでは、アセンブラを擬似コードに簡略化しています)

value = varList[varName]
if (value && value[0] == quote ){
    value = unquote(value)
} else if (varName == '0') {
    value = batchFullName
}

はい、これは、バッチ ファイルが引用されている場合、 の最初の部分ifが実行され、バッチ ファイルへの完全な参照が使用されないことを意味します。代わりに、取得された値は、呼び出し時にバッチ ファイルを参照するために使用される文字列です。

その後どうなりますか?バッチ ファイルがフル パスで呼び出された場合、問題はありません。ただし、呼び出しでフル パスが使用されていない場合は、バッチ呼び出しに存在しないパス内の要素を取得する必要があります。この検索で​​は相対パスを想定しています。

単純なバッチ ファイル ( test.cmd)

@echo off
echo %~f0

test(拡張子なし、引用符なし)を使用して呼び出すと、取得しますc:\somewhere\test.cmd

"test"(拡張子なし、引用符なし)を使用して呼び出すと、取得しますc:\somewhere\test

最初のケースでは、引用符なしで、正しい内部値が使用されます。2 番目のケースでは、呼び出しが引用符で囲まれているため、バッチ ファイルの呼び出しに使用される文字列 ( "test") は引用符で囲まれずに使用されます。フル パスを要求しているため、 と呼ばれるものへの相対参照と見なされtestます。

これが理由です。の解き方?

C# コードから

  • 引用符を使用しないでください:cmd /c batchfile.cmd

  • 引用符が必要な場合は、バッチ ファイルへの呼び出しでフル パスを使用します。その方法%0には、必要なすべての情報が含まれています。

バッチファイルから

バッチ ファイルは、任意の場所から任意の方法で呼び出すことができます。現在のバッチ ファイルの情報を取得する唯一の信頼できる方法は、サブルーチンを使用することです。修飾子 ( ~) が使用されている%0場合、 は内部の「変数」を使用してデータを取得します。

@echo off
    setlocal enableextensions disabledelayedexpansion

    call :getCurrentBatch batch
    echo %batch%

    exit /b

:getCurrentBatch variableName
    set "%~1=%~f0"
    goto :eof

これは、引用符の有無にかかわらず、ファイルの呼び出し方法に関係なく、現在のバッチ ファイルへのフル パスをコンソールにエコーします。

: なぜ機能するのですか? %~f0サブルーチン内の参照が異なる値を返すのはなぜですか? サブルーチン内からアクセスされるデータは同じではありません。がcall実行されると、新しいバッチ ファイル コンテキストがメモリ内に作成され、内部の「変数」を使用してこのコンテキストが初期化されます。

于 2014-11-10T19:52:46.250 に答える
5

なぜこれが奇妙に振る舞うかを説明しようと思います。かなり技術的で長ったらしい話なので、要約したままにします。この問題の出発点は次のとおりです。

   ProcessInfo.UseShellExecute = false;

このステートメントを省略したり、 trueを割り当てたりすると、期待どおりに機能することがわかります。

Windows では、プログラムを開始する基本的な方法として、ShellExecuteEx() と CreateProcess() の 2 つが用意されています。UseShellExecute プロパティは、これら 2 つの間で選択します。前者は「スマートでフレンドリーな」バージョンで、たとえばシェルの動作方法について多くのことを知っています。これが、たとえば「foo.doc」のような任意のファイルへのパスを渡すことができる理由です。.doc ファイルのファイルの関連付けを調べて、foo.doc を開く方法を知っている .exe ファイルを見つける方法を知っています。

CreateProcess()は低レベルの winapi 関数であり、ネイティブ カーネル関数 (NtCreateProcess) との間にはほとんど接着剤がありません。関数の最初の 2 つの引数 と に注意してくださいlpApplicationNamelpCommandLineこれらを 2 つの ProcessStartInfo プロパティに簡単に一致させることができます。

CreateProcess() がプログラムを開始する 2 つの異なる方法を提供するほど目に見えないもの。1 つ目は、lpApplicationName を空の文字列に設定したままにし、lpCommandLine を使用してコマンド ライン全体を提供する場所です。これにより、CreateProcess が使いやすくなり、実行可能ファイルが見つかった後、アプリケーション名が完全なパスに自動的に展開されます。たとえば、「cmd.exe」は「c:\windows\system32\cmd.exe」に展開されます。ただし、lpApplicationName 引数を使用する場合はこれを行わず、文字列をそのまま渡します。

この癖は、コマンド ラインの正確な指定方法に依存するプログラムに影響を与えます。argv[0]特に C プログラムの場合、実行可能ファイルへのパスが含まれていると想定します。そしてそれは に影響を与え%~dp0、それもその引数を使用します。そして、それが機能するパスは、たとえば「c:\temp\mybatfile.bat」ではなく「mybatfile.bat」であるため、あなたの場合はヒラメします。これにより、「c:\temp」ではなく現在のディレクトリが返されます。

したがって、あなたがすべきことは、.NET Framework のドキュメントでは完全に文書化されていないことですが、ファイルに完全なパス名を渡すのはあなた次第ですしたがって、適切なコードは次のようになります。

   string path = @"c:\temp";   // Dir where batch file resides
   Directory.SetCurrentDirectory(path);
   string batfile = System.IO.Path.Combine(path, "mybatfile.bat");
   ProcessStartInfo = new ProcessStartInfo(batfile);

そして、期待どおりに展開することがわかります%~dp0path現在のディレクトリの代わりに使用しています。

于 2014-11-10T16:16:28.513 に答える
4

ジョーイの提案が役に立ちました。交換するだけで

ProcessInfo = new ProcessStartInfo("mybatfile.bat"); 

ProcessInfo = new ProcessStartInfo("cmd", "/c " + "mybatfile.bat");

トリックをしました。

于 2012-08-27T12:36:19.420 に答える
3

引用符と の問題%~0です。

cmd.exe は%~0特別な方法で処理します ( 以外%~1)。
が相対ファイル名であるかどうかを確認%0し、開始ディレクトリを先頭に追加します。

ファイルが見つかった場合はこの組み合わせを使用し、そうでない場合は実際のディレクトリを先頭に追加します。
しかし、名前が引用符で始まる場合、ディレクトリを先頭に追加する前に、引用符を削除できないようです。

cmd /c myBatch.batthenmyBatch.batは引用符なしで呼び出されるため、これが機能する理由です。
完全修飾パスを使用してバッチを開始することもできます。これも機能します。

または、ディレクトリを変更する前に、バッチにフル パスを保存します。

test.batcmd.exe の問題を示す小さな例

@echo off
setlocal
echo %~fx0 %~fx1
cd ..
echo %~fx0 %~fx1

(C:\ tempで)経由で呼び出します

test test

出力は

C:\temp\test.bat C:\temp\test
C:\temp\test.bat C:\test

そのため、cmd.exeを見つけることができましたが、開始ディレクトリを先頭に追加する test.batためだけです。%~fx0

経由で呼び出す場合

"test" "test"

で失敗します

C:\temp\test C:\temp\test
C:\test C:\test

cmd.exeディレクトリが変更される前でもバッチファイルを見つけることができず、名前を完全な名前に展開することはできませんc:\temp\test.bat

編集:FixIt、%~0引用符がある場合でもフルネームを取得

関数呼び出しによる回避策があります。

@echo off
echo This can be wrong %~f0
call :getCorrectName
exit /b

:getCorrectName
echo Here the value is correct %~f0
exit /b
于 2014-11-10T14:23:16.940 に答える
-3

ProcessStart によって呼び出されるバッチ内の各新しい行は、独立して新しい cmd コマンドと見なされます。

たとえば、次のようにしてみます。

echo %~dp0 && CD Arvind && echo %~dp0

できます。

于 2012-08-27T12:28:03.407 に答える