Ich habe Probleme beim Verarbeiten von Dateien mit Namen, die Folgendes enthalten:"^"
(Caret-Zeichen).
Was mir auffällt, ist, dass die „Caretzeichen“ verdoppelt werden, wenn ich bei der Auswertung der Dateinamen doppelte Anführungszeichen verwende. Wenn ich die doppelten Anführungszeichen nicht verwende, werden die „Caretzeichen“ in den Dateinamen NICHT verdoppelt (bleiben erhalten), aber da einige der Dateinamen eingebettete Leerzeichen enthalten, muss ich die Dateinamen MIT den doppelten Anführungszeichen auswerten.
Als Beispiel habe ich einen Ordner, der einige Dateien enthält:
G:\Test-folder\Abcxyz 1.txt
G:\Test-folder\Abcxyz2.txt
G:\Test-folder\Abcxyz3.txt
G:\Test-folder\Abc^xyz 1.txt
G:\Test-folder\Abc^xyz2.txt
G:\Test-folder\Abc^xyz3^.txt
Ich habe ein Batch-Skript, das die Dateinamen sammelt, dann die Dateinamen liest und jede Datei verarbeitet.
@echo off
rem collect the filenames
dir /s /b "G:\Test-folder\ab*" > "G:\Test-folder\list.txt"
rem Note: here I have an opportunity to inspect and modify the filenames as necessary, but I have not found any modifications that solve this problem.
rem process each file
for /f "usebackq delims=" %%f in ("G:\Test-folder\list.txt") do call :work "%%~f"
@echo.
@echo Back: f1="%f1%"
@echo.
@echo.
@echo Running again, with "setlocal enabledelayedexpansion".
@echo.
for /f "usebackq delims=" %%f in ("G:\Test-folder\list.txt") do call :work2 "%%~f"
@echo.
@echo Back: f2="%f2%"
@echo.
goto :EOF
:work
rem :work
set "f1=%~1"
if exist "%f1%" goto :dostuff
@echo.
@echo File "%f1%" not found.
@echo %f1%
@echo "%~1"
@echo %~1
@echo.
goto :EOF
:dostuff
rem do some stuff :dostuff
@echo File "%f1%" found.
goto :EOF
:work2
rem :work2
setlocal enabledelayedexpansion
set "f2=%~1"
if exist "!f2!" goto :dostuff2
@echo.
@echo File "!f2!" not found.
@echo !f2!
@echo "%~1"
@echo %~1
@echo.
endlocal
goto :EOF
:dostuff2
rem do some stuff :dostuff2
@echo File "!f2!" found.
endlocal
goto :EOF
Wenn ich dieses Skript ausführe, erhalte ich die folgende Ausgabe:
File "G:\Test-folder\Abcxyz 1.txt" found.
File "G:\Test-folder\Abcxyz2.txt" found.
File "G:\Test-folder\Abcxyz3.txt" found.
File "G:\Test-folder\Abc^^xyz 1.txt" not found.
G:\Test-folder\Abc^xyz 1.txt
"G:\Test-folder\Abc^^xyz 1.txt"
G:\Test-folder\Abc^xyz 1.txt
File "G:\Test-folder\Abc^^xyz2.txt" not found.
G:\Test-folder\Abc^xyz2.txt
"G:\Test-folder\Abc^^xyz2.txt"
G:\Test-folder\Abc^xyz2.txt
File "G:\Test-folder\Abc^^xyz3^^.txt" not found.
G:\Test-folder\Abc^xyz3^.txt
"G:\Test-folder\Abc^^xyz3^^.txt"
G:\Test-folder\Abc^xyz3^.txt
Back: f1="G:\Test-folder\Abc^^xyz3^^.txt"
Läuft wieder, mit setlocal enabledelayedexpansion
.
File "G:\Test-folder\Abcxyz 1.txt" found.
File "G:\Test-folder\Abcxyz2.txt" found.
File "G:\Test-folder\Abcxyz3.txt" found.
File "G:\Test-folder\Abc^^xyz 1.txt" not found.
G:\Test-folder\Abc^^xyz 1.txt
"G:\Test-folder\Abc^^xyz 1.txt"
G:\Test-folder\Abc^xyz 1.txt
File "G:\Test-folder\Abc^^xyz2.txt" not found.
G:\Test-folder\Abc^^xyz2.txt
"G:\Test-folder\Abc^^xyz2.txt"
G:\Test-folder\Abc^xyz2.txt
File "G:\Test-folder\Abc^^xyz3^^.txt" not found.
G:\Test-folder\Abc^^xyz3^^.txt
"G:\Test-folder\Abc^^xyz3^^.txt"
G:\Test-folder\Abc^xyz3^.txt
Back: f2=""
So oder so, mit oder ohne die Verwendung von"enabledelayedexpansion"
kann ich keine Dateien verarbeiten, deren Namen ein"^"
(Caret-Zeichen).
Irgendwelche Ideen, wie das geht oder was ich falsch mache?
Antwort1
Nachdem ich eine Weile damit herumgespielt hatte, kam ich auf diese funktionierende Lösung:
@echo off
rem collect the filenames
dir /s /b "G:\Test-folder\ab*" >"G:\Test-folder\list.txt"
rem process each file
for /f "usebackq delims=" %%f in ("G:\Test-folder\list.txt") do call :work "%%~f"
@echo.
rem Note: I still could not make this work with "setlocal enabledelayedexpansion".
goto :EOF
:work
rem :work
set "f1=%~1"
if exist "%f1%" goto :dostuff
@echo.
@echo File "%f1%" not found.
@echo %f1%
@echo "%~1"
@echo %~1
@echo.
rem Notice that the "action" of this (next) for-loop is: [set "f1=%%~f"]
rem which uses the "for-variable" from the "outer" for-loop: "%%f"
rem instead of the "for-variable" from the "this" for-loop: "%%g"
@for /f "usebackq delims=" %%g in (`echo "dummy"`) do set "f1=%%~f"
if exist "%f1%" goto :dostuff
@echo File "%f1%" not found.
@echo %f1%
@echo.
goto :EOF
:dostuff
rem do some stuff :dostuff
@echo File "%f1%" found.
for %%g in ("%f1%") do echo name:"%%~ng" extn:"%%~xg" file-size:"%%~zg"
@echo.
goto :EOF
Die Ausgabe nach der Ausführung dieses Skripts lautet:
File "G:\Test-folder\Abcxyz 1.txt" found.
name:"Abcxyz 1" extn:".txt" file-size:"14"
File "G:\Test-folder\Abcxyz2.txt" found.
name:"Abcxyz2" extn:".txt" file-size:"13"
File "G:\Test-folder\Abcxyz3.txt" found.
name:"Abcxyz3" extn:".txt" file-size:"13"
File "G:\Test-folder\Abc^^xyz 1.txt" not found.
G:\Test-folder\Abc^xyz 1.txt
"G:\Test-folder\Abc^^xyz 1.txt"
G:\Test-folder\Abc^xyz 1.txt
File "G:\Test-folder\Abc^xyz 1.txt" found.
name:"Abc^xyz 1" extn:".txt" file-size:"15"
File "G:\Test-folder\Abc^^xyz2.txt" not found.
G:\Test-folder\Abc^xyz2.txt
"G:\Test-folder\Abc^^xyz2.txt"
G:\Test-folder\Abc^xyz2.txt
File "G:\Test-folder\Abc^xyz2.txt" found.
name:"Abc^xyz2" extn:".txt" file-size:"14"
File "G:\Test-folder\Abc^^xyz3^^.txt" not found.
G:\Test-folder\Abc^xyz3^.txt
"G:\Test-folder\Abc^^xyz3^^.txt"
G:\Test-folder\Abc^xyz3^.txt
File "G:\Test-folder\Abc^xyz3^.txt" found.
name:"Abc^xyz3^" extn:".txt" file-size:"15"
Ich bin zufällig über diese funktionierende Lösung „gestolpert“, die eine Methode verwendet, die ein nicht dokumentiertes Verhalten verschachtelter For-Schleifen sein könnte.
Ich habe versucht, mit "sed" die"^^"
in der zitierten Zeichenfolge zu einem einzelnen"^"
, so was:
@for /f "usebackq delims=" %%g in (`echo "%f1%"^|sed -r "s/(\x5e)\1/\1/g"`) do set "f1=%%~g"
Ich habe stattdessen versehentlich Folgendes eingegeben:
@for /f "usebackq delims=" %%g in (`echo "%f1%"^|sed -r "s/(\x5e)\1/\1/g"`) do set "f1=%%~f"
Ich war (zunächst) nicht wirklich überrascht, als das funktionierte, weil ich dachte, "sed" würde wie erwartet funktionieren. Dann bemerkte ich, dass ich die falsche For-Variable verwendet hatte:set "f1=%%~f"
anstatt:set "f1=%%~g"
, was überraschend war.
Ich habe es geändert, um die richtige Variable zu verwenden:set "f1=%%~g"
, nur um festzustellen, dass es nicht funktionierte.
Ich habe verschiedene Versionen davon ausprobiert, darunter:
@for /f "usebackq delims=" %%g in (`echo "%f1%"`) do set "f1=%%~g"
nichts davon hat funktioniert.
Dies scheint also nur zu funktionieren, wenn es durch Verwendung der falschen For-Variable „missbraucht“ wird. Obwohl dies in diesem Fall nützlich erscheint, fällt es mir schwer, darauf zu vertrauen, dass es langfristig funktioniert.
Es würde mich sehr interessieren, von anderen zu hören, ob es sich hierbei tatsächlich um ein „dokumentiertes“ (erwartetes) Verhalten handelt oder nicht.
Antwort2
Ein großes Lob an Sie für die Entdeckung zweier obskurer Windows-Batch-Verhaltensweisen in einer einzigen Frage und Antwort!
Es ist unmöglich, eine ungerade Anzahl von Zitierzeichen als Zeichenfolgenliteral über CALL zu übergeben, weder im Batch- noch im Kommandozeilenmodus. Eine Erklärung finden Sie in Phase 6) unterWie analysiert der Windows-Befehlsinterpreter (CMD.EXE) Skripts?.
Hier ist ein Beispiel für das Problem. Angenommen, ein Skript enthält den folgenden Befehl:
call echo Unquoted ^^ "Quoted ^"
Nach Phase 2 des Parsers verbraucht der nicht in Anführungszeichen gesetzte Teil als Teil des Escape-Verhaltens ein Zirkumflex. Der in Anführungszeichen gesetzte Teil bleibt unverändert. Der Befehl sieht nun folgendermaßen aus:
call echo Unquoted ^ "Quoted ^"
Beim Erkennen von CALL in Phase 6 werden alle Caretzeichen verdoppelt und Folgendes durch den CALL-Mechanismus geleitet:
echo Unquoted ^^ "Quoted ^^"
Der CALL durchläuft eine 2. Phase 2), die folgendes Ergebnis bringt:
echo Unquoted ^ "Quoted ^^"
Das Endergebnis ist:
Unquoted ^ "Quoted ^^"
Ihr Beispiel mit der FOR-Schleife umgeht die anfängliche Phase 2, da die FOR-Variablenerweiterung nach Phase 2 erfolgt.
Die Lösung: Übergeben Sie keine in Anführungszeichen gesetzten Zeichenfolgenliterale, die ein Caretzeichen enthalten, über CALL.Verwenden Sie eine alternative Strategie. Es gibt mehrere Optionen. Ich habe unten einige aufgeführt.
1a)Verwenden Sie CALL überhaupt nicht. Sie können Klammern nach DO verwenden, um beliebig komplexen Code zu erstellen. Dies ist bei weitem meine bevorzugte Strategie, da CALL von Natur aus langsam ist. Das Einzige, was Sie nicht tun können, ist GOTO innerhalb der Schleife zu verwenden, da dies die Schleifenverarbeitung sofort beenden würde. Wenn Sie Variablen innerhalb der Schleife bearbeiten müssen, müssen Sie die verzögerte Erweiterung aktivieren und verwenden.
setlocal enableDelayedExpansion
for ....%%A in (...) do (
set "var=%%A"
echo the value of var=!var!
... whatever
)
1b)Wenn die FOR-Variable ! enthalten kann, müssen Sie die verzögerte Erweiterung innerhalb der Schleife ein- und ausschalten, um eine Beschädigung zu verhindern.
for ... %%A in (...) do (
setlocal enableDelayedExpansion
set "var=%%A"
echo the value of var=!var!
... whatever
endlocal
)
2a)Wenn Sie CALL wirklich verwenden möchten, übergeben Sie den Wert nicht als Zeichenfolgenliteral. Speichern Sie den Wert stattdessen in einer Umgebungsvariablen. Beachten Sie, dass der Wert von var zum Schutz vor Sonderzeichen in Anführungszeichen gesetzt wird.
for ... %%A in (...) do (
set var="%%~A"
call :work
)
exit /b
:work
echo var=%var%
... etc.
exit /b
2b)Ich bevorzuge die verzögerte Erweiterung, damit ich mir keine Gedanken darüber machen muss, ob Sonderzeichen in der Zeichenfolge in Anführungszeichen gesetzt sind. Beachten Sie, dass der Wert von var nicht in Anführungszeichen gesetzt ist, da das öffnende Anführungszeichen vor dem Variablennamen in der SET-Anweisung steht.
for ... %%A in (...) do (
set "var=%%~A"
call :work
)
exit /b
:work
setlocal enableDelayedExpansion
echo var=!var!
... etc.
exit /b
2c)Anstatt eine Subroutine zu schreiben, die nur mit einer Variablen arbeiten kann, können Sie den Namen der Variablen als Argument übergeben. Dies erfordert eine verzögerte Erweiterung.
for ... %%A in (...) do (
set "var=%%~A"
call :work var
)
exit /b
:work
setlocal enableDelayedExpansion
echo %1=!%1!
... etc.
exit /b
3)Verwenden Sie FOR-Variablen "Tunneling" wie inDeine Antwort. Ich habe diese Technik in der Vergangenheit verwendet, aber sie gefällt mir nicht, weil sie verschleiert. Jemand, der versucht, den Code nach dem Schreiben zu warten, wird wahrscheinlich nicht verstehen, was vor sich geht.
FOR-Variablen haben nur innerhalb der DO-Schleife einer FOR-Anweisung Gültigkeit. Wenn Sie die Schleife mit CALL verlassen, endet der Gültigkeitsbereich. Aber wie Sie festgestellt haben, tauchen die älteren FOR-Variablen „wie von Zauberhand“ wieder auf, wenn die aufgerufene Routine eine eigene FOR-Anweisung hat.
for ... %%A in (...) do call :work
exit /b
:work
echo The A variable is no longer in scope: %%A
for %%x in (x) do echo The A variable is back: %%A
Die Erklärung ist, dass FOR-Variablen global sind, aber nur innerhalb einer DO-Schleife zugänglich sind. Dies wird im integrierten Hilfesystem kryptisch erklärt. Geben Sie help for
oder for /?
ein, um die Hilfe zu erhalten. Der relevante Abschnitt befindet sich etwa in der Mitte. Beachten Sie das fettgedruckte Wort am Ende des Zitats.
Einige Beispiele könnten hilfreich sein:
FÜR /F "eol=; tokens=2,3* delims=, " %i in (meineDatei.txt) mache @echo %i %j %k
würde jede Zeile in myfile.txt analysieren, Zeilen ignorieren, die mit einem Semikolon beginnen, und das 2. und 3. Token aus jeder Zeile an den for-Body übergeben, wobei die Token durch Kommas und/oder Leerzeichen getrennt sind. Beachten Sie, dass die for-Body-Anweisungen auf %i verweisen, um das 2. Token abzurufen, auf %j, um das 3. Token abzurufen, und auf %k, um alle verbleibenden Token nach dem 3. abzurufen. Bei Dateinamen, die Leerzeichen enthalten, müssen Sie die Dateinamen in doppelte Anführungszeichen setzen. Um doppelte Anführungszeichen auf diese Weise zu verwenden, müssen Sie auch die Option usebackq verwenden, da die doppelten Anführungszeichen sonst so interpretiert werden, als würden sie eine zu analysierende Literalzeichenfolge definieren.
%i wird explizit in der for-Anweisung deklariert und %j und %k werden implizit über die Option tokens= deklariert. Sie können bis zu 26 Token über die Zeile tokens= angeben, vorausgesetzt, es wird nicht versucht, eine Variable zu deklarieren, die höher als der Buchstabe 'z' oder 'Z' ist. Denken Sie daran, dass FOR-Variablen aus einem Buchstaben bestehen und die Groß-/Kleinschreibung beachten.weltweit, und Sie können nicht mehr als 52 gleichzeitig aktiv haben.
Das ist die meiste offizielle Dokumentation, die ich je zu diesem Verhalten gesehen habe. Sehr kryptisch und nicht sehr hilfreich. Tatsächlich sind viele der Informationen in diesem letzten Absatz einfachfalsch! Sehenhttps://stackoverflow.com/a/8520993/1012053für die Wahrheit bezüglich der maximalen Anzahl verfügbarer FOR-Variablen und was gültige Zeichen für FOR-Variablen sind.
Antwort3
Ich bin absolut kein Experte und kann daher nicht sagen, was in Ihrem ausführlichen Programm falsch ist. Aber ich habe diesen Batch ausgeführt und einige beabsichtigte Ergebnisse gefunden:
@ECHO OFF
DIR /b /s >list.txt
SETLOCAL enabledelayedexpansion
FOR /f "delims=" %%x IN (list.txt) DO IF EXIST "%%x" (@ECHO %%x found) else (@ECHO %%x not found)