piping into set /p

  • Thread starter Thread starter Liviu
  • Start date Start date
L

Liviu

"C:>echo 123 | set /p xyz=" at a cmd prompt doesn't set/change "xyz".

Is there another way to set a variable from piped standard input
(without temp files etc)? Question is related to
http://www.robvanderwoude.com/variableexpansionbug.html, and could have
interesting uses inside a "for" with enabledelayedexpansion, for example

--------
@echo off
echo.
setlocal enabledelayedexpansion
for %%x in ("%%^^&^!") do (
echo "%%~x"
(echo "%%~x") | more
)
--------

gives

--------
"%&"
"%^&!"
--------

where the second line could be useful if "captured" into a variable.

Cheers,
Liviu
 
"C:>echo 123 | set /p xyz=" at a cmd prompt doesn't set/change "xyz".

Is there another way to set a variable from piped standard input
(without temp files etc)?

No. Only temp files and `for /f`
 
No. Only temp files and `for /f`

I'd take 'for /f ' if only it worked ;-)

----
C:\>echo 123 | set /p xyz=

C:\>set xyz
Environment variable xyz not defined

C:\>echo 123 | for /f "delims=" %x in ('more') do set xyz=%x

C:\>set xyz=123

C:\>set xyz
Environment variable xyz not defined
 
I'd take 'for /f ' if only it worked ;-)

It does work. But not the way you used it :) Try this:

----setfromstdin.bat
@echo off
setlocal
for /f "delims=" %%x in ('more') do set xyz=%%x
set xyz
----EOF

C:\> echo 123| setfromstdin.bat
xyz=123



To understand what happens you have to know how anonymous pipes are
handled in NT. Both ends of the pipe are launched in separate child
processes. Once all the commands are executed, those processes are
terminated and their environments destroyed, so any environment
changes will not survive (unless you use some utility capable of
setting the environment of the parent process).

Now, on to the supposed bug with delayed expansion that you link to in
your original post. There is no bug, it's the expected behaviour.
Let's look at the example from the link http://www.robvanderwoude.com/variableexpansionbug.html
:

----BOF
@echo off
setlocal enabledelayedexpansion
set myvar=value
(
echo.!myvar!
) | more
endlocal
----EOF

The reason it doesn't work is because 'echo.!myvar!' is execuded in a
separate 'cmd.exe' and by default delayed expansion is disabled in
command interpreter. So let's enable it and see what happens:

C:\>set xyz=123 & (cmd.exe /V:ON /C echo.!xyz!)|more
123

Now it works!

I hope that clears a few things.

Cheers,

Tomek
 
It does work. But not the way you used it :) Try this:

Thanks, but it still does not work the way I asked ;-)
----setfromstdin.bat
@echo off
setlocal
for /f "delims=" %%x in ('more') do set xyz=%%x
set xyz
----EOF

C:\> echo 123| setfromstdin.bat
xyz=123

The .bat is not setting the environment in the caller process. In fact
it's not too different from the example I gave with the one-line for /f
at the cmd prompt, where you could see the set xyz=123 being executed,
but then "xyz" was still undefined once control returned to the prompt.
Using your batch does pretty much the same...

----
C:>echo 123 | setfromstdin.bat
xyz=123

C:>set xyz
Environment variable xyz not defined
----
To understand what happens you have to know how anonymous pipes
are handled in NT. Both ends of the pipe are launched in separate
child processes. Once all the commands are executed, those processes
are terminated and their environments destroyed, so any environment
changes will not survive (unless you use some utility capable of
setting the environment of the parent process).

Would you know of such utility usable in batch files?

That said, I believe your explanation is close to what's going on
internally. However, it is not an intrinsic limitation of anonymous
pipes. If anything, it's a careless (mis)use of them by cmd.
Now, on to the supposed bug with delayed expansion that you link to
in your original post. There is no bug, it's the expected behaviour.
[...]
The reason it doesn't work is because 'echo.!myvar!' is execuded in a
separate 'cmd.exe' and by default delayed expansion is disabled in
command interpreter. So let's enable it and see what happens [...]

