Home > Enterprise >  How to process pathnames with ! within an for /F loop?
How to process pathnames with ! within an for /F loop?

Time:05-08

In a complex batch file I want to read in files with paths, among other things, to read them into a variable one after the other separated by spaces.

This works with the following code so far quite well - but only if the path does not contain an exclamation mark.

Even using the setlocal command (enabledelayedexpansion / disabledelayedexpansion) I did not succeed in processing exclamation marks.

Does anyone here have a clever idea to the problem?

The following example batch creates a text file in the current directory and then reads it in a for /F loop. At the end all three paths from the text file should be in the variable %Output%. But with the exclamation mark.

@echo off

setlocal enabledelayedexpansion

echo This is an example^^! > "textfile.txt"
echo This is a second example^^! >> "textfile.txt"
echo And this line have an ^^! exclamation mark in the middle >> "textfile.txt"

for /F "usebackq tokens=* delims=" %%a in (textfile.txt) do (
    set "Record=%%a"
    set "Output=!Output!!Record! - "
  )
)

echo %Output%
echo !Output!

endlocal

The Output is like this:

This is an example  - This is a second example  - And this line have an  exclamation mark in the middle

But should be like this:

This is an example!  - This is a second example!  - And this line have an ! exclamation mark in the middle

CodePudding user response:

It is advisable not using delayed variable expansion on processing files and directories, lines in a text file, strings not defined by the batch file itself, or output captured from the execution of a program or a command line. If it is for some reasons necessary to make use of delayed variable expansion inside a FOR loop, there should be first assigned the file/directory name, the line, or the string to process to an environment variable while delayed expansion is disabled and then enable delayed expansion temporary inside the FOR loop.

Here is a batch file demo which can be simply run from within a command prompt window or by double clicking on the batch file. It creates several files for demonstration in the directory for temporary files, but deletes them all before exiting.

@echo off
setlocal EnableExtensions DisableDelayedExpansion

echo This is an example!> "%TEMP%\TextFile.tmp"
echo This is a second example!>> "%TEMP%\TextFile.tmp"
echo And this line has an exclamation mark ! in the middle.>> "%TEMP%\TextFile.tmp"

set "Output="
(for /F usebackq^ delims^=^ eol^= %%I in ("%TEMP%\TextFile.tmp") do call :ConcatenateLines "%%I") & goto ContinueDemo
:ConcatenateLines
set "Output=%Output% - %~1" & goto :EOF

:ContinueDemo
cls
echo/
echo All lines concatenated are:
echo/
echo %Output:~3%
set "Output="
del "%TEMP%\TextFile.tmp"

echo File with name ".Linux hidden file!">"%TEMP%\.Linux hidden file!"
echo File with name "A simple test!">"%TEMP%\A simple test!"
echo File with name "  100%% Development & 'Test' (!).tmp">"%TEMP%\  100%% Development & 'Test(!)'.tmp"
echo/
echo Files with ! are:
echo/
for /F "eol=| tokens=* delims=" %%I in ('dir "%TEMP%\*!*" /A-D /B /ON 2^>nul') do (
    set "NameFile=%%I"
    set "FileName=%%~nI"
    set "FileExtension=%%~xI"
    set "FullName=%TEMP%\%%I"
    setlocal EnableDelayedExpansion
    if defined FileName (
        if defined FileExtension (
            echo File with ext. !FileExtension:~1!: !NameFile!
        ) else (
            echo Extensionless file: !NameFile!
        )
    ) else echo Extensionless file: !NameFile!
    del "!FullName!"
    endlocal
)
endlocal
echo/
@setlocal EnableExtensions EnableDelayedExpansion & for /F "tokens=1,2" %%G in ("!CMDCMDLINE!") do @endlocal & if /I "%%~nG" == "cmd" if /I "%%~H" == "/c" set /P "=Press any key to exit the demo . . . "<nul & pause >nul

The output of this batch file is:

All lines concatenated are:

This is an example! - This is a second example! - And this line has an exclamation mark ! in the middle.

Files with ! are:

File with ext. tmp:   100% Development & 'Test(!)'.tmp
Extensionless file: .Linux hidden file!
Extensionless file: A simple test!

The text file example with concatenating lines makes use of a subroutine called from within the FOR loop processing the lines in the text file. The syntax used here is for maximum performance by getting the subroutine as near as possible to the FOR command line. That is important if the FOR loop has to process hundreds or even thousands of items.

The example processing file names enables and disables delayed expansion inside the FOR loop after having assigned all parts of the currently processed file to environment variables. It could be useful to reduce the list of environment variables before processing thousands of files for a better performance on using this method.

Another method is shown in Magoo´s answer using the command CALL to get a command line with referenced environment variables (re)defined inside the loop parsed a second time. I used that method also in the past quite often, but don't that anymore as it is not fail-safe and not efficient. call set results in searching by cmd.exe in current directory and next in all directories of environment variable PATH for a file with name set and a file extension of environment variable PATHEXT. So it results in lots of file system accesses in the background on each iteration of the FOR loop and if there is by chance a file set.exe, set.bat, set.cmd, etc. found by cmd.exe somewhere, the batch file does not work anymore as expected because of running the executable or calling the batch file instead of the (re)definition of the environment variable.

The following answers written by me could be also helpful:

CodePudding user response:

It's delayedexpansion mode that appears to raise this problem.

@ECHO OFF
setlocal enabledelayedexpansion

echo This is an example^^^! > "textfile.txt"
echo This is a second example^^^! >> "textfile.txt"
echo And this line have an ^^^! exclamation mark in the middle >> "textfile.txt"
TYPE "textfile.txt"
SETLOCAL disabledelayedexpansion
for /F "usebackq tokens=* delims=" %%a in (textfile.txt) do (
    set "Record=%%a"
    CALL set "Output2=%%Output2%%%%record%% - "
    CALL set "Output=%%Output%%%%a - "
    SET out
  )
)
endlocal&SET "output=%output%"

echo %Output%
echo !Output!
SET out

I've no doubt that with delayedexpansion off, there would be the same problem with %. Just special characters, I suppose.

Note that with endlocal&SET "output=%output%", the set is executed in delayedexpansion mode.

CodePudding user response:

Pew. I finally got it to work. It works via a workaround using a second text file. Not pretty, not performant, but it works and is sufficient for my purposes. @Magoo, thanks for your post.

This is my solution:

@echo off
setlocal enableextensions enabledelayedexpansion

echo This is an example^^!> "textfile.txt"
echo This is a second example^^!>> "textfile.txt"
echo And this line have an ^^! exclamation mark in the middle>> "textfile.txt"
echo.
echo Content of the textfile:
type "textfile.txt"

set output=

del "textfile2.txt" 1> nul 2>&1
setlocal disabledelayedexpansion

for /f "usebackq tokens=* delims=" %%a IN ("textfile.txt") do (
  rem Write each line without a newline character into a new text file
  echo|set /p "dummy=%%a, ">>"textfile2.txt"
)

endlocal

rem Loading the content of the new text file into the variable
set /p output=<"textfile2.txt"
del "textfile2.txt" 1> nul 2>&1

echo.
echo --------------------------------------------
echo Content of the variable:

set out

endlocal

The output looks like this:

Content of the textfile:
This is an example!
This is a second example!
And this line have an ! exclamation mark in the middle

--------------------------------------------
Content of the variable:
output=This is an example!, This is a second example!, And this line have an ! exclamation mark in the middle,
  • Related