carlos-montiers / enhancedbatch

Enhances your windows command prompt https://www.enhancedbatch.com
Other
5 stars 1 forks source link

Catching exitcode of subject of loop #12

Open lazna opened 4 years ago

lazna commented 4 years ago

Have loop parsing output of program:

for /f %%a in ('program.exe parameter') do (something)

If program.exe failed and produce exitcode, it could not be easily catched. There is a possibility to use conditional execution

for /f %%a in ('program.exe parameter ^|^| set failed^=y ^&^& set failed^=n') do (something)

but value of exitcode is probably definitely lost. It would be nice if exitcode could be kept and (if possible) accesible durring loop processing to control parsing.

adoxa commented 4 years ago

for /f %%a in ('program.exe parameter ^|^| set failed^=y ^&^& set failed^=n') do (something)

Does that work? It doesn't in my tests.

It would be nice if exitcode could be kept and (if possible) accessible during loop processing to control parsing.

I think I can get the exit code, but it's not possible to do it in the loop - the process has exited, the loop is finished. If you want conditional parsing based on the exit code I could modify @@ to set errorlevel and then you parse the string.

set $out=@@program.exe parameter
if errorlevel 1 goto :handle_error
for /f %%a in ("!$out!") do (something)

If it's not practical to get the complete output at once then you're out of luck, it's not possible.

carlos-montiers commented 4 years ago

Seems that currently is not working properly if the command failed:

C:\enhancedbatch>set result=@@volx /?
'volx' is not recognized as an internal or external command,
operable program or batch file.

C:\enhancedbatch>echo %errorlevel%
1

C:\enhancedbatch>volx /?
'volx' is not recognized as an internal or external command,
operable program or batch file.

C:\enhancedbatch>echo %errorlevel%
9009

In this case when is executed directly the errorlevel is 9009, using the popen feature the errorlevel is 1.

adoxa commented 4 years ago

The problem is that popen runs cmd which runs volx, so it is cmd "successfully" returning 1. To return 9009 I'd have to write my own popen, which is not worth the effort.

carlos-montiers commented 4 years ago

The problem is that popen runs cmd which runs volx, so it is cmd "successfully" returning 1. To return 9009 I'd have to write my own popen, which is not worth the effort.

Now I understand, is the same as this:

C:\enhancedbatch>cmd /c volx
'volx' is not recognized as an internal or external command,
operable program or batch file.

C:\enhancedbatch>echo %errorlevel%
1
lazna commented 4 years ago

for /f %%a in ('program.exe parameter ^|^| set failed^=y ^&^& set failed^=n') do (something)

Does that work? It doesn't in my tests.

I saw such solution long time a go in some discussion, not tested by me..

carlos-montiers commented 4 years ago

for /f %%a in ('program.exe parameter ^|^| set failed^=y ^&^& set failed^=n') do (something)

Does that work? It doesn't in my tests.

I saw such solution long time a go in some discussion, not tested by me..

Untested, but not seems clear code.

carlos-montiers commented 4 years ago

@adoxa Maybe is possible create a custom popen that in this case will be use like this: call @spawn var command that will create three variables: var.code // exit code var.stdout // stdout var.stderr // stderr ?

I think var should always use the heap, for avoid fill the capacity of the environment block.

adoxa commented 4 years ago

I was thinking something similar, but with named arguments.

call @pipe out[err]=$out err=$err command...
:: %errorlevel% contains exit code
call @async pid=$pid ret=$ret out[err]=$out err=$err command...

outerr captures both stdout and stderr. Anything not specified is ignored. Could probably combine them into @spawn: if pid or ret is present run asynchronously, otherwise wait.

:: Run command as normal (redundant, use command directly)
call @spawn command...
:: Run command, capture stdout to $out (stderr is displayed), wait for it to finish
call @spawn out=$out command...
:: Run command in the background, additional extensions will make use of the pid
call @spawn pid=$pid outerr=$out command...

