Loading commands/shell/while.cpp +81 −74 Changes for commands/shell/while.cpp: 81 added lines, 74 removed lines. Original line number Diff line number Diff line Loading @@ -30,6 +30,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // condition that never becomes false. Shell-only builtin, no symlink. #include <iostream> #include <cstring> #include <cerrno> #include <fcntl.h> #include <unistd.h> Loading @@ -38,9 +40,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace fresh { namespace cmd { class WhileCommand : public ShellCommand { // "while COND; do BODY; done [REDIRECTIONS]" and its inverse "until". // Redirections after "done" apply to the whole loop ("done < file", // "done > out 2>&1"), as does a here-document. class LoopCommand : public ShellCommand { public: WhileCommand():ShellCommand("while"){} LoopCommand(const char *name,bool until):ShellCommand(name),_until(until),_name(name){} int runCommand(int argc,const char **argv){ std::string remainder=(argc>1 && argv[1]) ? argv[1] : ""; Loading @@ -48,13 +53,13 @@ namespace fresh { namespace cmd { size_t doIdx=findShellKeyword(words,0,words.size(),"do"); if(doIdx==words.size()){ std::cerr << "while: expected 'do'" << std::endl; std::cerr << _name << ": expected 'do'" << std::endl; return 2; } size_t doneIdx=findShellKeyword(words,doIdx+1,words.size(),"done"); if(doneIdx==words.size()){ std::cerr << "while: expected 'done'" << std::endl; std::cerr << _name << ": expected 'done'" << std::endl; return 2; } Loading @@ -64,24 +69,10 @@ namespace fresh { namespace cmd { Shell &shell=Shell::instance(); // "done <<WORD" -- a here-doc redirect on the loop AS A WHOLE // (e.g. the kernel's own tools/lib/subcmd/../sync-check.sh: // "while read -r f; do ...; done <<EOF\n$FILES\nEOF"), not more // statements after it (this builtin's other, "; more..." use of // `trailing`, handled below). Must be applied to fd 0 BEFORE the // loop below ever runs: the loop's own CONDITION is frequently // exactly the "read" that's supposed to consume this body (as // in the sync-check.sh example above), so redirecting only // after the loop returns is already too late -- every previous // build of this shell instead left fd 0 pointed at whatever it // already was (a real terminal or an unrelated inherited pipe), // so that first "read" blocked forever waiting for input that // was never coming, or silently consumed the wrong stream. int savedStdin=-1; int savedStdin=-1,savedStdout=-1,savedStderr=-1; std::string heredocTempFile; HeredocSpec heredocSpec; bool hasHeredoc=extractHeredocSpec(trailing,heredocSpec); if(hasHeredoc){ if(extractHeredocSpec(trailing,heredocSpec)){ heredocTempFile=shell.collectHeredocBody(heredocSpec); if(!heredocTempFile.empty()){ int fd=open(heredocTempFile.c_str(),O_RDONLY); Loading @@ -91,42 +82,33 @@ namespace fresh { namespace cmd { close(fd); } } trailing.clear(); } // the remaining trailing words are redirections for the loop std::string trailingCommand; if(!trailing.empty()){ Redirection redir; std::vector<std::string> rest=extractRedirections(tokenizeShellArgs(trailing),redir); if(!rest.empty()){ trailingCommand=trailing; // not redirections: run it after the loop, as before }else if(!applyRedirections(redir,savedStdin,savedStdout,savedStderr)){ restore(savedStdin,savedStdout,savedStderr); return 1; } } // Default for a loop that genuinely never iterates (condition // false first try) -- matching real shells. `condition` is // evaluated lazily inside the loop below, not yet here, so // (unlike if/for/case's own analogous fallback) there is // nothing to read back from shell.exitCode() yet at this // point; see the post-loop check below instead for the // "fatal error stopped the shell before any iteration" case. int status=0; const long maxiterations=1000000L; long iterations=0; int status=0; shell.enterLoop(); // false is how the loop ends, so the condition must not // trigger set -e. while(shell.running() && !shell.returning() && !shell.breaking() && !shell.continuing() && shell.parse(condition,false)==0){ if(++iterations>maxiterations){ std::cerr << "while: iteration limit exceeded" << std::endl; while(shell.running() && !shell.returning() && !shell.breaking() && !shell.continuing()){ bool condTrue=shell.parse(condition,false)==0; if(condTrue==_until) break; } if(!body.empty()) status=shell.parse(body); // A pending "return" (see Shell::returning()) must break // this loop for good, same as "exit" already does via // running() -- so it can keep unwinding up to the // callFunction() waiting to consume it, instead of the loop // re-testing its condition and running another iteration. if(!shell.running() || shell.returning()) break; // "break"/"continue" (see Shell::breaking()/continuing()): // this loop is the one that actually consumes either // signal, same reasoning as for.cpp's own handling (see // its comment for the full explanation of the // consume*Level() true/false split). if(shell.breaking()){ shell.consumeBreakLevel(); break; Loading @@ -139,45 +121,70 @@ namespace fresh { namespace cmd { } shell.exitLoop(); // Restore whatever fd 0 really was before this loop redirected // it above, and drop the temp file backing the heredoc body -- // both regardless of how the loop ended (normal exhaustion, // break, return, or a fatal error), same as any other // redirection's scope never outliving the construct it was // attached to. if(savedStdin>=0){ dup2(savedStdin,0); close(savedStdin); } std::cout.flush(); std::cerr.flush(); restore(savedStdin,savedStdout,savedStderr); if(!heredocTempFile.empty()) unlink(heredocTempFile.c_str()); // Unlike if/for/case (whose condition/subject/list is always // evaluated into a separate local BEFORE their own "status" // fallback is set up, so a fatal trigger there is already // reflected by the time that fallback runs), this loop's // condition is evaluated lazily inside the `while(...)` // controlling expression itself -- including on the very // first pass, which happens AFTER `status` above was already // initialized. Re-check here instead: if the shell is still // running, nothing overrides a legitimately-computed `status` // (whether left at its initial 0 for a loop that genuinely // never iterated, or set by the loop body's own last run); // if a "${var?word}" fired during any condition check (first // pass or later) it already stopped the shell via // requestExit(), and that real code must win here instead of // being left at whatever `status` was before that happened. if(!shell.running()) status=shell.exitCode(); if(!trailing.empty() && shell.running() && !shell.returning() && if(!trailingCommand.empty() && shell.running() && !shell.returning() && !shell.breaking() && !shell.continuing()) status=shell.parse(trailing); status=shell.parse(trailingCommand); return status; } private: bool _until; const char *_name; static bool redirect(int target,int fd,int &saved){ if(fd<0) return false; if(saved<0) saved=dup(target); dup2(fd,target); close(fd); return true; } bool applyRedirections(const Redirection &r,int &in,int &out,int &err){ if(r.hasStdin && !redirect(0,open(r.stdinFile.c_str(),O_RDONLY),in)){ std::cerr << "fresh: " << r.stdinFile << ": " << strerror(errno) << std::endl; return false; } if(r.hasStdout && !redirect(1,open(r.stdoutFile.c_str(),O_WRONLY|O_CREAT|(r.append ? O_APPEND : O_TRUNC),0644),out)){ std::cerr << "fresh: " << r.stdoutFile << ": " << strerror(errno) << std::endl; return false; } if(r.hasStderr && !redirect(2,open(r.stderrFile.c_str(),O_WRONLY|O_CREAT|(r.stderrAppend ? O_APPEND : O_TRUNC),0644),err)){ std::cerr << "fresh: " << r.stderrFile << ": " << strerror(errno) << std::endl; return false; } if(r.stderrToStdout) redirect(2,dup(1),err); if(r.stdoutToStderr) redirect(1,dup(2),out); return true; } static void restore(int in,int out,int err){ if(in>=0){ dup2(in,0); close(in); } if(out>=0){ dup2(out,1); close(out); } if(err>=0){ dup2(err,2); close(err); } } }; class WhileCommand : public LoopCommand { public: WhileCommand():LoopCommand("while",false){} }; class UntilCommand : public LoopCommand { public: UntilCommand():LoopCommand("until",true){} }; FRESH_REGISTER_COMMAND(WhileCommand); FRESH_REGISTER_COMMAND(UntilCommand); }} src/shell.cpp +52 −8 Changes for src/shell.cpp: 52 added lines, 8 removed lines. Original line number Diff line number Diff line Loading @@ -1259,7 +1259,7 @@ size_t fresh::findShellKeyword(const std::vector<ShellWord> &words,size_t from,s i+=consumed-1; continue; } if(w=="if" || w=="for" || w=="while" || w=="case") if(w=="if" || w=="for" || w=="while" || w=="until" || w=="case") ++depth; else if(depth>0 && (w=="fi" || w=="done" || w=="esac")) --depth; Loading @@ -1285,7 +1285,7 @@ static bool isCompoundStart(const std::vector<fresh::ShellWord> &words,size_t at if(at>=words.size()) return false; const std::string &w=words[at].text; return w=="if" || w=="for" || w=="while" || w=="case" || w=="{" || w=="!"; return w=="if" || w=="for" || w=="while" || w=="until" || w=="case" || w=="{" || w=="!"; } fresh::Shell::Shell(){ Loading Loading @@ -1879,7 +1879,8 @@ std::vector<std::string> fresh::splitPipelineStages(const std::string &segment, // "echo done" 's own "done" (cat's argument, not a real loop // terminator) can never desync this count. int keywordDepth=0; static const char *kwOpeners[]={"if","elif","while","for","case"}; // "elif" is no opener: it continues an "if" that "fi" closes once static const char *kwOpeners[]={"if","while","until","for","case"}; static const char *kwClosers[]={"fi","done","esac"}; auto atKeywordStmtStart=[&](size_t pos)->bool{ size_t p=pos; Loading @@ -1889,7 +1890,15 @@ std::vector<std::string> fresh::splitPipelineStages(const std::string &segment, char c=segment[p-1]; if(c==';' || c=='\n' || c=='|') return true; if(c=='&' && p>=2 && segment[p-2]=='&') return true; return false; // right after a keyword that itself expects a command // ("do while ...", "then if ...", "! while ...") size_t wordEnd=p,wordStart=p; while(wordStart>0 && segment[wordStart-1]!=' ' && segment[wordStart-1]!='\t' && segment[wordStart-1]!=';' && segment[wordStart-1]!='\n' && segment[wordStart-1]!='|') --wordStart; std::string word=segment.substr(wordStart,wordEnd-wordStart); return word=="do" || word=="then" || word=="else" || word=="elif" || word=="if" || word=="while" || word=="until" || word=="!"; }; auto isWordBoundary=[&](size_t pos)->bool{ return pos>=segment.size() || segment[pos]==' ' || segment[pos]=='\t' || Loading Loading @@ -2921,6 +2930,22 @@ int fresh::Shell::runSimple(const std::string &segment){ stageRedirs.push_back(redir); continue; } // "... | while ...; done": a compound command stage is a body run // by parse() in the stage's child, like "{ }" above { size_t b=rawstages[i].find_first_not_of(" \t\r\n"); std::string kw; if(b!=std::string::npos){ size_t e=rawstages[i].find_first_of(" \t\r\n;",b); kw=rawstages[i].substr(b,e==std::string::npos ? std::string::npos : e-b); } if(kw=="while" || kw=="until" || kw=="for" || kw=="if" || kw=="case"){ stageSubshellBody.push_back(rawstages[i]); stageTokens.push_back(std::vector<std::string>()); stageRedirs.push_back(redir); continue; } } stageSubshellBody.push_back(""); std::vector<std::string> tokens=tokenizeShellArgs(rawstages[i]); Loading Loading @@ -3274,7 +3299,25 @@ int fresh::Shell::runSegmentWords(const std::string &input,const std::vector<She return (status==0) ? 1 : 0; } if(first=="if" || first=="for" || first=="while" || first=="case"){ // "while ...; done | cmd", "{ ...; } | cmd": a compound command as // the first stage of a pipeline goes through runSimple(), which runs // each compound stage as a body of its own if(first=="if" || first=="for" || first=="while" || first=="until" || first=="case" || first=="{"){ int d=0; for(size_t i=from;i<to;++i){ const std::string &w=words[i].text; bool stmtStart=(i==from || words[i-1].text==";" || words[i-1].text=="&&" || words[i-1].text=="||" || words[i-1].text=="|" || words[i-1].text=="do" || words[i-1].text=="then" || words[i-1].text=="else" || words[i-1].text=="if" || words[i-1].text=="elif" || words[i-1].text=="while" || words[i-1].text=="until" || words[i-1].text=="!"); if(stmtStart && (w=="if" || w=="for" || w=="while" || w=="until" || w=="case" || w=="{")) ++d; else if(stmtStart && d>0 && (w=="fi" || w=="done" || w=="esac" || w=="}")) --d; else if(d==0 && w=="|") return runSimple(shellWordsRawText(input,words,from,to)); } } if(first=="if" || first=="for" || first=="while" || first=="until" || first=="case"){ ShellCommand *cmd=find(first); if(!cmd){ std::cerr << first << ": not supported" << std::endl; Loading Loading @@ -3814,9 +3857,10 @@ int fresh::Shell::parse(const std::string &input,bool applyErrexit){ words[i-1].text=="&&" || words[i-1].text=="||" || words[i-1].text=="|" || words[i-1].text=="if" || words[i-1].text=="elif" || words[i-1].text=="while" || (i==segstart+1 && words[segstart].text=="!")); if(atStmtStart && (w=="if" || w=="for" || w=="while" || w=="case" || w=="{")) words[i-1].text=="while" || words[i-1].text=="until" || words[i-1].text=="do" || words[i-1].text=="then" || words[i-1].text=="else" || words[i-1].text=="!"); if(atStmtStart && (w=="if" || w=="for" || w=="while" || w=="until" || w=="case" || w=="{")) ++depth; else if(atStmtStart && depth>0 && (w=="fi" || w=="done" || w=="esac" || w=="}")) --depth; Loading Loading
commands/shell/while.cpp +81 −74 Changes for commands/shell/while.cpp: 81 added lines, 74 removed lines. Original line number Diff line number Diff line Loading @@ -30,6 +30,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // condition that never becomes false. Shell-only builtin, no symlink. #include <iostream> #include <cstring> #include <cerrno> #include <fcntl.h> #include <unistd.h> Loading @@ -38,9 +40,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace fresh { namespace cmd { class WhileCommand : public ShellCommand { // "while COND; do BODY; done [REDIRECTIONS]" and its inverse "until". // Redirections after "done" apply to the whole loop ("done < file", // "done > out 2>&1"), as does a here-document. class LoopCommand : public ShellCommand { public: WhileCommand():ShellCommand("while"){} LoopCommand(const char *name,bool until):ShellCommand(name),_until(until),_name(name){} int runCommand(int argc,const char **argv){ std::string remainder=(argc>1 && argv[1]) ? argv[1] : ""; Loading @@ -48,13 +53,13 @@ namespace fresh { namespace cmd { size_t doIdx=findShellKeyword(words,0,words.size(),"do"); if(doIdx==words.size()){ std::cerr << "while: expected 'do'" << std::endl; std::cerr << _name << ": expected 'do'" << std::endl; return 2; } size_t doneIdx=findShellKeyword(words,doIdx+1,words.size(),"done"); if(doneIdx==words.size()){ std::cerr << "while: expected 'done'" << std::endl; std::cerr << _name << ": expected 'done'" << std::endl; return 2; } Loading @@ -64,24 +69,10 @@ namespace fresh { namespace cmd { Shell &shell=Shell::instance(); // "done <<WORD" -- a here-doc redirect on the loop AS A WHOLE // (e.g. the kernel's own tools/lib/subcmd/../sync-check.sh: // "while read -r f; do ...; done <<EOF\n$FILES\nEOF"), not more // statements after it (this builtin's other, "; more..." use of // `trailing`, handled below). Must be applied to fd 0 BEFORE the // loop below ever runs: the loop's own CONDITION is frequently // exactly the "read" that's supposed to consume this body (as // in the sync-check.sh example above), so redirecting only // after the loop returns is already too late -- every previous // build of this shell instead left fd 0 pointed at whatever it // already was (a real terminal or an unrelated inherited pipe), // so that first "read" blocked forever waiting for input that // was never coming, or silently consumed the wrong stream. int savedStdin=-1; int savedStdin=-1,savedStdout=-1,savedStderr=-1; std::string heredocTempFile; HeredocSpec heredocSpec; bool hasHeredoc=extractHeredocSpec(trailing,heredocSpec); if(hasHeredoc){ if(extractHeredocSpec(trailing,heredocSpec)){ heredocTempFile=shell.collectHeredocBody(heredocSpec); if(!heredocTempFile.empty()){ int fd=open(heredocTempFile.c_str(),O_RDONLY); Loading @@ -91,42 +82,33 @@ namespace fresh { namespace cmd { close(fd); } } trailing.clear(); } // the remaining trailing words are redirections for the loop std::string trailingCommand; if(!trailing.empty()){ Redirection redir; std::vector<std::string> rest=extractRedirections(tokenizeShellArgs(trailing),redir); if(!rest.empty()){ trailingCommand=trailing; // not redirections: run it after the loop, as before }else if(!applyRedirections(redir,savedStdin,savedStdout,savedStderr)){ restore(savedStdin,savedStdout,savedStderr); return 1; } } // Default for a loop that genuinely never iterates (condition // false first try) -- matching real shells. `condition` is // evaluated lazily inside the loop below, not yet here, so // (unlike if/for/case's own analogous fallback) there is // nothing to read back from shell.exitCode() yet at this // point; see the post-loop check below instead for the // "fatal error stopped the shell before any iteration" case. int status=0; const long maxiterations=1000000L; long iterations=0; int status=0; shell.enterLoop(); // false is how the loop ends, so the condition must not // trigger set -e. while(shell.running() && !shell.returning() && !shell.breaking() && !shell.continuing() && shell.parse(condition,false)==0){ if(++iterations>maxiterations){ std::cerr << "while: iteration limit exceeded" << std::endl; while(shell.running() && !shell.returning() && !shell.breaking() && !shell.continuing()){ bool condTrue=shell.parse(condition,false)==0; if(condTrue==_until) break; } if(!body.empty()) status=shell.parse(body); // A pending "return" (see Shell::returning()) must break // this loop for good, same as "exit" already does via // running() -- so it can keep unwinding up to the // callFunction() waiting to consume it, instead of the loop // re-testing its condition and running another iteration. if(!shell.running() || shell.returning()) break; // "break"/"continue" (see Shell::breaking()/continuing()): // this loop is the one that actually consumes either // signal, same reasoning as for.cpp's own handling (see // its comment for the full explanation of the // consume*Level() true/false split). if(shell.breaking()){ shell.consumeBreakLevel(); break; Loading @@ -139,45 +121,70 @@ namespace fresh { namespace cmd { } shell.exitLoop(); // Restore whatever fd 0 really was before this loop redirected // it above, and drop the temp file backing the heredoc body -- // both regardless of how the loop ended (normal exhaustion, // break, return, or a fatal error), same as any other // redirection's scope never outliving the construct it was // attached to. if(savedStdin>=0){ dup2(savedStdin,0); close(savedStdin); } std::cout.flush(); std::cerr.flush(); restore(savedStdin,savedStdout,savedStderr); if(!heredocTempFile.empty()) unlink(heredocTempFile.c_str()); // Unlike if/for/case (whose condition/subject/list is always // evaluated into a separate local BEFORE their own "status" // fallback is set up, so a fatal trigger there is already // reflected by the time that fallback runs), this loop's // condition is evaluated lazily inside the `while(...)` // controlling expression itself -- including on the very // first pass, which happens AFTER `status` above was already // initialized. Re-check here instead: if the shell is still // running, nothing overrides a legitimately-computed `status` // (whether left at its initial 0 for a loop that genuinely // never iterated, or set by the loop body's own last run); // if a "${var?word}" fired during any condition check (first // pass or later) it already stopped the shell via // requestExit(), and that real code must win here instead of // being left at whatever `status` was before that happened. if(!shell.running()) status=shell.exitCode(); if(!trailing.empty() && shell.running() && !shell.returning() && if(!trailingCommand.empty() && shell.running() && !shell.returning() && !shell.breaking() && !shell.continuing()) status=shell.parse(trailing); status=shell.parse(trailingCommand); return status; } private: bool _until; const char *_name; static bool redirect(int target,int fd,int &saved){ if(fd<0) return false; if(saved<0) saved=dup(target); dup2(fd,target); close(fd); return true; } bool applyRedirections(const Redirection &r,int &in,int &out,int &err){ if(r.hasStdin && !redirect(0,open(r.stdinFile.c_str(),O_RDONLY),in)){ std::cerr << "fresh: " << r.stdinFile << ": " << strerror(errno) << std::endl; return false; } if(r.hasStdout && !redirect(1,open(r.stdoutFile.c_str(),O_WRONLY|O_CREAT|(r.append ? O_APPEND : O_TRUNC),0644),out)){ std::cerr << "fresh: " << r.stdoutFile << ": " << strerror(errno) << std::endl; return false; } if(r.hasStderr && !redirect(2,open(r.stderrFile.c_str(),O_WRONLY|O_CREAT|(r.stderrAppend ? O_APPEND : O_TRUNC),0644),err)){ std::cerr << "fresh: " << r.stderrFile << ": " << strerror(errno) << std::endl; return false; } if(r.stderrToStdout) redirect(2,dup(1),err); if(r.stdoutToStderr) redirect(1,dup(2),out); return true; } static void restore(int in,int out,int err){ if(in>=0){ dup2(in,0); close(in); } if(out>=0){ dup2(out,1); close(out); } if(err>=0){ dup2(err,2); close(err); } } }; class WhileCommand : public LoopCommand { public: WhileCommand():LoopCommand("while",false){} }; class UntilCommand : public LoopCommand { public: UntilCommand():LoopCommand("until",true){} }; FRESH_REGISTER_COMMAND(WhileCommand); FRESH_REGISTER_COMMAND(UntilCommand); }}
src/shell.cpp +52 −8 Changes for src/shell.cpp: 52 added lines, 8 removed lines. Original line number Diff line number Diff line Loading @@ -1259,7 +1259,7 @@ size_t fresh::findShellKeyword(const std::vector<ShellWord> &words,size_t from,s i+=consumed-1; continue; } if(w=="if" || w=="for" || w=="while" || w=="case") if(w=="if" || w=="for" || w=="while" || w=="until" || w=="case") ++depth; else if(depth>0 && (w=="fi" || w=="done" || w=="esac")) --depth; Loading @@ -1285,7 +1285,7 @@ static bool isCompoundStart(const std::vector<fresh::ShellWord> &words,size_t at if(at>=words.size()) return false; const std::string &w=words[at].text; return w=="if" || w=="for" || w=="while" || w=="case" || w=="{" || w=="!"; return w=="if" || w=="for" || w=="while" || w=="until" || w=="case" || w=="{" || w=="!"; } fresh::Shell::Shell(){ Loading Loading @@ -1879,7 +1879,8 @@ std::vector<std::string> fresh::splitPipelineStages(const std::string &segment, // "echo done" 's own "done" (cat's argument, not a real loop // terminator) can never desync this count. int keywordDepth=0; static const char *kwOpeners[]={"if","elif","while","for","case"}; // "elif" is no opener: it continues an "if" that "fi" closes once static const char *kwOpeners[]={"if","while","until","for","case"}; static const char *kwClosers[]={"fi","done","esac"}; auto atKeywordStmtStart=[&](size_t pos)->bool{ size_t p=pos; Loading @@ -1889,7 +1890,15 @@ std::vector<std::string> fresh::splitPipelineStages(const std::string &segment, char c=segment[p-1]; if(c==';' || c=='\n' || c=='|') return true; if(c=='&' && p>=2 && segment[p-2]=='&') return true; return false; // right after a keyword that itself expects a command // ("do while ...", "then if ...", "! while ...") size_t wordEnd=p,wordStart=p; while(wordStart>0 && segment[wordStart-1]!=' ' && segment[wordStart-1]!='\t' && segment[wordStart-1]!=';' && segment[wordStart-1]!='\n' && segment[wordStart-1]!='|') --wordStart; std::string word=segment.substr(wordStart,wordEnd-wordStart); return word=="do" || word=="then" || word=="else" || word=="elif" || word=="if" || word=="while" || word=="until" || word=="!"; }; auto isWordBoundary=[&](size_t pos)->bool{ return pos>=segment.size() || segment[pos]==' ' || segment[pos]=='\t' || Loading Loading @@ -2921,6 +2930,22 @@ int fresh::Shell::runSimple(const std::string &segment){ stageRedirs.push_back(redir); continue; } // "... | while ...; done": a compound command stage is a body run // by parse() in the stage's child, like "{ }" above { size_t b=rawstages[i].find_first_not_of(" \t\r\n"); std::string kw; if(b!=std::string::npos){ size_t e=rawstages[i].find_first_of(" \t\r\n;",b); kw=rawstages[i].substr(b,e==std::string::npos ? std::string::npos : e-b); } if(kw=="while" || kw=="until" || kw=="for" || kw=="if" || kw=="case"){ stageSubshellBody.push_back(rawstages[i]); stageTokens.push_back(std::vector<std::string>()); stageRedirs.push_back(redir); continue; } } stageSubshellBody.push_back(""); std::vector<std::string> tokens=tokenizeShellArgs(rawstages[i]); Loading Loading @@ -3274,7 +3299,25 @@ int fresh::Shell::runSegmentWords(const std::string &input,const std::vector<She return (status==0) ? 1 : 0; } if(first=="if" || first=="for" || first=="while" || first=="case"){ // "while ...; done | cmd", "{ ...; } | cmd": a compound command as // the first stage of a pipeline goes through runSimple(), which runs // each compound stage as a body of its own if(first=="if" || first=="for" || first=="while" || first=="until" || first=="case" || first=="{"){ int d=0; for(size_t i=from;i<to;++i){ const std::string &w=words[i].text; bool stmtStart=(i==from || words[i-1].text==";" || words[i-1].text=="&&" || words[i-1].text=="||" || words[i-1].text=="|" || words[i-1].text=="do" || words[i-1].text=="then" || words[i-1].text=="else" || words[i-1].text=="if" || words[i-1].text=="elif" || words[i-1].text=="while" || words[i-1].text=="until" || words[i-1].text=="!"); if(stmtStart && (w=="if" || w=="for" || w=="while" || w=="until" || w=="case" || w=="{")) ++d; else if(stmtStart && d>0 && (w=="fi" || w=="done" || w=="esac" || w=="}")) --d; else if(d==0 && w=="|") return runSimple(shellWordsRawText(input,words,from,to)); } } if(first=="if" || first=="for" || first=="while" || first=="until" || first=="case"){ ShellCommand *cmd=find(first); if(!cmd){ std::cerr << first << ": not supported" << std::endl; Loading Loading @@ -3814,9 +3857,10 @@ int fresh::Shell::parse(const std::string &input,bool applyErrexit){ words[i-1].text=="&&" || words[i-1].text=="||" || words[i-1].text=="|" || words[i-1].text=="if" || words[i-1].text=="elif" || words[i-1].text=="while" || (i==segstart+1 && words[segstart].text=="!")); if(atStmtStart && (w=="if" || w=="for" || w=="while" || w=="case" || w=="{")) words[i-1].text=="while" || words[i-1].text=="until" || words[i-1].text=="do" || words[i-1].text=="then" || words[i-1].text=="else" || words[i-1].text=="!"); if(atStmtStart && (w=="if" || w=="for" || w=="while" || w=="until" || w=="case" || w=="{")) ++depth; else if(atStmtStart && depth>0 && (w=="fi" || w=="done" || w=="esac" || w=="}")) --depth; Loading