22 Stimmen

Was ist der Grund dafür, dass sich der Pfad einer Batchdatei, auf die mit %~dp0 verwiesen wird, bei einem Verzeichniswechsel manchmal ändert?

Ich habe eine Batch-Datei mit folgendem Inhalt:

echo %~dp0
CD Arvind
echo %~dp0

Auch nach der Änderung des Verzeichniswertes von %~dp0 ist die gleiche. Wenn ich jedoch diese Batch-Datei aus dem CSharp-Programm ausführe, wird der Wert von %~dp0 Änderungen nach CD . Er verweist nun auf das neue Verzeichnis. Im Folgenden finden Sie den von mir verwendeten Code:

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();

Warum gibt es einen Unterschied in der Ausgabe, wenn dasselbe Skript auf verschiedenen Wegen ausgeführt wird?

Habe ich hier etwas übersehen?

16voto

MC ND Punkte 69107

Diese Frage begann die Diskussion über diesen Punkt, und es wurden einige Tests durchgeführt, um herauszufinden, warum. Nach einiger Fehlersuche in cmd.exe ... (dies ist für eine 32 bit Windows XP cmd.exe aber da das Verhalten auf neueren Systemversionen konsistent ist, wird wahrscheinlich der gleiche oder ein ähnlicher Code verwendet)

In der Antwort von Jeb heißt es

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

und hier hat Jeb recht.

Im aktuellen Kontext der laufenden Batch-Datei gibt es einen Verweis auf die aktuelle Batch-Datei, eine "Variable", die den vollständigen Pfad und Dateinamen der laufenden Batch-Datei enthält.

Wenn auf eine Variable zugegriffen wird, wird ihr Wert aus einer Liste verfügbarer Variablen abgerufen, aber wenn die angeforderte Variable %0 und es wurde ein Modifikator angefordert ( ~ verwendet wird), werden die Daten in der laufenden Chargenreferenz "Variable" verwendet.

Aber die Verwendung von ~ hat eine weitere Auswirkung auf die Variablen. Wenn der Wert in Anführungszeichen steht, werden die Anführungszeichen entfernt. Und hier gibt es einen Fehler im Code. Er ist etwa so kodiert (hier vereinfacht Assembler zu Pseudocode)

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

Und ja, das bedeutet, dass, wenn die Batch-Datei zitiert wird, der erste Teil der if ausgeführt und der vollständige Verweis auf die Stapeldatei wird nicht verwendet. Stattdessen wird die Zeichenfolge abgerufen, die beim Aufruf der Stapeldatei als Verweis auf diese verwendet wird.

Was geschieht dann? Wenn beim Aufruf der Batch-Datei der vollständige Pfad verwendet wurde, gibt es kein Problem. Wird jedoch beim Aufruf nicht der vollständige Pfad verwendet, muss jedes Element des Pfades, das im Batch-Aufruf nicht vorhanden ist, abgerufen werden. Bei diesem Abruf wird von relativen Pfaden ausgegangen.

Eine einfache Batch-Datei ( test.cmd )

@echo off
echo %~f0

Beim Aufruf mit test (keine Erweiterung, keine Anführungszeichen), erhalten wir c:\somewhere\test.cmd

Beim Aufruf mit "test" (keine Erweiterung, Anführungszeichen), erhalten wir c:\somewhere\test

Im ersten Fall, ohne Anführungszeichen, wird der korrekte interne Wert verwendet. Im zweiten Fall, da der Aufruf in Anführungszeichen steht, wird die Zeichenkette für den Aufruf der Batch-Datei ( "test" ) ist nicht zitiert und wird verwendet. Da wir einen vollständigen Pfad anfordern, wird er als relativer Verweis auf etwas namens test .

Das ist der Grund. Wie ist es zu lösen?

Aus dem C#-Code

  • Verwenden Sie keine Anführungszeichen: cmd /c batchfile.cmd

  • Wenn Anführungszeichen erforderlich sind, verwenden Sie den vollständigen Pfad im Aufruf der Batch-Datei. Auf diese Weise %0 enthält alle erforderlichen Informationen.

Aus der Stapeldatei

Die Stapeldatei kann in beliebiger Weise von jedem Ort aus aufgerufen werden. Die einzige zuverlässige Möglichkeit, die Informationen über die aktuelle Batch-Datei abzurufen, ist die Verwendung eines Unterprogramms. Wenn ein Modifikator ( ~ ) verwendet wird, wird die %0 verwendet die interne "Variable", um die Daten zu erhalten.

@echo off
    setlocal enableextensions disabledelayedexpansion

    call :getCurrentBatch batch
    echo %batch%

    exit /b

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

Dies gibt den vollständigen Pfad zur aktuellen Batch-Datei auf der Konsole aus, unabhängig davon, wie Sie die Datei aufrufen, mit oder ohne Anführungszeichen.

Hinweis : Warum funktioniert es? Warum die %~f0 Referenz innerhalb eines Unterprogramms einen anderen Wert zurückgeben? Die Daten, auf die innerhalb der Unterroutine zugegriffen wird, sind nicht dieselben. Wenn die call ausgeführt wird, wird ein neuer Stapeldateikontext im Speicher erstellt, und die interne "Variable" wird zur Initialisierung dieses Kontexts verwendet.