Yes and no. Yes, I see your point. No, because you could make literally
the same point if 'echo.!myvar!' was not enclosed in parantheses, yet it
works as expected in that case.

Cheers,
Liviu
 
foxidrive said:
What do you want to do, in specific terms?

Fight the "!" demons ;-) Quoting from the top post: [...] could have
interesting uses inside a "for" with enabledelayedexpansion, for example

--------
@echo off
echo.
setlocal enabledelayedexpansion
for %%x in ("%%^^&^!") do (
echo "%%~x"
(echo "%%~x") | more
)
--------

gives

--------
"%&"
"%^&!"
--------

where the second line could be useful if "captured" into a variable.
Nothing to do with malware is it? ;-)

Not at all, unless you classify enabledelayedexpansion as malware ;-)
 
What do you want to do, in specific terms?

Fight the "!" demons ;-) Quoting from the top post: š[...] could have
interesting uses inside a "for" with enabledelayedexpansion, for example

--------
@echo off
echo.
setlocal enabledelayedexpansion
for %%x in ("%%^^&^!") do (
š echo "%%~x"
š (echo "%%~x") | more
)
--------

gives

--------
"%&"
"%^&!"
--------

where the second line could be useful if "captured" into a variable.
Nothing to do with malware is it? ;-)

Not at all, unless you classify enabledelayedexpansion as malware ;-)

@echo off

for %%x in (%%^&!) do set "var=%%x" && call:1
goto:eof

:1
setlocal enabledelayedexpansion
echo !var!
endlocal
 
Thanks, but it still does not work the way I asked ;-)
The .bat is not setting the environment in the caller process. In fact
it's not too different from the example I gave with the one-line for /f
at the cmd prompt, where you could see the set xyz=123 being executed,
but then "xyz" was still undefined once control returned to the prompt.
Using your batch does pretty much the same...

Yes, that's correct. What I wanted to show was that you can read from
stdin in your parrent batch and set the evironment accordingly. If
that doesn't work for you then you are left with temp files or some
3rd party utilities.
Would you know of such utility usable in batch files?

I think it was 'setenv' but I'm not sure. Just search this group and
you will surely find it.
That said, I believe your explanation is close to what's going on
internally. However, it is not an intrinsic limitation of anonymous
pipes. If anything, it's a careless (mis)use of them by cmd.

It's actually easy to test if this explanation is true. Run
C:\>echo|(echo&ping 1.1.1.1)
and check windows task manager. You will briefly see an extra cmd.exe
process while piped commands are executed. So my explanation wasn't
100% accurate. Only receiving end of the pipe is launched in a
separate process.

If that's a 'careless (mis)use' I don't know but that's how things
work on NT. What can you do about it?
Now, on to the supposed bug with delayed expansion that you link to
in your original post. There is no bug, it's the expected behaviour.
[...]
The reason it doesn't work is because 'echo.!myvar!' is execuded in a
separate 'cmd.exe' and by default delayed expansion is disabled in
command interpreter. So let's enable it and see what happens [...]

Yes and no. Yes, I see your point. No, because you could make literally
the same point if 'echo.!myvar!' was not enclosed in parantheses, yet it
works as expected in that case.

Yes, you are right. I've jumped too far with my conclusions. It is a
bug, indeed. Interestingly enough, if you enable delayed expansion
globally in windows' registry then the following works (even without
restarting of command interpreter!)

C:\>set xyz=123 & (echo.!xyz!)|more
123


Cheers,

Tomek
 
Yes, that's correct. What I wanted to show was that you can read from
stdin in your parrent batch and set the evironment accordingly. If
that doesn't work for you then you are left with temp files or some
3rd party utilities.

What is wrong with using temp files? As long as a batch is written to remove
them, there should be no reason to avoid them. Sure you have to write one or
two extra lines of code but it's not that big a deal.
 
I think it was 'setenv' but I'm not sure. Just search this group and
you will surely find it.