(There's the problem of running a background process in the same console or a new one, but that it is a discussion for a separate issue. Actually, this whole comment is a separate issue...)

carlos-montiers commented 4 years ago

Jason I really like the syntax of PIPE, very useful. And I think this will replac the popen feature that I don't like with the double at syntax. Maybe we can also add a timeout parameter? What is the difference between PIPE and SPAWN ?

adoxa commented 4 years ago

Pipe always captures output; spawn just runs, optionally capturing output. The arguments would be normal options, now, too (call @spawn /pid $pid ...).

lazna commented 4 years ago

Today I am dealing with how to capture exitcodes of chained programs:

command.exe 2>NUL | findstr "string" >NUL

Need to distinguish situation if 'command.exe' failed, from situation the 'command.exe' succeed but its output does not contain "string". By brief googling result, there is no simple solution.

Is it possible to catch both (all in case more chained commands are used) exitcodes and store it for later analysis?

adoxa commented 4 years ago

Looks like I should be able to do that. Is it okay if they're in reverse order? That would simplify things a lot, and work with all previously run programs. How about @exitcodeN, where N is the number of the previously run program (16 should be enough, right?). Then you could have something like this:

progA
progB | progC
progD
echo progA returned %@exitcode4%
echo progB returned %@exitcode3%
echo progC returned %@exitcode2%
echo progD returned %@exitcode1%
progE | progF | progG
echo progE returned %@exitcode3%
echo progF returned %@exitcode2%
echo progG returned %@exitcode1%

Hm, might be better to use negative numbers.

progA | progB | progC
echo progA returned %@exitcode-3%
echo progB returned %@exitcode-2%
echo progC returned %@exitcode-1%

Maybe not, that looks too much like it's subtracting a number from the exit code. What about multiple and numbered tildes?

progA | progB | progC | progD
echo progA returned %@exitcode~3%
echo progB returned %@exitcode~~%
echo progC returned %@exitcode~%
echo progD returned %@exitcode%

Yeah, I think that works for me.

lazna commented 4 years ago

Syntax does not matter for me. So variable %@exitcode~~2% could have following states:

1 - Undefined (2 or more programs never been piped togeather from beginning of script) 2 - Defined by value 0 (second program from end of last pipeline return exitcode zero) 3 - Defined by value 1+ (second program from end of last pipeline return exitcode except zero) 4 - ??? What in case any program in the pipeline does not return any exitcode? Leave according variable undefined?

It will be fine to have simple way to reset ALL %@exitcode% variables at once to undefined state to make debugging easier, or do it automaticaly on every next pipeline run?

adoxa commented 4 years ago

Only one tilde, but yes. (One tilde with a number, or multiple tildes in lieu of a number.)

1 - No, it will default to 0. It's all programs, not just pipes. 3 - It'll be signed, so negative will be possible, too. 4 - There's always an exit code.

It's far simpler to not reset it at all (technically I will hook API function GetExitCodeProcess and store the result in a stack, which is very easy; resetting it for a pipeline means identifying that code in each binary, which is not exactly hard, but quite time consuming). A function to reset it would be easy, though, perhaps the somewhat verbose but obvious: call @ResetExitCodes [N] (set all codes to 0 or N).

lazna commented 4 years ago

4 - There's always an exit code.

Thats right, but if some program does NOT produce exitcode, errorlevel preserve its previous value, isnt? That is why I propose to keep our extra variable undefined by default and have some reset method too. If there is no pipeline, we could use %errorlevel% as usual. Am I mis someting??

I am also wondering if '&&' is a kind of alias for exitcode 0 and '||' for exicode 1+

adoxa commented 4 years ago

Every program produces an exit code (not every program provides a meaning for it, though); some internal commands do not set errorlevel (depending if it's .bat or .cmd). My implementation will get the exit code of every process, so may as well make use of it.

prog1 && prog2 is basically equivalent to:

prog1
if %errorlevel% == 0 prog2

prog1 || prog2 is basically equivalent to:

prog1
if not %errorlevel% == 0 prog2
DaveBenham commented 4 years ago

Thats right, but if some program does NOT produce exitcode, errorlevel preserve its previous value, isnt?

exitcode and ERRORLEVEL are not the same thing. ERRORLEVEL is strictly a batch (cmd.exe) concept, that indeed does get preserved if it is not explicitly set. But as Jason said, every program produces an exit code, with a default of 0 if not explicitly set.

&& does indeed respond to exitcode 0, and || to exitcode non-zero.

lazna commented 4 years ago

Thats right, but if some program does NOT produce exitcode, errorlevel preserve its previous value, isnt? But as Jason said, every program produces an exit code, with a default of 0 if not explicitly set.

So why here is word "almost"?

"Almost all applications and utilities will set an Exit Code when they complete/terminate." https://ss64.com/nt/errorlevel.html

There are also notified some exceptions:

https://groups.google.com/forum/#!msg/microsoft.public.win2000.cmdprompt.admin/XHeUq8oe2wk/LIEViGNmkK0J

adoxa commented 4 years ago

Perhaps that's where the "meaning" comes in - they return something, but it doesn't mean anything. All processes return an exit code, because you can't exit a process without doing so. Internal commands aren't processes, but they do generally return success/failure and set errorlevel accordingly.

lazna commented 4 years ago

All processes return an exit code, because you can't exit a process without doing so.

Thats the key information I do not know. Thanks

carlos-montiers commented 4 years ago

4 - There's always an exit code.

Thats right, but if some program does NOT produce exitcode, errorlevel preserve its previous value, isnt? That is why I propose to keep our extra variable undefined by default and have some reset method too. If there is no pipeline, we could use %errorlevel% as usual. Am I mis someting??

I am also wondering if '&&' is a kind of alias for exitcode 0 and '||' for exicode 1+

@lazna Info: The differences between .CMD and .BAT as far as CMD.EXE is concerned are: With extensions enabled, PATH/APPEND/PROMPT/SET/ASSOC in .CMD files will set ERRORLEVEL regardless of error. .BAT sets ERRORLEVEL only on errors.

carlos-montiers commented 4 years ago

Now, the syntaxis for the test has changed to:

set result := @@volx /?
echo %errorlevel%

The problem is that popen runs cmd which runs volx, so it is cmd "successfully" returning 1. To return 9009 I'd have to write my own popen, which is not worth the effort.

@adoxa That test already returns 1 on errorlevel. Now we have My_popen and My_wpopen, but I think is not needed to customize it for care about the errorlevel of the internal command. For internal commands: interpret 1 as an error is good.