6voto

Hans Passant Punkte 894572

Ich werde versuchen zu erklären, warum sich dies so seltsam verhält. Die Geschichte ist ziemlich technisch und langatmig, ich werde versuchen, sie kurz zu halten. Der Ausgangspunkt für dieses Problem ist:

   ProcessInfo.UseShellExecute = false;

Sie werden sehen, dass Sie, wenn Sie diese Anweisung weglassen oder die wahr dass es so funktioniert, wie Sie es erwartet haben.

Windows bietet zwei grundlegende Möglichkeiten, Programme zu starten: ShellExecuteEx() und CreateProcess(). Die Eigenschaft UseShellExecute wählt zwischen diesen beiden aus. Die erstere ist die "intelligente und freundliche" Version, sie weiß zum Beispiel viel über die Funktionsweise der Shell. Deshalb können Sie zum Beispiel den Pfad zu einer beliebigen Datei wie "foo.doc" übergeben. Sie weiß, wie man die Dateizuordnung für .doc-Dateien nachschlägt und die .exe-Datei findet, die weiß, wie man foo.doc öffnet.

CreateProcess() ist die Low-Level-Winapi-Funktion, zwischen ihr und der nativen Kernel-Funktion (NtCreateProcess) besteht nur eine geringe Verbindung. Beachten Sie die ersten beiden Argumente der Funktion, lpApplicationName y lpCommandLine können Sie sie leicht mit den beiden ProcessStartInfo-Eigenschaften abgleichen.

Was nicht so offensichtlich ist, ist, dass CreateProcess() zwei verschiedene Möglichkeiten bietet, ein Programm zu starten. Die erste Möglichkeit ist, lpApplicationName auf eine leere Zeichenkette zu setzen und lpCommandLine zu verwenden, um die gesamte Befehlszeile anzugeben. Das macht CreateProcess freundlich, es erweitert den Anwendungsnamen automatisch zum vollständigen Pfad, nachdem es die ausführbare Datei gefunden hat. So wird zum Beispiel "cmd.exe" zu "c: \windows\system32\cmd.exe ". Aber es tut no dies tun, wenn Sie das Argument lpApplicationName verwenden, wird die Zeichenfolge so übergeben, wie sie ist.

Diese Eigenart wirkt sich auf Programme aus, die von der genauen Angabe der Befehlszeile abhängen. Insbesondere für C-Programme gilt, dass sie davon ausgehen, dass argv[0] enthält den Pfad zu ihrer ausführbaren Datei. Und es hat Auswirkungen auf %~dp0 verwendet auch sie dieses Argument. Und scheitert in Ihrem Fall, da der Pfad, mit dem es arbeitet, einfach "mybatfile.bat" ist, anstatt, sagen wir, "c: \temp\mybatfile.bat ". Dadurch wird das aktuelle Verzeichnis anstelle von "c." zurückgegeben: \temp ".

Was sind also Ihre angeblich zu tun, und dies ist in der .NET Framework-Dokumentation völlig unterdokumentiert, ist, dass es nun an Ihnen liegt, die vollständig Pfadname für die Datei. Der richtige Code sollte also wie folgt aussehen:

   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);

Und Sie werden sehen, dass %~dp0 jetzt wie erwartet expandiert. Es verwendet path anstelle des aktuellen Verzeichnisses.

4voto

sunny days Punkte 827

Joeys Vorschlag hat geholfen. Einfach durch Ersetzen

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

mit

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

hat den Zweck erfüllt.

3voto

jeb Punkte 74279

Es ist ein Problem mit den Anführungszeichen und %~0 .

cmd.exe Griffe %~0 auf eine besondere Weise (anders als %~1 ).
Sie prüft, ob %0 ein relativer Dateiname ist, dann wird ihm das Startverzeichnis vorangestellt.

Wenn dort eine Datei gefunden wird, wird diese Kombination verwendet, andernfalls wird ihr das aktuelle Verzeichnis vorangestellt.
Wenn der Name jedoch mit einem Anführungszeichen beginnt, werden die Anführungszeichen anscheinend nicht entfernt, bevor das Verzeichnis vorangestellt wird.

Das ist der Grund, warum cmd /c myBatch.bat funktioniert, denn dann myBatch.bat wird ohne Anführungszeichen aufgerufen.
Sie können den Batch auch mit einem voll qualifizierten Pfad starten, dann funktioniert es auch.

Oder Sie speichern den vollständigen Pfad in Ihrem Stapel, bevor Sie das Verzeichnis wechseln.

Eine kleine test.bat kann die Probleme von cmd.exe demonstrieren

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

Rufen Sie es über (in C:\temp )

test test

Die Ausgabe sollte sein

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

Also, cmd.exe konnte finden test.bat , aber nur für %~fx0 wird das Startverzeichnis vorangestellt.

Im Falle des Aufrufs über

"test" "test"

Sie scheitert mit

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

cmd.exe nicht in der Lage ist, die Batchdatei zu finden, bevor das Verzeichnis geändert wurde, kann es den Namen nicht auf den vollständigen Namen von c:\temp\test.bat