I am familiar with setx.exe (from the nt/2k resource kits), but that one
sets the top-level "master" environment (in the registry, actually). An
applet to dynamically modify the immediate parent environment would not
be technically possible without the knowledge and cooperation of the
parent process - which would have to be aware that such changes may
happen "under its feet".
Interestingly enough, if you enable delayed expansion globally in
windows' registry then the following works [...]

C:\>set xyz=123 & (echo.!xyz!)|more
123

Interesting. FWIW that's consistent with the theory that the () and |
combination somehow triggers a separate instance of cmd, which inherits
some global default setting for delayed expansion. Why the () would
matter is still a mystery, but then I've long held the belief that
cmd.exe defies human logic ;-)

Cheers,
Liviu
 
foxidrive said:
What do you want to do, in specific terms?

Fight the "!" demons ;-) Quoting from the top post: [...] could have
interesting uses inside a "for" with enabledelayedexpansion, for
example

--------
@echo off
echo.
setlocal enabledelayedexpansion
for %%x in ("%%^^&^!") do (
echo "%%~x"
(echo "%%~x") | more
)

@echo off

for %%x in (%%^&!) do set "var=%%x" && call:1
goto:eof

:1
setlocal enabledelayedexpansion
echo !var!
endlocal

Thanks. I am close to being sold on the passing of shared
variables instead of actual arguments, though I still don't like it too
much from an aesthetical standpoint, and it comes with its own overhead
in case of recursive calls. But it's hard to argue against something
that works ;-)
 
foxidrive said:
Please explain specifically what you need to do and under what
circumstances.

Umm... Pipe standard input into an environment variable, of course ;-)
Granted, that seems more and more like impossible.

Also, understand a little better the cmd syntax contortions. Yet, the
more insight I get into it, the more it looks like hopeless.

In practical terms, the question was related to my other post about
for /d. Consider the skeleton of a directory walker...

--------
@echo off
setlocal disabledelayedexpansion
set /a errs = 0
call :tree "%~1"
if %errs% gtr 0 echo. 1>&2 & echo error: %errs% directories not found
goto :eof

:tree
call :dir "%~1"
for /d %%d in ("%~1\*") do call :tree "%%~d"
goto :eof

:dir
echo "%~1"
if not exist "%~1" echo error: "%~1" -- not found & set /a errs += 1
goto :eof
--------

It's simple, elegant, and fails miserably ;-) on pathnames with ^ carets
or % percents (and ! exclamations, too, if the 2nd line is changed to
enable delayed expansion).

I was looking for (and still weighing on) the "least ugly" workaround.
On my requirements/grading scale:
- the code must allow for some local variables at each dir level;
- also allow for some shared variables to bubble up;
- call parameters are preferred over variables since they are stacked
automatically with each call, while variables need to be explicitly
protected by setlocal's lest they affect the parent context;
- for /d is preferred over for /f ('dir /b') since the latter requires
additional gymnastics to work with unicode pathnames;
- temp files are discouraged for the same unicode reasons.

I've gathered enough good advice (thanks again, everybody here) to work
out at least couple of alternatives. Yet, I am still mystified that what
looks to me like the most trivial application of "for /d" is so
fundamentally flawed.
 
Liviu said:
Umm... Pipe standard input into an environment variable, of course ;-)

Of course, but we already knew the generic description. :-/

Granted, that seems more and more like impossible.

Also, understand a little better the cmd syntax contortions. Yet, the
more insight I get into it, the more it looks like hopeless.

In practical terms, the question was related to my other post about
for /d. Consider the skeleton of a directory walker...

--------
@echo off
setlocal disabledelayedexpansion
set /a errs = 0
call :tree "%~1"
if %errs% gtr 0 echo. 1>&2 & echo error: %errs% directories not found
goto :eof

:tree
call :dir "%~1"
for /d %%d in ("%~1\*") do call :tree "%%~d"
goto :eof

