Simon Laing
Simon Laing

Reputation: 1214

Batch file call escaping redirection within for loop

I am trying to call a command from a batch file which is reading in lines from a file.
This is working correctly, except for when the line contains the redirection character >.

Is there a way to tell call to escape this character, or to replace it within the content of the for loop?

I've looked at setlocal enabledelayedexpansion and (when the call is updated to use ! it still doesn't work).

set /p status=<%tmp_file%

for /f "delims=*" %%a in (%tmp_file%) do (
  echo "%%a"
  call check.bat "%%a"
  echo.

  if not "%errorlevel%" == "0" do exit /b 1
)

This produces the following output (when check.bat echo's %1)

"a"
"a"

"b -> b"
"b -"

I've tried to replace > within %%a but I'm not entirely sure how this can be achieved, each time i try, it yields an empty string, i.e.

set line=%a:^>=¬%

EDIT 1
Some more clarification (it appears to only be the case if %1 is set to a variable, and then that variable is used)?:

check.bat:

 set rawinput=%1
 set input=%~1

 echo %1
 echo "%rawinput%"
 echo "%input%"
 echo.

This yields the following output, although im not quite sure why setting %1 to a variable causes it to mangle the value?

 "a"
 "a"
 ""a""
 "a"

 "b -> b"
 "b -> b"
 "b -"

Interestingly b -> b is only output 2 times, echo "%rawinput%" is not showing at all. Both echo "%input%" and echo "%rawinput%" are writing to a file named b.

This means that the check.bat for b -> b must be interpreted as:

 echo "b -> b"
 echo ""b -> b""
 echo "b -> b"        REM - this is what 'should' be happening, however does appear to be the case, as it writes '' to a file named b
 echo.

If anyone can shed light on why echo "b -> b" in a batch file does not appear to be behaving it would be greatly appreciated.

Upvotes: 1

Views: 1937

Answers (2)

dbenham
dbenham

Reputation: 130919

You have multiple problems in your modified check.bat

The quotes are a state machine in that the 1st quote turns quoting semantics ON, the 2nd OFF, the 3rd ON, etc. An unquoted (and unescaped) > is interpreted as an instruction to redirect output.

I've heavily modified check.bat by putting in a bunch of ECHO statements to provide a narrative of what is happening. Your original code is embedded in my version as all CAPS.

Here is the output of my modified version, which I hope explains why you are getting your results. I want you to read this output before you try to understand how my modified check.bat produces this output.

"a"
---------------------------------------------
Executing: SET RAWINPUT=%1  which expands to  SET RAWINPUT="a"
Here is the result:
rawinput="a"

Executing: SET INPUT=%~1  which expands to  SET INPUT=a
Here is the result:
input=a

Executing: ECHO %1  which expands to  ECHO "a"
"a"

Executing: ECHO "%RAWINIPUT%"  which expands to  ECHO ""a""
""a""

Executing: ECHO "%INPUT%"  which expands to  ECHO "a"
"a"


"b -> b"
---------------------------------------------
Executing: SET RAWINPUT=%1  which expands to  SET RAWINPUT="b -> b"
Here is the result:
rawinput="b -> b"

Executing: SET INPUT=%~1  which expands to  SET INPUT=b -> b
Here is the result:
input=b -

Also created a file named "b" because the output of the command
is redirected. The file is empty because SET has no output.

 Volume in drive C is OS
 Volume Serial Number is EE2C-5A11

 Directory of C:\Users\Public\utils

11/20/2012  09:30 AM                 0 b
               1 File(s)              0 bytes
               0 Dir(s)  316,104,765,440 bytes free

Executing: ECHO %1  which expands to  ECHO "b -> b"
"b -> b"

Executing: ECHO "%RAWINIPUT%"  which expands to  ECHO ""b -> b""

There is no screen output because output was redirected to file "b"
Here are the contents of "b":
""b -

Executing: ECHO "%INPUT%"  which expands to  ECHO "b -"
"b -"

Here is my modified check.bat that produces the output above.

setlocal enableDelayedExpansion
set value=%1
set "unquotedValue=%~1"

echo ---------------------------------------------
echo Executing: SET RAWINPUT=%%1  which expands to  SET RAWINPUT=!value!
SET RAWINPUT=%1
echo Here is the result:
set rawinput
echo.

echo Executing: SET INPUT=%%~1  which expands to  SET INPUT=!unquotedValue!
SET INPUT=%~1
echo Here is the result:
set input
if exist b (
  echo.
  echo Also created a file named "b" because the output of the command
  echo is redirected. The file is empty because SET has no output.
  echo.
  dir b
  del b
)
echo.

echo Executing: ECHO %%1  which expands to  ECHO !value!
ECHO %1
echo.

echo Executing: ECHO "%%RAWINIPUT%%"  which expands to  ECHO "!rawinput!"
ECHO "%RAWINPUT%"
if exist b (
  echo.
  echo There is no screen output because output was redirected to file "b"
  echo Here are the contents of "b":
  type b
  del b
)
echo.

echo Executing: ECHO "%%INPUT%%"  which expands to  ECHO "!input!"
ECHO "%INPUT%"
echo.


You can get your desired results via a variable if you do not add any quotes in your check.bat

set rawinput=%1
echo %rawinput%

But all hell can break loose if your original text file includes its own quotes.

That is the reason jeb suggests storing each line in a variable within your main routine and then pass the name of the variable to check.bat. Within check.bat you can use delayed expansion to safely work with any value without worrying about quotes or escapes.

Upvotes: 2

jeb
jeb

Reputation: 82418

Do not try to use call by value, that's nearly impossible with batch.
Simply set a new variable with the value and then use the variable name.

for /f "delims=" %%a in (%tmp_file%) do (
  set "line=%%a"
  call :check
)
exit /b

:check
setlocal EnableDelayedExpansion
echo(!line!
endlocal
exit /b

Edit: Your problem

You try to call a function with the content "b -> b" using
call check.bat "%%a" this will expand to
call check.bat ""b -> b"" and that's the problem!
As the > redirect character is now outside of the quotes, it will redirect the output of check.bat to the file b.
You can escape a special character (outside of quotes) when using a call, but this is a bit advanced and has many problems.

So, I would recommend again, not using directly the content in a call.
You could use a reference, like

for /f "delims=" %%a in (%tmp_file%) do (
  set "line=%%a"
  call :check line
)

check.bat

setlocal EnableDelayedExpansion
set "var=!%1!"
echo(!var!

Upvotes: 3

Related Questions