EDIT: FixIt, den vollständigen Namen abrufen, auch wenn %~0 hat Zitate

Es gibt einen Workaround mit einem Funktionsaufruf.

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

:getCorrectName
echo Here the value is correct %~f0
exit /b

1voto

Mofi Punkte 42073

Kommandozeileninterpreter cmd.exe hat eine Fehler im Code, um den Pfad der Batch-Datei zu erhalten, wenn die Batch-Datei mit doppelten Anführungszeichen und mit einem Pfad relativ zum aktuellen Arbeitsverzeichnis aufgerufen wurde.

Ein Verzeichnis erstellen C:\Temp\TestDir . Erstellen Sie in diesem Verzeichnis eine Datei mit dem Namen PfadTest.bat und fügen Sie in diese Batch-Datei den folgenden Code ein:

@echo off
set "StartIn=%CD%"
set "BatchPath=%~dp0"
echo Batch path before changing working directory is: %~dp0
cd ..
echo Batch path after  changing working directory is: %~dp0
echo Saved path after  changing working directory is: %BatchPath%
cd "%StartIn%"
echo Batch path after restoring working directory is: %~dp0

Öffnen Sie dann ein Eingabeaufforderungsfenster und setzen Sie das Arbeitsverzeichnis auf C:\Temp\TestDir mit Hilfe des Befehls:

cd /D C:\Temp\TestDir

Jetzt anrufen Test.bat auf folgende Weise:

  1. PathTest
  2. PathTest.bat
  3. .\PathTest
  4. .\PathTest.bat
  5. ..\TestDir\PathTest
  6. ..\TestDir\PathTest.bat
  7. \Temp\TestDir\PathTest
  8. \Temp\TestDir\PathTest.bat
  9. C:\Temp\TestDir\PathTest
  10. C:\Temp\TestDir\PathTest.bat

Die Leistung beträgt das Vierfache C:\Temp\TestDir\ wie erwartet für alle 10 Testfälle.

Die Testfälle 7 und 8 starten die Batch-Datei mit einem Pfad relativ zum Root-Verzeichnis des aktuellen Laufwerks.

Schauen wir uns nun die Ergebnisse an, wenn wir dasselbe wie zuvor tun, aber den Namen der Batch-Datei in Anführungszeichen setzen.

  1. "PathTest"
  2. "PathTest.bat"
  3. ".\PathTest"
  4. ".\PathTest.bat"
  5. "..\TestDir\PathTest"
  6. "..\TestDir\PathTest.bat"
  7. "\Temp\TestDir\PathTest"
  8. "\Temp\TestDir\PathTest.bat"
  9. "C:\Temp\TestDir\PathTest"
  10. "C:\Temp\TestDir\PathTest.bat"

Die Leistung beträgt das Vierfache C:\Temp\TestDir\ wie für die Testfälle 5 bis 10 erwartet.

Für die Testfälle 1 bis 4 lautet die zweite Ausgabezeile jedoch nur C:\Temp\ anstelle von C:\Temp\TestDir\ .

Verwenden Sie nun cd .. um das Arbeitsverzeichnis zu wechseln C:\Temp und laufen PfadTest.bat wie folgt:

  1. "TestDir\PathTest.bat"
  2. ".\TestDir\PathTest.bat"
  3. "\Temp\TestDir\PathTest.bat"
  4. "C:\Temp\TestDir\PathTest.bat"

Das Ergebnis der zweiten Ausgabe für die Testfälle 1 und 2 lautet C:\TestDir\ die es gar nicht gibt.

Wenn Sie die Batch-Datei ohne die Anführungszeichen starten, erhalten Sie für alle 4 Testfälle die richtige Ausgabe.

Dies macht deutlich, dass das Verhalten durch einen Fehler verursacht wird.

Immer dann, wenn eine Batch-Datei mit Anführungszeichen und mit einem Pfad relativ zum aktuellen Arbeitsverzeichnis beim Start gestartet wird, %~dp0 ist nicht zuverlässig, wenn es darum geht, den Pfad der Batch-Datei zu ermitteln, wenn das aktuelle Arbeitsverzeichnis während der Batch-Ausführung geändert wird.

Dieser Fehler wurde auch an Microsoft gemeldet, und zwar laut Windows-Shell-Fehler bei der Auflösung von %~dp0 .

Es ist möglich, diesen Fehler zu umgehen, indem man den Pfad der Stapeldatei sofort einer Umgebungsvariablen zuweist, wie im obigen Code gezeigt, bevor man das Arbeitsverzeichnis ändert.

Dann referenzieren Sie den Wert dieser Variablen überall dort, wo der Pfad der Batch-Datei benötigt wird, wobei Sie bei Bedarf doppelte Anführungszeichen verwenden. Etwas wie %BatchPath% ist immer besser lesbar als %~dp0 .

Eine weitere Abhilfe besteht darin, die Batch-Datei immer mit dem vollständigen Pfad (und mit der Dateierweiterung) zu starten, indem man doppelte Anführungszeichen als Klasse verwendet Process が行う。

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X