feat: extend shell with shellExtractRedirects and shellParseVarRef
This commit is contained in:
1 parent
96148ff0e7
commit
6bc12b43a7
1 file changed
+173
+173
@@ -192,6 +192,179 @@ int shellSplitAndOr(char* line, char* segments[], ShellAndOrOp andOrOps[], int m
|
||||
int opCount = 0;
|
||||
for (const char* cursor = line; *cursor; ) {
|
||||
if ((cursor[0] == '&' && cursor[1] == '&') || (cursor[0] == '|' && cursor[1] == '|')) {
|
||||
opCount++;
|
||||
cursor += 2;
|
||||
} else {
|
||||
cursor++;
|
||||
}
|
||||
}
|
||||
|
||||
if (opCount == 0)
|
||||
return 0;
|
||||
if (opCount + 1 > maxSegments)
|
||||
return -1;
|
||||
|
||||
int count = 0;
|
||||
char* segStart = line;
|
||||
char* cursor = line;
|
||||
while (true) {
|
||||
bool atAnd = (cursor[0] == '&' && cursor[1] == '&');
|
||||
bool atOr = (cursor[0] == '|' && cursor[1] == '|');
|
||||
bool atEnd = (*cursor == 0);
|
||||
if (atAnd || atOr || atEnd) {
|
||||
char* segEnd = cursor;
|
||||
while (segEnd > segStart && segEnd[-1] == ' ')
|
||||
segEnd--;
|
||||
*segEnd = 0;
|
||||
|
||||
segments[count++] = segStart;
|
||||
|
||||
if (atEnd)
|
||||
break;
|
||||
|
||||
andOrOps[count - 1] = atAnd ? shellAndOrAnd : shellAndOrOr;
|
||||
|
||||
cursor += 2;
|
||||
segStart = cursor;
|
||||
while (*segStart == ' ')
|
||||
segStart++;
|
||||
cursor = segStart;
|
||||
continue;
|
||||
}
|
||||
cursor++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
bool shellExtractRedirects(char** argv, int* argc, ShellRedirect* out)
|
||||
{
|
||||
int count = *argc;
|
||||
|
||||
/* Validate BEFORE mutating anything: a dangling operator (no filename
|
||||
token after it) must leave argv and argc completely untouched,
|
||||
exactly like shellSplitPipelineStages leaves `line` untouched when it
|
||||
finds no pipe -- the caller needs to tell "malformed, nothing
|
||||
changed" apart from "changed, here is the result" without having to
|
||||
snapshot argv itself first. */
|
||||
for (int i = 0; i < count; i++) {
|
||||
bool isOperator = strcmp(argv[i], "<") == 0 || strcmp(argv[i], "<<<") == 0 ||
|
||||
strcmp(argv[i], ">") == 0 || strcmp(argv[i], ">>") == 0 ||
|
||||
strcmp(argv[i], "2>") == 0 || strcmp(argv[i], "2>>") == 0;
|
||||
if (isOperator && i + 1 >= count)
|
||||
return false;
|
||||
}
|
||||
|
||||
out->inPath = nullptr;
|
||||
out->outPath = nullptr;
|
||||
out->appendOut = false;
|
||||
out->errPath = nullptr;
|
||||
out->appendErr = false;
|
||||
out->hereString = nullptr;
|
||||
|
||||
int writeIndex = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (strcmp(argv[i], "<") == 0) {
|
||||
out->inPath = argv[++i];
|
||||
out->hereString = nullptr;
|
||||
} else if (strcmp(argv[i], "<<<") == 0) {
|
||||
out->hereString = argv[++i];
|
||||
out->inPath = nullptr;
|
||||
} else if (strcmp(argv[i], ">") == 0) {
|
||||
out->outPath = argv[++i];
|
||||
out->appendOut = false;
|
||||
} else if (strcmp(argv[i], ">>") == 0) {
|
||||
out->outPath = argv[++i];
|
||||
out->appendOut = true;
|
||||
} else if (strcmp(argv[i], "2>") == 0) {
|
||||
out->errPath = argv[++i];
|
||||
out->appendErr = false;
|
||||
} else if (strcmp(argv[i], "2>>") == 0) {
|
||||
out->errPath = argv[++i];
|
||||
out->appendErr = true;
|
||||
} else {
|
||||
argv[writeIndex++] = argv[i];
|
||||
}
|
||||
}
|
||||
*argc = writeIndex;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool shellParseVarRef(const char* token, char* nameOut, int nameCap)
|
||||
{
|
||||
if (token[0] != '$' || token[1] == 0)
|
||||
return false;
|
||||
|
||||
int len = 0;
|
||||
for (const char* c = token + 1; *c; c++) {
|
||||
bool isNameChar = (*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z') ||
|
||||
(*c >= '0' && *c <= '9') || *c == '_';
|
||||
if (!isNameChar)
|
||||
return false;
|
||||
len++;
|
||||
}
|
||||
if (len + 1 > nameCap)
|
||||
return false;
|
||||
|
||||
memcpy(nameOut, token + 1, (uint64)len);
|
||||
nameOut[len] = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool shellIsStatusVarToken(const char* token)
|
||||
{
|
||||
return strcmp(token, "$?") == 0;
|
||||
}
|
||||
|
||||
/* Shared expansion rule behind both shellExpandArgv (per-argv-position, an
|
||||
entire spawned program's argument list) and shellExpandRedirect
|
||||
(per-redirect-target) below: `$?` (shellIsStatusVarToken, this shell's
|
||||
own most-recently-completed status, lastStatus above) is checked FIRST
|
||||
since shellParseVarRef can never match it anyway (see
|
||||
shellIsStatusVarToken's own doc comment in shell.h), then a real
|
||||
`$NAME` environment lookup (shellParseVarRef + currentThreadGetEnv, an
|
||||
unset name expanding to an empty string like a real shell's own
|
||||
default). Anything that is neither is returned completely untouched,
|
||||
still pointing into whatever buffer the caller originally handed it --
|
||||
`storage` is only ever written to, and only ever the return value, when
|
||||
an expansion actually happened. */
|
||||
static const char* shellExpandToken(const char* token, char* storage, int storageCap)
|
||||
{
|
||||
if (shellIsStatusVarToken(token)) {
|
||||
snprintf(storage, (uint64)storageCap, "%ld", lastStatus);
|
||||
return storage;
|
||||
}
|
||||
|
||||
char name[envEntryMax];
|
||||
if (shellParseVarRef(token, name, sizeof(name))) {
|
||||
const char* value = currentThreadGetEnv(name);
|
||||
snprintf(storage, (uint64)storageCap, "%s", value ? value : "");
|
||||
return storage;
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
/* Resolves every `$?`/`$NAME` token in argv[0..argc) via shellExpandToken
|
||||
above, overwriting argv[i] with a pointer into storage[i] whenever one
|
||||
expands -- necessary because argv[i] itself is a pointer into the
|
||||
shared line buffer with only enough room for the ORIGINAL (unexpanded)
|
||||
token, never enough for whatever length the variable's value happens to
|
||||
be. */
|
||||
static void shellExpandArgv(char** argv, int argc, char storage[][envEntryMax])
|
||||
{
|
||||
for (int i = 0; i < argc; i++)
|
||||
argv[i] = (char*)shellExpandToken(argv[i], storage[i], envEntryMax);
|
||||
}
|
||||
|
||||
/* Expands `$?`/`$NAME` (shellExpandToken above) in each of ShellRedirect's
|
||||
own target fields (inPath/outPath/errPath/hereString) in place -- these
|
||||
come straight from shellExtractRedirects, which only ever pulls raw
|
||||
tokens out of argv with no expansion of its own, so a redirect target or
|
||||
here-string text written as `$NAME`/`$?` was previously left completely
|
||||
unexpanded (unlike every other argv position, which shellExpandArgv
|
||||
already covered) -- an honestly-scoped gap flagged when `$NAME`
|
||||
expansion was first added, closed here. Each field gets its own
|
||||
|
||||
static void printHelp(void)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user