1

I've a code like below:

    FOR /F "tokens=*" %%B IN ('%*') DO (
    ECHO %%B
    SET data=%%B
    ECHO %data%
    )        

The %%B contains one or more than one | character, for example the value of %%B is First | Second | Third. The SET data=%%B or SET data="%%B" function doesn't work. When %data% is called back, it shows Echo is off.

I need to remove the | character from %%B and save it to a %data% variable. But I don't know how?

I will be grateful if any solution has been provided from anyone....

Gouranga Das
  • 374
  • 3
  • 10
  • 1
    [`SetLocal EnableDelayedExpansion`](https://stackoverflow.com/questions/22278456/enable-and-disable-delayed-expansion-what-does-it-do). – user202729 Aug 26 '18 at 15:54
  • SETLOCAL ENABLEDELAYEDEXPANSION doesn't solve the problem. For info, I also have the same at the first line of code, but here I've skipped that. – Gouranga Das Aug 26 '18 at 15:58
  • Possible duplicate of [How to set a variable inside a loop for /F](https://stackoverflow.com/questions/13805187/how-to-set-a-variable-inside-a-loop-for-f) – melpomene Aug 26 '18 at 16:16
  • https://stackoverflow.com/a/5274061/5267751 – user202729 Aug 26 '18 at 16:17
  • @melpomene That's only half the problem. – user202729 Aug 26 '18 at 16:18
  • Maybe, maybe not. It's not clear whether OP really wants to remove `|` from a variable (in which case the posted code is irrelevant because it doesn't even try to do that), or whether OP thinks removing `|` would fix the code (which it wouldn't, making this an XY problem). – melpomene Aug 26 '18 at 16:21
  • What does **`%*`** contain, because you're using only **`'`**, which means that you're providing a command. It sounds as if you are trying to split a string. _Take a look at the output of `For /?` to find out the difference between a set, command and a string_. – Compo Aug 27 '18 at 01:01

1 Answers1

2

The first solution is using:

@echo off
setlocal EnableExtensions EnableDelayedExpansion

for /F delims^=^ eol^= %%B in ('%*') do (
    set "data=%%B"
    set "data=!data:|=!"
    echo(!data!
)

endlocal

tokens=* results in getting assigned to loop variable B the entire line with leading spaces/tabs removed while delims= results in getting assigned to the loop variable B the entire line including leading spaces/tabs. So the definition of an empty list of delimiters is better in general.

FOR ignores by default empty lines and lines starting with a semicolon because of ; is interpreted by default as end of line character. For that reason the uncommon, not double quoted option string delims^=^ eol^= is used here to define an empty list of delimiters and no end of line character. The caret character ^ is used to escape the next character to get it interpreted as literal character and not as argument separator although not enclosed in a double quoted argument string.

But there is one problem with this solution: A line containing one or more exclamation marks ! is not processed correct because Windows command processor interprets on command line set "data=%%B" each ! as begin/end of a delayed environment variable reference and replaces the string between two exclamation marks by the current value of the environment variable with that name or nothing on no variable existing with such a name and removes everything after ! if there is no more !.

There are at least three solutions.

The first one is enabling and disabling delayed expansion within the loop.

@echo off
setlocal EnableExtensions DisableDelayedExpansion

for /F delims^=^ eol^= %%B in ('%*') do (
    set "data=%%B"
    setlocal EnableDelayedExpansion
    set "data=!data:|=!"
    echo(!data!
    endlocal
)

endlocal

Please read this answer for details about the commands SETLOCAL and ENDLOCAL as they do much more than just toggling delayed expansion on/off in the loop.

( between echo and value of environment variable data is used in case of a line contains only | without or with additionally just spaces/tabs resulting in data becoming either undefined or a string consisting only of spaces/tabs. A space between echo and !data! would be on execution just the command echo  with an ignored space and 0 or more spaces/tabs resulting in getting output ECHO is off. instead of an empty line or a line with just spaces/tabs. The opening round bracket prevents that and is interpreted by cmd.exe as separator between command echo and its argument string which begins in this case always with (. ECHO ignores the first character of the argument string on output as long as the argument string is not /? and so ( is never output.

The second solution is using a subroutine:

@echo off
setlocal EnableExtensions DisableDelayedExpansion

for /F delims^=^ eol^= %%B in ('%*') do (
    set "data=%%B"
    call :ProcessLine
)

endlocal
goto :EOF

:ProcessLine
set "data=%data:|=%"
echo(%data%
goto :EOF

Note: echo %data% can result in unexpected behavior if the line contains characters like the redirection operators < and > or the operator & as Windows command processor executes echo(%data% after substituting %data% with current string of environment variable data. Even set "data=%data:|=%" can be problematic depending on line containing " and <>&. So this solution is really not safe.

See also Where does GOTO :EOF return to?

The third solution is using a "double percent" environment variable reference and using command CALL to force a double parsing of the command line containing %%variable%% instead of just %variable% as usual.

@echo off
setlocal EnableExtensions DisableDelayedExpansion

for /F delims^=^ eol^= %%B in ('%*') do (
    set "data=%%B"
    call set "data=%%data:|=%%"
    call echo(%%data%%
)

endlocal

Note Also this solution is not really safe depending on the data as the second solution.

See also How does the Windows Command Interpreter (CMD.EXE) parse scripts?

Windows command processor is designed for executing commands and applications and not for reformatting or processing CSV files using vertical bar as delimiter/separator.

For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.

  • call /?
  • echo /?
  • endlocal /?
  • for /?
  • goto /?
  • set /?
  • setlocal /?

And read also answer on Why is no string output with 'echo %var%' after using 'set var = text' on command line? for the reason using the syntax set "variable=value" instead of set variable=value or set variable="value".

Mofi
  • 46,139
  • 17
  • 80
  • 143
  • Quote: "The opening round bracket prevents that and is interpreted by `cmd.exe` as separator between command `echo` and its argument string and therefore not output by **ECHO**." | Actually The opening round bracket `(` is part of the `echo` arguments, it's the `echo` command that skips the first character in its arguments thus `(` will not be printed but since the `echo` arguments is not EMPTY, It will not print the **echo on/off** message. (EMPTY means No argument or an argument composed of only white spaces). – sst Aug 26 '18 at 21:04
  • Yea, I know the rule that `cmd.exe` parser will play here, otherwise `echo` could not have chance to be called, I just provided additional info on inner workings of `cmd.exe` in this regard. Debugging is not needed for determining the fact that `(` (or any other character that is not part of the command token) will be included in the arguments, and not just for `echo` but for every other internal or external command. This can be easily checked by typing `set(` or by typing `cmd(` then `echo %cmdcmdline%` – sst Aug 27 '18 at 07:13
  • But to explain the behavior of `echo` in cases like `echo(` or `echo,` or `echo.` and the like, debugging or disassembly of `cmd.exe` is required. If your are interested, The internal function name for `echo` is `eEcho`. In `eEcho` it can be seen that the first character will be skipped before attempting to print the passed argument. – sst Aug 27 '18 at 07:14
  • @sst Thanks for the additional information. I verified that as written by you and also using the executables compiled by me with C code posted in [this answer](https://stackoverflow.com/a/24008269/3074564) and you are absolutely right. `(` is passed by `cmd.exe` to the internal command __ECHO__ as well as to console applications as first character of argument 1 on being the first character after command/file name. I have updated the paragraph about interpretation of `(` between `echo` and `!data!`. I am always glad to learn something new. Thanks for this information. – Mofi Aug 27 '18 at 09:48