Why does console output keep self-referencing batch script from exiting cleanly? - windows

I was writing a self-referencing Windows 10 (home ed.) batch script to locate a string in a large number of log files, create a results file and, when finished, open the log file in notepad++. This process sometimes takes a few minutes hence the self-referencing part which allows me to return control to the original command window until the log file is opened (and takes focus).
However, when the second command window, started with the "start" command and the "/b" switch, includes at least one "echo" command it won't exit cleanly and requires me to press the Enter key to fully exit that "nested" command window.
I've distilled the code down to nine lines so you can hopefully see what I mean. To see it in action, save the following as "test.bat" and run it from a command prompt:
#echo off
if "%1" EQU "" call :noArgs & goto :done
echo There was at least one argument.
:done
exit /b
:noArgs
echo There were no arguments.
start "" /b cmd /c test.bat arg1
goto :eof
It will print "There were no arguments." below the prompt followed by "There was at least one argument." at the prompt and then hang, waiting for the Enter key before returning control back to the prompt.
If you remove the line:
echo There was at least one argument.
the Enter key is no longer needed for the second command shell to exit. Similarly, if the output from the echo command is redirected to a file the issue goes away. This problem also occurs without echo commands but if output is generated from EG the type command so it seems it is due to there being some form of console output. This can be easily demonstrated by commenting out both the "echo" line as well as the first line "#echo off" - with commands now being echoed to the console it again hangs before exiting.
I could get around this issue by changing the "start" call to this:
start "" /min cmd /c test.bat arg1
however any output is no longer easily visible in the minimized window so it's a poor solution.
I'd love to know why the code I posted behaves the way it does, why it won't exit cleanly without requiring the Enter key to be pressed. The only clue I have is from the "remarks" column in the matrix on this page Close and exit batch files that states, "Make sure no text is displayed in the console window to make it close automatically at the end of the batch file". However that seems to refer only to Windows 9.x versions of command.com - not EG Windows 10 nor cmd.exe.
Thanks for any input/thoughts.
-s1m0n-

You're misinterpreting the output. If I've understood you rightly, it looks like this:
C:\working\test>test
There were no arguments.
C:\working\test>There was at least one argument.
That happened like this:
C:\working\test> <---- output from first shell
test <---- input
There were no arguments. <---- output from first shell
<---- output from first shell
C:\working\test> <---- output from first shell
There was at least one argument. <---- output from second shell
<---- cursor is here
The second shell is running asynchronously - that's what start does - so the first shell has already finished the batch job and printed the next prompt by the time the second shell gets around to printing its output.
At this point, the second shell has exited, and the first shell is waiting for you to enter a command. It doesn't know that the second shell has printed anything, so it has no reason to think that it needs to reprint the prompt. But if you enter a command, it will work.

Related

How does one close stdin from the windows CMD prompt?

I know that one can close stdin when executing a command from a Linux CLI using logic such as:
0<&- <command>
This simulates/emulates running an application unattended, or within a CI/CD system such as Jenkins/Gitlab that has stdin closed.
Why do I care? Because we have an application that has a "Press any key to continue..." prompt.
When run under Jenkins/Gitlab or anything that doesn't have stdin open, it just moves on..... when run on Linux.
How would I do this, and is this possible to do this from the Windows CMD Window CLI?
I've tried 0<&- and that results in the message
0<& was unexpected at this time.
Google search gives many hits for stdin but all the documentation is on redirection. I haven't found anything for windows for closing.
You can try
command <nul
which basically means "take any input from the NUL device (as an infinite source of "CRLF"'s). This works as long as command takes its input from STDIN (like the pause command) and doesn't flush the input buffer (like the choice command)
If you need textual input (like the set /p command), you need another approach:
echo inputtext|command

Piping not seem to work with cd | explorer in cmd

I've been learning batch scripting so I came across pipes and I/O redirection.
If this works:
tasklist | find "winword"
Why does this not:
cd | explorer
I expect this command to open explorer at current working directory as cd without any parameters outputs current directory and:
explorer %directory%
opens explorer at %directory%.
Is there something I am doing wrong here?
Open a Command Prompt window, type find "blah" and press Enter; you'll see it awaits user/keyboard/console input, which STDIN points to (press Ctrl+Z and Enter to end the prompt). Then type echo blah and press Enter; you'll notice the text blah is printed, so there is display/console output; this is pointed to by STDOUT. A pipe | takes the data at STDOUT from the left-side command and redirects it into STDIN for the right-side command.
Now type explorer into the Command Prompt window; of course an Explorer Window pops up, but what happens in the Command Prompt? right, nothing, it does not await any input at STDIN. So you can pipe to it but it will not care as it does not read at STDIN. In fact, GUI applications do generally not use STDIN and STDOUT, because these things are intended for command line applications.
Yet another example: in a Command Prompt window, type echo C:\Windows; quite obvious what will happen; then type echo C:\Windows| dir; what happens? dir returns the contents of the current directory but not of C:\Windows. Why? well, let's type dir first and see what happens: yes, dir shows the contents of the current directory, and it does not await console input; so at the right side of the pipe it receives data at STDIN but it simply doesn't care. You can try using dir "C:\some\other\folder", without and with the pipe, the output is just the same, STDIN is ignored here.
The echo/dir example also demonstrates the difference between console input (STDIN) and command line arguments or parameters: the path in the command line dir "C:\some\other\folder" is such an argument, and you cannot replace it by data from STDIN. To understand why, you need to distinguish between parse time (when the command is read and parsed by the interpreter) and run time (when the command is actually executed): arguments already have to be present at parse time, whilst STDIN is only relevant during run time, which is later. So we can say they just never meet.
This also reflects the situation with your attempt cd | explorer: the latter accepts command line arguments (which anyway need to be available before execution, so at parse time), but it doesn't care about STDIN. Also the STDOUT data from cd isn't available before execution (but only during run time), so it would arrive too late anyway...