:dir
echo "%~1"
if not exist "%~1" echo error: "%~1" -- not found & set /a errs += 1
goto :eof
--------

It's simple, elegant, and fails miserably ;-) on pathnames with ^ carets
or % percents (and ! exclamations, too, if the 2nd line is changed to
enable delayed expansion).

I was looking for (and still weighing on) the "least ugly" workaround.
On my requirements/grading scale:
- the code must allow for some local variables at each dir level;
- also allow for some shared variables to bubble up;
- call parameters are preferred over variables since they are stacked
automatically with each call, while variables need to be explicitly
protected by setlocal's lest they affect the parent context;
- for /d is preferred over for /f ('dir /b') since the latter requires
additional gymnastics to work with unicode pathnames;
- temp files are discouraged for the same unicode reasons.

I've gathered enough good advice (thanks again, everybody here) to work
out at least couple of alternatives. Yet, I am still mystified that what
looks to me like the most trivial application of "for /d" is so
fundamentally flawed.

Now that you identified your limitation, this is a clear indication that you
need to consider using a HLL, instead of batch, to process such filenames.
You might even consider renaming those files to remove those special
characters from their names, and avoid allowing future files to be named as
such.
 
I've gathered enough good advice (thanks again, everybody here) to work
out at least couple of alternatives. Yet, I am still mystified that what
looks to me like the most trivial application of "for /d" is so
fundamentally flawed.

for /d or for /r have other problems that have been discussed in the past
and which for /f can work around when coupled with the dir command.
 
foxidrive said:
for /d or for /r have other problems that have been discussed in the
past and which for /f can work around when coupled with the dir
command.

Right. Just for the record, though, for /f ('dir /ad /b') also
introduces its own problems with pathnames containing unicode characters
(outside the current codepage), while for /d handles those correctly.
 
Todd Vargo said:
Of course, but we already knew the generic description. :-/

That was (and still is) a simple self-contained question which stands on
its own. I don't see why any more context is necessary or relevant to
the piping issue itself.
Now that you identified your limitation, this is a clear indication
that you need to consider using a HLL, instead of batch, to process
such filenames.

That'd be like using heavy machinery to sink a nail ;-) Plus, the only
thing guaranteed to exist (and be enabled) on every windows is,
unfortunately, cmd.
You might even consider renaming those files to remove those special
characters from their names [...]

Kind of hard on old read-only archives.

Cheers,
Liviu
 
Liviu said:
What do you want to do, in specific terms?

Fight the "!" demons ;-) Quoting from the top post: [...] could have
interesting uses inside a "for" with enabledelayedexpansion, for
example

--------
@echo off
echo.
setlocal enabledelayedexpansion
for %%x in ("%%^^&^!") do (
echo "%%~x"
(echo "%%~x") | more
)

@echo off

for %%x in (%%^&!) do set "var=%%x" && call:1
goto:eof

:1
setlocal enabledelayedexpansion
echo !var!
endlocal

Thanks. I am close to being sold on the passing of shared
variables instead of actual arguments, though I still don't like it too
much from an aesthetical standpoint, and it comes with its own overhead
in case of recursive calls. But it's hard to argue against something
that works ;-)

Batch is not the language of choice for those conserned with "aesthetics"
;-)

But, that said, I have argued against "something that works" when it is
aesthetically ugly and where the intent of the code is ambiguous.


/Al
 
Todd Vargo said:
What is wrong with using temp files? As long as a batch is written to
remove
them, there should be no reason to avoid them. Sure you have to write one
or
two extra lines of code but it's not that big a deal.

In general, I agree. But I still like to ignore the explicit creation of a
temporary file in those cases when this can be avoided without having to get
overly complicated.

For one simple example, how often have you seen this kind of thing in a
batch script:

type y.txt | {some-command}

where "some-command" was a built-in command that requires the user to
confirm an action by entering "y" followed by a carriage-return? I see this
in installation scripts that include the y.txt file, but sometimes it is
created on the fly. It seems to me always better to do this:

echo/y | {some-command}

/Al
 
Back
Top