For AI agents: This document provides a deep-dive analysis of Eagle's
[exec]command internals. For basic command syntax and options, seecore_language.md. For usage examples, seecore_examples.md. For theexec.eaglescript library procedures, seecore_script_library.md.
Eagle's [exec] is not a native-Tcl-style pipeline parser. It is a rich
wrapper around a single .NET ProcessStartInfo launch, with a large option
surface for building one command-line string, handling I/O, callbacks, logging,
credentials, timeouts, and result mapping. The central design difference is that
Eagle usually re-serializes its post-parser argument words into one
ProcessStartInfo.Arguments string, either with a simple ListOps.Concat
path or with the explicit RuntimeOps.BuildCommandLine quoting/escaping path.
Native Tcl, by contrast, treats the [exec] arguments as a shell-pipeline
specification where each word already has semantic meaning as a command word,
redirection token, or pipeline separator.
That one architectural choice explains most of the behavioral differences. In
native Tcl, quoting is primarily about getting the Tcl parser to produce
the words you want, and then [exec] interprets those words as pipeline syntax
and argv items. In Eagle, once the script parser has already produced words,
[exec] often performs a second-stage command-line construction step; in that
stage, literal double-quote characters can be intentional data for Eagle's
algorithm instead of just parser syntax.
Key source files:
| File | Role |
|---|---|
Eagle/Library/Commands/Exec.cs |
Main command implementation (638 lines) |
Eagle/Library/Components/Private/CommandOptions.cs |
Centralized option dictionary factory (see options.md) |
Eagle/Library/Components/Private/ObjectOps.cs |
Option definitions (GetExecOptions), delegated from CommandOptions |
Eagle/Library/Components/Private/RuntimeOps.cs |
Command line building and argument escaping |
Eagle/Library/Components/Private/ProcessOps.cs |
Process creation, capture, and result handling |
Eagle/Library/Components/Private/Enumerations.cs |
EscapeMode flags enum |
Eagle/lib/Eagle1.0/exec.eagle |
Script-level execution utilities |
Native Tcl [exec] treats its arguments as a pipeline specification. The
documented grammar includes |, |&, <, <<, >, 2>, 2>@1, and
related redirection operators, and each distinct command in the pipeline becomes
a subprocess. If the last argument is &, Tcl runs the pipeline in the
background and returns a list of subprocess IDs. Tcl does not perform shell
globbing or shell-like substitution on command arguments by default; arguments
are passed as arguments, not reparsed by a shell unless you explicitly invoke
one.
On Windows, Tcl's [exec] also performs platform-specific quoting/escaping
transparently. Arguments are mapped to the called program's arguments, quotes
are added where needed, and special characters are escaped. For shell built-ins
like dir and copy, Tcl requires you to invoke cmd.exe /c ... explicitly,
because those are not standalone executables.
Tcl's documented option surface is intentionally small: -ignorestderr,
-keepnewline, and --. All I/O control is done through positional
redirection operators and the pipeline syntax.
exec ?options? arg ?arg ...? ?&?- The first non-option argument is the executable file name.
- Subsequent arguments are passed to the executable.
- A trailing
&(as a separate argument) runs the process in the background. - Options begin with
-and are parsed via Eagle's standardGetOptionsmechanism. - The
--marker terminates option processing. - Eagle explicitly does not support Tcl-style pipeline syntax and does
not support Tcl-style redirection syntax such as
< file,> file, or2>&1. The docs instead point users to options like-stdin,-stdout,-stderr,-shell, or to invoking a shell manually, e.g.,exec cmd /c "cmd1 | cmd2".
Eagle's [exec] has 45+ options, defined in ObjectOps.GetExecOptions(),
organized into the following categories:
| Option | Description |
|---|---|
| Output Control | |
-keepnewline |
Don't remove trailing newline from output |
-nocarriagereturns |
Remove carriage returns from output (Windows) |
-trimall |
Trim whitespace from stdout and stderr |
-unicode |
Use Unicode encoding for captured output |
-ignorestderr |
Don't treat stderr output as an error |
| Capture Control | |
-nocapture |
Don't capture stdin or stdout (disables both) |
-nocaptureinput |
Don't capture/redirect stdin |
-nocaptureoutput |
Don't capture stdout/stderr |
-overridecapture |
Use custom handlers instead of wrapping built-in ones |
-noexitcode |
Don't capture exit code |
| Variable Storage | |
-processid varName |
Store process ID in variable |
-exitcode varName |
Store exit code in variable |
-stdin varName |
Read stdin content from variable |
-stdout varName |
Store stdout in variable (instead of returning) |
-stderr varName |
Store stderr in variable |
-stdinobject varName |
Get stdin stream object handle for direct writing |
-setall |
Set all output variables even on error |
| Execution Control | |
-background |
Run process in background (same as trailing &) |
-shell |
Use ShellExecute (opens documents, URLs, etc.) |
-timeout milliseconds |
Maximum time to wait for process exit |
-killonerror |
Kill process if interpreter error occurs |
-success exitCode |
Expected success exit code (default: none) |
-directory path |
Working directory for child process |
-windowstyle style |
Window style: Normal, Hidden, Minimized, Maximized |
| Authentication | |
-domainname name |
Domain for process credentials |
-username name |
Username for process credentials |
-password secureString |
Password for process credentials |
| Callbacks | |
-startcallback script |
Script to call when process starts |
-stdoutcallback script |
Script to call for each stdout line |
-stderrcallback script |
Script to call for each stderr line |
| Logging | |
-stdoutlogpath path |
Log stdout to file |
-stderrlogpath path |
Log stderr to file |
-logtag string |
Tag to include in log entries |
| Argument Processing | |
-commandline |
Build proper command line with quoting |
-dequote |
Remove outer quotes from arguments before building command line |
-quoteall |
Quote all arguments in command line |
-forprocessor |
Use command processor escaping rules |
-escaperanges ranges |
Index ranges of arguments to escape |
-escapesubstring command |
Custom command for substring escaping |
-preprocessarguments command |
Custom command to preprocess arguments |
| Debugging | |
-debug |
Enable debug output |
-trace |
Trace the final command line |
-nonormalize |
Don't normalize paths in debug output |
-noellipsis |
Don't truncate long values in debug output |
| Event Processing | |
-noevents |
Don't process events while waiting |
-nosleep |
Don't sleep while waiting for process |
-userinterface |
Process window messages while waiting |
-eventflags flags |
Event flags for waiting |
| Miscellaneous | |
-nointerpreter |
Don't associate interpreter with process |
-nopreviousprocessid |
Don't set interpreter's PreviousProcessId |
-objectflags flags |
Object flags for stdin stream handle |
-tags list |
Tags for external use |
-- |
End of options |
The full lifecycle of a call to [exec] proceeds through seven stages:
OptionDictionary options = ObjectOps.GetExecOptions(interpreter);
int argumentIndex = Index.Invalid;
code = interpreter.GetOptions(options, arguments, 0, 1,
Index.Invalid, true, ref argumentIndex, ref result);Eagle's generic option parser scans the argument list starting at index 1
(index 0 is the command name "exec" itself). It matches each -flagname
against the OptionDictionary returned by ObjectOps.GetExecOptions().
Options that require values consume the next argument. The parser sets
argumentIndex to the index of the first non-option argument (the executable
file name). This is much broader than Tcl's small switch surface of
-ignorestderr, -keepnewline, and --.
int argumentStopIndex = argumentCount - 1;
bool background = false;
if (SharedStringOps.SystemEquals(
arguments[argumentStopIndex],
Characters.Ampersand.ToString()))
{
argumentStopIndex--;
background = true;
}
else if (options.IsPresent("-background"))
{
background = true;
}If the last argument is literally "&", it is consumed and background mode is
activated. The -background option achieves the same effect. This mirrors
Tcl's trailing & convention.
string execFileName = arguments[argumentIndex];
if (!PathOps.IsRemoteUri(execFileName))
execFileName = PathOps.GetNativePath(execFileName);The first non-option argument is taken as the executable. Forward slashes are
converted to native path separators (on Windows). Remote URIs are preserved
as-is for use with -shell.
A second resolution step occurs later inside ProcessOps.ExecuteProcess().
It rejects a null or empty file name, calls
PathOps.SubstituteOrResolvePath(interpreter, fileName, true, ref remoteUri),
and, if the resulting name is an absolute local path, verifies that the file
exists with File.Exists(). If the name is not an absolute local path, Eagle
deliberately does not pre-verify it, because it may still be something the
OS or shell can resolve (e.g., via PATH lookup). That is why relative or bare
names can still work, while a rooted nonexistent path fails immediately with a
Tcl-compatible "couldn't execute ...: no such file or directory".
This is the most critical stage and where Eagle diverges most significantly from Tcl. There are three distinct code paths for assembling the argument string passed to the child process:
if (commandLine)
{
execArguments = RuntimeOps.BuildCommandLine(
interpreter,
ArgumentList.GetRangeAsStringList(
arguments, argumentStartIndex,
argumentStopIndex, dequote),
escapeSubStringCommand, quoteAll,
forProcessor, true, ref done, ref result);
}When -commandline is specified, arguments are individually processed by
RuntimeOps.BuildCommandLine(), which applies Windows-style command line
quoting and escaping (see Section 7 below). This is the path specifically
intended by Eagle for "build proper command line with quoting" and is the
recommended path when arguments contain spaces, quotes, or backslashes.
else if (escapeRanges != null)
{
execArguments = RuntimeOps.BuildCommandLine(
interpreter,
ArgumentList.GetRangeAsStringList(...),
escapeRanges,
interpreter.InternalCultureInfo,
escapeSubStringCommand, quoteAll,
forProcessor, true, ref done, ref result);
}When -escaperanges is specified (without -commandline), a more selective
variant is used. Only arguments whose zero-based indices fall within the
specified comma-separated ranges are quoted/escaped; the remaining arguments are
appended verbatim. This allows fine-grained control, e.g.,
-escaperanges "1,3-5" would only escape the 2nd, 4th, 5th, and 6th
arguments. So -escaperanges is not "escape everything"; it is selective
argument-level escaping over an otherwise raw command-line string.
else
{
execArguments = ListOps.Concat(arguments,
argumentStartIndex, argumentStopIndex);
}Without -commandline or -escaperanges, arguments are simply concatenated
with spaces. No quoting or escaping is applied. This is the default behavior
and is the simplest mode, but it means arguments containing spaces will not be
properly delimited for the child process.
Importantly, ListOps.Concat is not an argv-preserving join. It joins
arguments with spaces after trimming leading and trailing whitespace from each
one, and skips values that become null or empty after trimming. This means the
default Eagle path removes outer whitespace from each argument and drops
empty/whitespace-only arguments entirely. This is a major semantic difference
from native Tcl [exec], where the already-parsed Tcl words are the logical argv
items for the command/pipeline.
The branch order in Exec.cs is exact: -commandline wins first; only if
it is absent does Eagle consider -escaperanges; only if both are absent does
Eagle fall back to ListOps.Concat. This means that -dequote, -quoteall,
-forprocessor, and -escapesubstring only matter when one of the
BuildCommandLine paths is active. In the default ListOps.Concat path,
those options are effectively inert because the source never consults them
there.
code = ProcessOps.PreProcessArguments(
interpreter, preProcessArgumentsCommand,
execFileName, directory, ref execArguments,
ref done, ref result);If -preprocessarguments is specified, the given Eagle/Tcl command is invoked
with the executable name, working directory, and assembled argument string
appended as additional arguments. The command can:
- Return normally (
ReturnCode.Ok): no change to arguments. - Use
return -code return <value>: replace the argument string with<value>. - Use
return -code continue: abort the exec entirely (successfully, settingdone = true). - Return error: abort with an error.
This is an important difference from Tcl: Eagle exposes a dedicated post-build, pre-launch rewrite hook for the final argument string.
The assembled arguments are passed to ProcessOps.ExecuteProcess(), which
populates a .NET ProcessStartInfo (setting FileName, WorkingDirectory,
and Arguments), starts the process, manages I/O capture, waits for
completion (unless background), and collects exit code and output. If -trace
is enabled, a trace is emitted containing the final startInfo.FileName and
startInfo.Arguments. This confirms that Eagle's end product is a FileName
plus one command-line string, not a Tcl pipeline graph or an argv vector API.
ProcessOps.HandleCaptureResults() populates the requested output variables
(-processid, -exitcode, -stdout, -stderr), applies output
transformations (-nocarriagereturns, -trimall), and checks exit codes
against the -success value. If the exit code doesn't match the expected
success code, errorCode is set to {CHILDSTATUS pid exitcode} and the
command returns an error with the message "child process exited abnormally".
The -processid variable is set even on failure or even if execution was not
attempted; its value may be zero to indicate that no process was started.
-dequote is narrow and precise. ArgumentList.GetRangeAsStringList(..., bool dequote) calls FormatOps.StripOuter(string, Characters.QuotationMark) when
dequote is true. So Eagle is not running a general shell dequoting pass; it
is stripping one outer pair of double quotes from each selected argument
before command-line construction. This is useful when arguments arrive
pre-quoted from script construction and need to be re-quoted by the builder
using its own rules.
It operates in both the -commandline and -escaperanges branches, and
not in the default ListOps.Concat branch.
The core quoting logic resides in RuntimeOps.AppendCommandLineArgument()
(lines 3216-3595 of RuntimeOps.cs). It implements Windows
CommandLineToArgvW-compatible quoting, which is the standard convention for
how the Windows C runtime parses command line strings. This is building a
command-line string, not a Tcl list — a deliberately Windows-style rule set.
Three characters are treated as "special" and trigger wrapping an argument in double quotes:
char[] specials = {
Characters.Space, Characters.QuotationMark, Characters.Backslash
};An argument is wrapped (surrounded with "...") if:
-quoteallis active, or- The argument contains any of: space, double quote, backslash.
For each character in the argument:
-
Caret (
^) with-forprocessor: Doubled to^^(becausecmd.exetreats^as an escape character). -
Double quote (
"): Escaped as\". With-forprocessor, escaped as\^"(the caret protects the quote fromcmd.exeinterpretation while the backslash protects it fromCommandLineToArgvWparsing). -
Backslash sequences (
\): Follows theCommandLineToArgvWconvention:- A run of
Nbackslashes followed by a double quote becomes(2N+1)backslashes followed by a quote (i.e., each backslash is doubled, plus one more to escape the quote). With-forprocessor, a caret is also inserted before the quote. - A run of
Nbackslashes not followed by a quote passes through asNbackslashes (followed by the next character). - A run of
Nbackslashes at the end of an argument (which will be followed by the closing") is doubled to2Nbackslashes in normal mode, but not doubled in-forprocessormode (sincecmd.exedoes not applyCommandLineToArgvWdoubling rules).
- A run of
-
All other characters: Passed through unchanged.
This is much more explicit than native Tcl's "the platform quoting is done for you" model.
At three points during argument processing — start (before the opening
quote), middle (each character), and end (before the closing quote) —
the method calls MaybeEscapeSubString(). If -escapesubstring specifies a
command, that command is evaluated with the following appended arguments:
<command> <argument_value> <start_index> <stop_index> <escape_mode>The command's return code controls behavior:
| Return Code | Effect |
|---|---|
Ok |
Use the result as the replacement text; skip default handling |
Error |
Record a warning; continue with default handling |
Return |
Stop all further processing immediately |
Break |
Append result, then finalize this argument |
Continue |
Use default handling (the default) |
This hook is not just advisory; it is a real control point in the escaping pipeline.
The EscapeMode flags passed to the callback indicate context:
[Flags]
internal enum EscapeMode
{
None = 0x0,
Start = 0x100, // Before the opening quote
Middle = 0x200, // Processing a character within the argument
End = 0x400, // Before the closing quote
QuoteAll = 0x1000, // -quoteall was specified
ForProcessor = 0x2000, // -forprocessor was specified
NoComplain = 0x4000 // Suppress warnings
}The -escaperanges option accepts a comma-separated list of index ranges (e.g.,
"0,2-4,6") specifying which arguments (by zero-based position) should be
processed through the quoting/escaping logic. Arguments whose indices are not in
any range are appended verbatim with only a space separator.
The implementation:
ParseIndexRanges()parses the range string into anIndexRangeListusing a regex-validated, comma-separated format.MarkIndexRanges()builds anIndexDictionarymapping each argument index to a boolean (true = should be escaped).AppendCommandLine()iterates through arguments: marked indices go throughAppendCommandLineArgument(); unmarked indices are appended raw.
A ProcessStartInfo object is constructed with:
FileNameandArgumentsfrom the resolved values.WorkingDirectoryfrom the resolved directory (if-directoryis specified, the path is resolved to a full path; otherwise, the current directory of the process is used).UseShellExecutefrom the-shellflag.RedirectStandardInput/Output/Errorbased on capture settings.StandardOutputEncoding/StandardErrorEncodingset to Unicode (UTF-16) when-unicodeis specified; otherwise uses the .NET default encoding.WindowStylefrom the-windowstyleoption.Domain,UserName,Passwordfrom authentication options.
The -shell option maps directly to UseShellExecute = true. This opens
documents, URLs, or folders with the associated application. Using the shell
prevents some other features, especially output capture. This differs from
native Tcl's model, where shell built-ins are reached by explicitly invoking
cmd.exe /c ..., not by an option that flips the process-launch mechanism.
ProcessOps.HandleCaptureOptions() implements the input/callback setup layer:
- If
-stdin varNameis used, Eagle reads the variable value and later writes it to the child's standard input. - If
-stdinobject varNameis used, Eagle creates an object handle, stores the object name back into the variable, and later assigns the childStandardInputwriter into that object. - The same helper validates
-startcallback,-stdoutcallback, and-stderrcallback.
The redirection policy in CreateStartInfo sets RedirectStandardOutput only
when not using the shell and when either the process is foregrounded or a log
path is in use. RedirectStandardError is similar, except it is also disabled
by -ignorestderr. In other words, Eagle's implementation of -ignorestderr
is not just "don't complain later"; it avoids redirecting/capturing stderr in
the first place. That achieves a Tcl-like user-facing effect by a different
internal mechanism.
Eagle uses .NET's asynchronous output reading (BeginOutputReadLine /
BeginErrorReadLine) with DataReceivedEventHandler delegates:
- Pre-start setup (
PreSetupForCapture): Initializes capture buffers and attaches event handlers before the process starts. - Post-start setup (
PostSetupForCapture): Begins async reading and writes stdin data after the process starts. The standard input stream is closed after writing to allow the child process to proceed. - Wait loop: For foreground processes, processes Eagle events while waiting
for exit (unless
-noevents). The process is waited on synchronously to ensure all output is captured. - Result collection (
GetCaptureData): Retrieves accumulated stdout and stderr from the capture buffers. Unless-keepnewlineis set, a trailing newline is stripped from both stdout and stderr.
Three callback hooks are available, each accepting an Eagle ICallback object:
-startcallback: Invoked as anEventHandlerafter the process starts and I/O redirection is fully set up.-stdoutcallback: Invoked as aDataReceivedEventHandlerfor each line of stdout.-stderrcallback: Invoked as aDataReceivedEventHandlerfor each line of stderr.
When -overridecapture is set, the custom handlers replace the built-in
capture handlers rather than wrapping them.
HandleCaptureResults() performs the following in order:
- Process ID variable: Always set (even on failure or if execution was not attempted); value may be zero.
- Carriage return removal (
-nocarriagereturns): Strips\rfrom stdout and stderr, leaving only\nas line separators. - Whitespace trimming (
-trimall): Trims all surrounding whitespace from stdout and stderr. - Variable population: Sets
-exitcode,-stdout, and-stderrvariables (only for foreground, non-shell, captured executions). With-setall, these variables are set even on error. - Exit code checking: If
-successwas specified and the actual exit code differs, setserrorCodeto{CHILDSTATUS processId exitCode}, sets the error message to "child process exited abnormally", and returns an error. This deliberately adopts Tcl-style signaling, which is why helpers likemaybeGetExitCodework naturally with Eagle. - Error propagation: On failure, the result is set to the error output.
Tcl:
exec ls -la | grep ".txt" | wc -lEagle: Pipelines are not supported. The | and |& characters have
no special meaning. Each [exec] call launches exactly one subprocess. To
achieve pipelines, use the shell:
exec -commandline cmd /c "ls -la | grep .txt | wc -l"
# or on Unix:
exec -commandline /bin/sh -c "ls -la | grep .txt | wc -l"Tcl:
exec program < input.txt > output.txt 2>&1
exec program << "inline input"
exec program >& /dev/null
exec program 2> errors.txtEagle: The <, >, >>, 2>, 2>>, >&, >@, 2>@, <@, and <<
redirection operators are not recognized. Instead, Eagle provides structured
options:
| Tcl Syntax | Eagle Equivalent |
|---|---|
< file |
-stdin varName (read file into variable first) |
<< string |
-stdin varName (set variable to the string) |
> file |
Capture result, then write to file |
2> file |
-stderr varName, then write to file |
2>&1 |
-ignorestderr (to suppress stderr errors), or capture both separately |
>@ channel |
-stdoutcallback with custom handler |
<@ channel |
-stdinobject varName for stream access |
Tcl's [exec] has three user-facing options: -ignorestderr, -keepnewline, and --. All
I/O control is done through positional redirection operators and the pipeline
syntax.
Eagle's [exec] has 45+ options organized into categories (output control,
capture control, variable storage, execution control, authentication, callbacks,
logging, argument processing, debugging, event processing, and miscellaneous).
Tcl [exec] is centered on pipeline execution; Eagle [exec] exposes the
underlying process-launch object model.
Tcl on Unix: Each argument in the Tcl list becomes a separate element in the
argv array passed to execvp(). No quoting or escaping is needed because Unix
process creation preserves argument boundaries natively.
Tcl on Windows: Tcl's [exec] builds a Windows command line string from the
argument list, applying its own quoting rules to preserve argument boundaries
through the CommandLineToArgvW parsing step. This is transparent to the user.
Eagle: Arguments are assembled into a single command line string passed to
ProcessStartInfo.Arguments. By default (without -commandline), arguments are
concatenated via ListOps.Concat — no quoting is applied, whitespace is
trimmed, and empty arguments are dropped. The -commandline option must be
explicitly used to get proper Windows-style quoting. This is a significant
behavioral difference:
# Tcl: argument boundaries are preserved automatically
exec program "arg with spaces"
# -> program receives one argument: "arg with spaces"
# Eagle (default): arguments are concatenated via ListOps.Concat
exec program "arg with spaces"
# -> ProcessStartInfo.Arguments = "arg with spaces"
# -> program may see three arguments: "arg", "with", "spaces"
# Eagle (with -commandline): proper quoting
exec -commandline program "arg with spaces"
# -> ProcessStartInfo.Arguments = "\"arg with spaces\""
# -> program receives one argument: "arg with spaces"This is the single most important behavioral fact for wrapper scripts. If you
call Eagle [exec] in its default mode, the command-line text is not built from
a raw argv array; it is built by a [concat]-like pass over the remaining words.
That is why literal quotes or placeholder text can be useful data in Eagle
wrappers in ways that would look suspicious if you were reasoning from native
Tcl alone.
Eagle provides explicit support for escaping arguments destined for a command
processor (cmd.exe). The cmd.exe shell interprets ^ as an escape character
and " has special meaning. With -forprocessor:
- Carets are doubled:
^->^^ - Quotes are escaped with both backslash and caret:
"->\^" - Trailing backslashes at end of argument are not doubled (since
cmd.exedoes not applyCommandLineToArgvWdoubling rules).
This is a feature with no equivalent in Tcl, which handles cmd.exe escaping
internally and transparently on Windows.
Tcl: A non-zero exit code causes [exec] to return TCL_ERROR. The error
code is set to CHILDSTATUS pid exitcode. There is no way to specify an
alternative "success" exit code.
Eagle: By default, exit codes are captured but a non-zero exit code does
not automatically cause an error — unless -success is used. With
-success 0, behavior matches Tcl: a non-zero exit code produces an error
with errorCode set to {CHILDSTATUS pid exitcode}. The -exitcode varName
option allows capturing the exit code into a variable regardless of whether
it's considered a success or failure.
Tcl: Any output on stderr causes [exec] to return an error (unless 2>
or 2>@ redirects it).
Eagle: stderr output does not automatically cause an error. The
-ignorestderr option is available but works differently from Tcl internally:
rather than just suppressing the complaint, it actually prevents
redirecting/capturing stderr in the first place (as visible in the
CreateStartInfo source, marked COMPAT: Tcl). The -stderr varName option
captures stderr content separately into a variable.
Tcl: exec program & returns the process ID(s) of the pipeline.
Eagle: exec program & (or exec -background program) returns the
process ID. The -processid varName option can also capture it into a
variable. Eagle additionally supports:
-timeoutto kill the process after a specified time.-killonerrorto kill the process if the interpreter encounters an error.- Background log paths (
-stdoutlogpath,-stderrlogpath) that continue logging on a thread-pool thread after the[exec]call returns.
Tcl tells you to invoke cmd.exe /c yourself when you want shell
built-ins.
Eagle gives you both explicit shell invocation in the arguments and a
separate -shell option that flips UseShellExecute, primarily for opening
associated documents/URLs, at the cost of output capture.
| Feature | Description |
|---|---|
-shell |
Uses ShellExecute to open documents/URLs with associated apps |
-windowstyle |
Controls window appearance (Normal, Hidden, Minimized, Maximized) |
-domainname/-username/-password |
Run process under different credentials |
-unicode |
Use Unicode encoding for captured output |
-startcallback/-stdoutcallback/-stderrcallback |
.NET event-based callbacks |
-stdinobject |
Direct access to the stdin StreamWriter object |
-eventflags |
Control Eagle event processing during wait |
-userinterface |
Process window messages while waiting (for UI threads) |
-objectflags |
Control .NET object handle behavior for stdin stream |
-timeout |
Kill process after specified milliseconds |
-killonerror |
Kill process on interpreter error |
-stdoutlogpath/-stderrlogpath |
Log captured output to files |
Eagle provides two script-level extension points with no Tcl equivalent:
-
-escapesubstring command: A Tcl/Eagle command invoked at each phase (start, middle, end) during argument escaping. It can override, modify, or abort the default escaping behavior. This enables application-specific quoting conventions. -
-preprocessarguments command: A Tcl/Eagle command invoked after argument assembly but before process execution. It receives the executable name, working directory, and assembled argument string, and can modify or replace the arguments entirely.
Native Tcl starts from already-parsed words and maps them to subprocess arguments/pipeline words, preserving argument boundaries through the process creation mechanism.
Eagle's default path performs a [concat]-style reconstruction that trims each
argument and drops empty/whitespace-only ones entirely. The default Eagle path
is not argv-preserving in the same sense. Quote characters in Eagle can be
deliberate input to Eagle's command-line builder, not just Tcl syntax. This
is especially true when shaping the default ListOps.Concat result or
intentionally feeding the BuildCommandLine logic. A review based only on
native Tcl instincts would miss this, because native Tcl [exec] does not have
Eagle's second-stage command-line construction model.
The Eagle.Execute package (lib/Eagle1.0/exec.eagle) provides script-level
utilities for cross-runtime execution:
Returns the correct shell executable path depending on the runtime:
- .NET Framework:
[info nameofexecutable](the Eagle EXE directly). - Mono:
[lindex [info assembly true] 1](the managed assembly, to be invoked viamono). - .NET Core:
[lindex [info assembly true] 1](the managed assembly, to be invoked viadotnet exec). - Native Tcl:
[info nameofexecutable].
Builds the correct prefix for running a managed executable:
- Mono:
mono "filename" - .NET Core:
dotnet exec "filename" - .NET Framework / Tcl:
"filename"
Executes a sub-shell (Eagle or Tcl) with proper runtime-specific invocation.
Includes a workaround for .NET Core's issue with empty string arguments
(dotnet/cli#8892): empty "" arguments are replaced with " " (a single
space).
Extracts the numeric exit code from an errorCode value in the
CHILDSTATUS pid exitcode format. Works naturally with Eagle because Eagle
adopts Tcl's CHILDSTATUS error code convention.
-
If you want Eagle's intentional quoting/escaping algorithm, use
-commandlineor-escaperanges. These are the code paths designed for proper argument boundary preservation. -
If you do not use those options, Eagle falls back to
ListOps.Concat, and then leading/trailing whitespace is trimmed and empty/whitespace-only arguments are dropped. -
-dequote,-quoteall,-forprocessor, and-escapesubstringare only effective when one of theBuildCommandLinepaths is active. In the defaultListOps.Concatpath, they are inert. -
Literal double quotes can be deliberate input to Eagle's command-line builder, not just Tcl parser syntax. Wrapper code that looks odd from a native Tcl perspective can nevertheless be correct in Eagle.
-
For pipelines or complex shell commands, invoke a shell explicitly:
exec -commandline /bin/sh -c "..."orexec -commandline cmd /c "...".
| Scenario | Tcl Approach | Eagle Approach |
|---|---|---|
| Simple command | exec ls -la |
exec ls -la |
| Arguments with spaces | exec program "arg with spaces" (auto-quoted) |
exec -commandline program "arg with spaces" |
| Pipeline | exec cmd1 | cmd2 |
exec -commandline /bin/sh -c "cmd1 | cmd2" |
| Input from string | exec program << "data" |
set data "data"; exec -stdin data program |
| Input from file | exec program < file.txt |
set d [readFile file.txt]; exec -stdin d program |
| Output to file | exec program > file.txt |
set out [exec program]; writeFile file.txt $out |
| Stderr capture | exec program 2> err.txt |
exec -stderr errVar program |
| Merge stderr to stdout | exec program 2>@1 |
exec -ignorestderr program |
| Background with PID | set pid [exec program &] |
exec -processid pid -background program |
| Timeout | Not built-in | exec -timeout 5000 program |
| Open URL | Not built-in | exec -shell https://example.com |
| Custom credentials | Not built-in | exec -username u -password p program |
| Window style | Not built-in | exec -windowstyle Hidden program |
| Selective arg escaping | Not built-in | exec -escaperanges "1,3" program arg0 arg1 arg2 arg3 |
| Custom escape logic | Not built-in | exec -commandline -escapesubstring myCmd program args |
| Argument pre-processing | Not built-in | exec -preprocessarguments myCmd program args |
Library/Commands/Exec.cs— Main command implementationLibrary/Components/Private/RuntimeOps.cs— Command line building and argument escapingLibrary/Components/Private/ProcessOps.cs— Process creation, capture, and result handlingLibrary/Components/Private/ObjectOps.cs— Option definitions (GetExecOptions)Library/Components/Private/Enumerations.cs—EscapeModeflags enumlib/Eagle1.0/exec.eagle— Script-level execution utilities
core_language.md—[exec]command syntax and options referencecore_script_library.md—Eagle.Executepackage (exec.eagleprocedures)core_examples.md—[exec]usage examples