Passing multiple commands to START command in batch file

I'm using start to run a command from a batch file.
SET mycmd=SOME_CMD WITH ARGS
START "Demo" %mycmd%
This works fine, and the resulting cmd window persists after executing the contents of mycmd, even if the batch file was double-clicked - the reason why I'm using start to begin with.
However I would also like to print something in the new cmd window that start opens, before it runs the command.
I'd imagine that I would pass start an echo command, followed by the command I want it to run.
My first naive approach was as follows:
SET mycmd=SOME_CMD WITH ARGS
START "Demo" ECHO Running Command... && %mycmd%
Of course this does not work; start opens a new window which only runs the echo command, and the command after the && divider is run in the original window, not in the new one that the echo ran in.
Basically it performs (start echo) && (my_cmd) instead of start (echo && my_cmd) - parentheses added for clarity, not any actual syntactic meaning
So my question is: Is there a way to pass two commands to start at once? Specifically, I really just want it to echo out some content, and then run a command.
Change && to ^& to escape a single & which then cascades the echo with your command.

Call to .cmd file causes exit from parent .cmd file

I have a .cmd file which calls another .cmd file, as follows
parent.cmd
call "C:\Program Files\Prog1\bin\dostuff.cmd" -abc="def"
After def.cmd has run, the dos window skips to the next line, showing the prompt >
The parent.cmd file has therefore completed execution, according to the command prompt. However, after the call to dostuff.cmd, the parent.cmd file is not complete and has a number of other commands to run.
dostuff.cmd sets a number of environment variables and aliases which are required for the remaining commands in parent.cmd. Therefore it is necessary that dostuff.cmd runs in the same command prompt as parent.cmd.
dostuff.cmd is written by someone else and does all sorts of things which I know nothing of. If I call some other .cmd file of my own devising in the way described above, it executes correctly and then the parent.cmd file continues executing afterwards without any problem.
Therefore something in dostuff.cmd is shutting off the processing of parent.cmd. Any ideas what this could be and how I could stop it/get around it?
Here is the parent.cmd program:
#echo off
:Begin
echo.hello
call "C:\Program Files\Prog1\bin\dostuff.cmd" -abc="def"
echo.goodbye
:End
The output is
C:\Users\cowman\desktop>.\parent.cmd
hello
dostuff.cmd text...blah blah
C:\Users\cowman\desktop>
As you can see, the echo.goodbye code is not called.
Without a listing of dostuff.cmd, we're guessing.
My patented guess would be that dostuff.cmd itself invokes cmd. If you were to respond exit to the second prompt, you should then return to the caller (unless dostuff.cmd again invokes cmd.
if the exit response terminates the cmd session and closes the window, then that's a real mystery.
This sounds that doStuff.cmd is exited by a syntax error in the batch file while redirecting stream 2 to nul.
A syntax error stops immediatly all batch instances/call stack, but the command window stays open.
I suppose the name of your batch file isn't doStuff.cmd and also the parameters are more complex than -abc="def".
As you said in the comments, the command works from the command line, but not from your parent batch, I suppose the parameters contains percent signs or carets.
If they contain percent signs, try to quadruple them.
doStuff.cmd "printf("%d",1)"
convert it to
call doStuff.cmd "printf("%%%%d",1)"
When there are carets involved try something like
set "myCaret=^"
call doStuff.cmd "a caret%%myCaret%%"
Sounds like dostuff.cmd is exiting for some reason, which will cause the whole cmd.exe to exit.
Does it have a "exit" command anywhere?, can try changing to "exit /B" to just exit the script not cmd.exe

is it possible to distinguish when shell is run from command line or from other shell?

For example I can directly call myscript.cmd or in other script I can put a line to myscript.
The reason is that if a script is run on it's own it dissapears as soon as it stop executing, so I can't see the result, so at the end I must add #pause but when I run it from another shell this causes annoyance since console window wouldn't exit that way.
So I look for some kind of 'if' condition to address this issue.
To get your script paused when double-clicked (or by dropping files on it), but terminating the usual way when invoked from console:
#echo off
echo Hello World!
:: ...your ScriptCode...
(((echo.%CMDCMDLINE%)|find /I "%~0")>NUL)&&pause
Unless you create an environment variable like Stu suggested, you're not going to find any that do what you want. You're going to need to write a small program that queries the parent process programmatically and returns a value your script can check. If you're being run from Start->run your parent will be explorer.exe. Otherwise it will be cmd.exe or some other exe.
Sample code to find the parent process can be found here.
Why not set it yourself?
SET RUNNINGFROMOTHERSHELL=YES
CALL MYSCRIPT.CMD
SET RUNNINGFROMOTHERSHELL=
In MyScript.Cmd:
IF "%RUNNINGFROMOTHERSHELL%"=="" GOTO NOPAUSE
PAUSE
:NOPAUSE

Resources