Bugs with 0.74g

+ Identifiers starting with an underscore '_' are reserved for
the compiler implementation. One should honor this rule if possible.

+ Anywhere COMMAND might loop some time, you can add:
if(chkCBreak(BREAK_INPUT)) {
// ^Break pressed
to fully support ^Break handling.

+ Replace getch() by cgetchar(). You must igore the return value
'\3' and probe with chkCBreak().

There is a statement I has told that SET should work without the
equal sign (yes, people tend to mangle my name :).
That's not true as it written there. I've said that this shall work:

where.c: find_which():
+ The previous version of this function did not make use of searchpath()
because, unlike under Windows, under DOS the search behaviour is
If: PATH=path1;path2
C:\> file

DOS probes:

The current implementation breaks this rule by probing this order:

path.c: cmd_path():
+ when called without arguments, getenv() might return NULL.
MS COMMAND says "No Path" in such case; however, I'd suppress the
message if it's redirected into a file.

internal.c: directory_handler(): (also at other places)
+ A filename buffer is allocated with "128" bytes.
DOS semi-standard defines MAXPATH, POSIX defines PATH_MAX for this

goto.c: cmd_goto():
+ Both WinNT command.com and 4dos interprete the following line (without
the quotes):
":label comment"
as the label 'label', the 'comment' is ignored.
This is different to the current implementation.

+ How about moving the search of the label into readbatchline().
A check for a label has to be performed there anyway, one had to
insert a "search for label" member into the batch context. An insert
the actual search code where the label check currently is located.
If the label is found, the member is NULLed.
If the member is not NULL, the current line is ignored.
If exit_batch() is performed and the member is not NULL, an error
message is displayed.

1) The ^Break code would work ;-)
2) One could use the label search functionality for other things,
e.g. a GOSUB internal command.

echo.c: cmd_echo():
+ ECHO[./\:] doesn't work

dir.c: pause():
+ To enable full ^Break handling, do:
++ replace c = getch() by c = cgetchar()
++ replace c == 3 by chkCBreak(BREAK_INPUT)
++ delete the if(c == 0) statement & br‘nch

+ Perhaps the BREAK_INPUT is not very intentionally, how about
adding a 'case 0:' just before the one within chkCBreak(), in order
to able ^Break checking with: chkCBreak(0)? Or just a macro?

+ _Read_Dir(): When you use findfirst() in dir_list(), you can
use it in _Read_Dir(), too. In this case you won't need to call
stat(), too.

+ To add full ^Break support:
dir_list() seems to be the central function here, and the do {}
loop the central part within. I'd suggest to put:
return 1;
at the top of the do{} loop.

del.c: cmd_del():
+ split() is called with only a char **arg; split() expects an array here.

+ Why are drive etc. static?

+ The "check if the file exists" checks for '*', but not for '?'.
The check however is unnecessary anyway: you call stat(), but
it will definitely fail if you pass a non-existant filename.
Currently "DEL a*" would ignore that stat("a*") fails and happily
use the random values within the statbuf.

I'd change the code as follows (reduced syntax):

filename = malloc(MAXPATH);
strcpy(filename, pattern to delete);

if(stat(filename) && filename is directory)
strcat(filename, "\\*.*");
// ^^^^^^^^ this is the way MS COMMAND works

name = p;
if((p = strrchr(filename, '\\')) == NULL)
p = filename[1] == ':'? &filename[1]: filename - 1;

if(*++p == '*' && (p = strchr(p, '.')) != NULL && p[1] == '*') {
prompt user if to really delete all files

if(findfirst(filename, no attributes) != 0) {
error file not found
// from the return value one can differ "path not found" etc.
else do {
strcpy(name, ff.ff_name);
} while(findnext());

You don't need to change disk and/or drive at all.

command.c: command():
+ Internal commands are matched with any portion up to the first terminator.
MS COMMAND treats any whitespace and the characters returned by
DOS-65-01 offset 10. However, '"' produces an error.

Furthermore, this terminator may trigger special actions, e.g.:

If terminator is a whitespace, '=', ',', or ';', one sees
Otherwise, one sees an empty line.

+ The line buffer should be increased:
++ The limited resorce is the command line argument string to a called
external program. It is 125 bytes long. [125]
++ The name of the executable to spawn might be fully-qualified [+ 80]
++ an '<' and '>>' redirections are present (2* full path/space) [+165]
==> 370 bytes that can be typed in _and_ can be successfully processed!
(Umm +1 '\0' ;-)

You can issue the "command line too long" error, e.g., when the
"rest" parameter is longer than 125 bytes for externals.

Internals may could process even long lines!

batch.c: batch():
+ The ^Break support has been corrupted.
chkCBreak(BREAK_BATCHFILE) checks within batchfiles
If the user presses ^Break, this call will display the
message "Abort batchfile Yes/No/All" or something.
Yes --> terminate the current batch file.
No --> ignore
All --> terminate _all_ batchfiles
That means the current position of chkCBreak(BREAK_BATCHFILE)
should be moved into the if() of the fgets() call, e.g.:
if(chkCBreak(BREAK_BATCHFILE) || fgets() == NULL) {
If the user pressed 'A' in the above mentioned dialog, all
successive chkCBreak(BREAK_BATCHFILE) will return TRUE.
If the user pressed 'Y', excatly one time TRUE is returned.

To make sure the 'A' mode is canceled as soon as all the
batchfiles has been terminated, chkCBreak(BREAK_OUTOFBATCH)
must be called. As I understand the batch handling,
exit_batch() is the best place, just behind the if()
if(bc == NULL)

+ The echo resetting fails, if bc == NULL. Ergo the commented
out code must be re-enabled, but the added line into its
'else' branch.