Loading commands/filesystem/find.cpp +571 −123 File changed.Preview size limit exceeded, changes collapsed. Show changes commands/text/xargs.cpp +163 −68 Changes for commands/text/xargs.cpp: 163 added lines, 68 removed lines. Original line number Diff line number Diff line Loading @@ -138,59 +138,134 @@ namespace fresh { namespace cmd { } } // splits input at a single delimiter character (-0, -d) static std::vector<std::string> splitAt(const std::string &text,char delim){ if(delim=='\0') return splitNul(text); std::vector<std::string> tokens; size_t start=0; for(size_t i=0;i<=text.size();++i){ if(i==text.size() || text[i]==delim){ if(i>start || (i<text.size() && i>start)) tokens.push_back(text.substr(start,i-start)); start=i+1; } } return tokens; } // "-d '\n'" & co.: C escapes or a single character static bool parseDelim(const std::string &d,char &out){ if(d.size()==1){ out=d[0]; return true; } if(d.size()==2 && d[0]=='\\'){ switch(d[1]){ case 'n': out='\n'; return true; case 't': out='\t'; return true; case '0': out='\0'; return true; case '\\': out='\\'; return true; default: return false; } } if(d.size()>2 && d[0]=='\\' && (d[1]=='x' || d[1]=='X')){ out=(char)strtol(d.c_str()+2,nullptr,16); return true; } if(d.size()>1 && d[0]=='\\'){ out=(char)strtol(d.c_str()+1,nullptr,8); return true; } return false; } // one invocation; GNU xargs' status rules: 255 stops everything // (124), 1-125 make the overall status 123, 126/127 are passed on static bool invoke(const std::vector<std::string> &tokens,bool trace,int &overall){ if(trace){ for(size_t k=0;k<tokens.size();++k) std::cerr << (k ? " " : "") << tokens[k]; std::cerr << std::endl; } int st=Shell::instance().runCommandTokens(tokens); if(st==255){ overall=124; return false; } if(st==126 || st==127){ overall=st; return false; } if(st!=0 && overall==0) overall=123; return true; } int runCommand(int argc,const char **argv){ bool noRunIfEmpty=false,nulSeparated=false; long maxArgs=-1; std::string replaceStr; bool noRunIfEmpty=false,trace=false,haveDelim=false; char delim='\n'; long maxArgs=-1,maxLines=-1; std::string replaceStr,eofStr; std::string inputFile; std::vector<std::string> baseArgv; // option letters that take a value, attached ("-n32") or // separate ("-n 32"); others may be combined ("-0r") auto needsValue=[](char c){ return strchr("adEeIiLlnPs",c)!=nullptr; }; auto setOpt=[&](char c,const std::string &v)->bool{ switch(c){ case '0': haveDelim=true; delim='\0'; return true; case 'r': noRunIfEmpty=true; return true; case 't': trace=true; return true; case 'x': case 'p': return true; // limits/prompting: not needed here case 'd': if(!parseDelim(v,delim)){ std::cerr << "xargs: invalid delimiter '" << v << "'" << std::endl; return false; } haveDelim=true; return true; case 'I': case 'i': replaceStr=v.empty() ? "{}" : v; return true; case 'n': maxArgs=atol(v.c_str()); return true; case 'L': case 'l': maxLines=v.empty() ? 1 : atol(v.c_str()); return true; case 'a': inputFile=v; return true; case 'E': case 'e': eofStr=v; return true; case 'P': case 's': return true; // parallelism/size: run sequentially default: std::cerr << "xargs: invalid option -- '" << c << "'" << std::endl; return false; } }; int i=1; for(; i<argc; ++i){ std::string a=argv[i]; if(a=="-r" || a=="--no-run-if-empty"){ noRunIfEmpty=true; }else if(a=="-0" || a=="--null"){ nulSeparated=true; }else if(a=="-I"){ if(i+1>=argc){ std::cerr << "xargs: -I: option requires an argument" << std::endl; return 2; } replaceStr=argv[++i]; }else if(a.compare(0,2,"-I")==0 && a.size()>2){ replaceStr=a.substr(2); }else if(a=="-n"){ if(i+1>=argc){ std::cerr << "xargs: -n: option requires an argument" << std::endl; return 2; } maxArgs=atol(argv[++i]); }else if(a.compare(0,2,"-n")==0 && a.size()>2){ maxArgs=atol(a.c_str()+2); }else if(a.compare(0,11,"--max-args=")==0){ maxArgs=atol(a.c_str()+11); }else if(a=="-a"){ if(i+1>=argc){ std::cerr << "xargs: -a: option requires an argument" << std::endl; return 2; } inputFile=argv[++i]; }else if(a.compare(0,2,"-a")==0 && a.size()>2){ inputFile=a.substr(2); }else if(a=="--"){ ++i; break; }else if(!a.empty() && a[0]=='-' && a.size()>1){ // Unrecognized option: real xargs would error; this // build never passes one this applet doesn't already // understand, so treat it (conservatively) as already // being the command name rather than silently eating // an argument the caller meant for COMMAND. break; if(a=="--"){ ++i; break; } if(a.compare(0,2,"--")==0){ std::string name=a.substr(2),val; size_t eq=name.find('='); if(eq!=std::string::npos){ val=name.substr(eq+1); name=name.substr(0,eq); } if(name=="null"){ haveDelim=true; delim='\0'; } else if(name=="no-run-if-empty") noRunIfEmpty=true; else if(name=="verbose") trace=true; else if(name=="exit" || name=="interactive") {} else if(name=="max-args" || name=="max-lines" || name=="max-procs" || name=="max-chars" || name=="delimiter" || name=="replace" || name=="arg-file" || name=="eof"){ if(eq==std::string::npos && name!="replace" && name!="eof"){ if(i+1>=argc){ std::cerr << "xargs: --" << name << " requires an argument" << std::endl; return 1; } val=argv[++i]; } static const char *const longs[]={"max-args","max-lines","max-procs","max-chars","delimiter","replace","arg-file","eof"}; static const char shorts[]="nLPsdIaE"; for(int k=0;k<8;++k) if(name==longs[k] && !setOpt(shorts[k],val)) return 1; }else{ std::cerr << "xargs: unrecognized option '" << a << "'" << std::endl; return 1; } continue; } if(a.size()<2 || a[0]!='-') break; for(size_t k=1;k<a.size();++k){ char c=a[k]; if(needsValue(c)){ std::string v=a.substr(k+1); // -I/-i/-L/-l/-e/-E may stand alone; the rest need a value if(v.empty() && !strchr("iIlLeE",c)){ if(i+1>=argc){ std::cerr << "xargs: option requires an argument -- '" << c << "'" << std::endl; return 1; } v=argv[++i]; }else if(v.empty() && c=='I'){ if(i+1>=argc){ std::cerr << "xargs: option requires an argument -- 'I'" << std::endl; return 1; } v=argv[++i]; } if(!setOpt(c,v)) return 1; break; } if(!setOpt(c,"")) return 1; } } for(; i<argc; ++i) Loading @@ -203,7 +278,7 @@ namespace fresh { namespace cmd { std::ifstream f(inputFile.c_str()); if(!f.is_open()){ std::cerr << "xargs: " << inputFile << ": " << strerror(errno) << std::endl; return 2; return 1; } std::ostringstream ss; ss << f.rdbuf(); Loading @@ -214,43 +289,63 @@ namespace fresh { namespace cmd { input=ss.str(); } int overall=0; // -I: one invocation per input line (or delimited item) if(!replaceStr.empty()){ // "-I replstr": one invocation per LINE of input (never // further word-split), substituting every occurrence of // replstr in the base argv with that line's own text -- // matches real xargs' own "-I" semantics. std::istringstream lines(input); std::string line; std::vector<std::string> items=splitAt(input,haveDelim ? delim : '\n'); bool any=false; int status=0; while(std::getline(lines,line)){ if(line.empty()) continue; for(auto &item: items){ if(item.empty()) continue; if(!eofStr.empty() && item==eofStr) break; any=true; std::vector<std::string> tokens=baseArgv; for(size_t k=0;k<tokens.size();++k) replaceAll(tokens[k],replaceStr,line); status=Shell::instance().runCommandTokens(tokens); replaceAll(tokens[k],replaceStr,item); if(!invoke(tokens,trace,overall)) return overall; } if(!any && !noRunIfEmpty) return Shell::instance().runCommandTokens(baseArgv); return status; (void)any; return overall; } std::vector<std::string> items=nulSeparated ? splitNul(input) : tokenize(input); if(items.empty()) return noRunIfEmpty ? 0 : Shell::instance().runCommandTokens(baseArgv); std::vector<std::vector<std::string>> batches; if(maxLines>0 && !haveDelim){ // -L N: N non-blank input lines per invocation std::istringstream lines(input); std::string line; std::vector<std::string> cur; long n=0; while(std::getline(lines,line)){ std::vector<std::string> t=tokenize(line); if(t.empty()) continue; cur.insert(cur.end(),t.begin(),t.end()); if(++n==maxLines){ batches.push_back(cur); cur.clear(); n=0; } } if(!cur.empty()) batches.push_back(cur); }else{ std::vector<std::string> items=haveDelim ? splitAt(input,delim) : tokenize(input); if(!eofStr.empty()){ auto it=std::find(items.begin(),items.end(),eofStr); items.erase(it,items.end()); } size_t batchLimit=(maxArgs>0) ? (size_t)maxArgs : MAX_BATCH_ARGS; int status=0; for(size_t start=0; start<items.size(); start+=batchLimit){ size_t end=std::min(start+batchLimit,items.size()); batches.emplace_back(items.begin()+start,items.begin()+end); } } if(batches.empty()){ if(noRunIfEmpty) return 0; invoke(baseArgv,trace,overall); return overall; } for(auto &b: batches){ std::vector<std::string> tokens=baseArgv; for(size_t k=start;k<end;++k) tokens.push_back(items[k]); status=Shell::instance().runCommandTokens(tokens); tokens.insert(tokens.end(),b.begin(),b.end()); if(!invoke(tokens,trace,overall)) return overall; } return status; return overall; } }; Loading Loading
commands/filesystem/find.cpp +571 −123 File changed.Preview size limit exceeded, changes collapsed. Show changes
commands/text/xargs.cpp +163 −68 Changes for commands/text/xargs.cpp: 163 added lines, 68 removed lines. Original line number Diff line number Diff line Loading @@ -138,59 +138,134 @@ namespace fresh { namespace cmd { } } // splits input at a single delimiter character (-0, -d) static std::vector<std::string> splitAt(const std::string &text,char delim){ if(delim=='\0') return splitNul(text); std::vector<std::string> tokens; size_t start=0; for(size_t i=0;i<=text.size();++i){ if(i==text.size() || text[i]==delim){ if(i>start || (i<text.size() && i>start)) tokens.push_back(text.substr(start,i-start)); start=i+1; } } return tokens; } // "-d '\n'" & co.: C escapes or a single character static bool parseDelim(const std::string &d,char &out){ if(d.size()==1){ out=d[0]; return true; } if(d.size()==2 && d[0]=='\\'){ switch(d[1]){ case 'n': out='\n'; return true; case 't': out='\t'; return true; case '0': out='\0'; return true; case '\\': out='\\'; return true; default: return false; } } if(d.size()>2 && d[0]=='\\' && (d[1]=='x' || d[1]=='X')){ out=(char)strtol(d.c_str()+2,nullptr,16); return true; } if(d.size()>1 && d[0]=='\\'){ out=(char)strtol(d.c_str()+1,nullptr,8); return true; } return false; } // one invocation; GNU xargs' status rules: 255 stops everything // (124), 1-125 make the overall status 123, 126/127 are passed on static bool invoke(const std::vector<std::string> &tokens,bool trace,int &overall){ if(trace){ for(size_t k=0;k<tokens.size();++k) std::cerr << (k ? " " : "") << tokens[k]; std::cerr << std::endl; } int st=Shell::instance().runCommandTokens(tokens); if(st==255){ overall=124; return false; } if(st==126 || st==127){ overall=st; return false; } if(st!=0 && overall==0) overall=123; return true; } int runCommand(int argc,const char **argv){ bool noRunIfEmpty=false,nulSeparated=false; long maxArgs=-1; std::string replaceStr; bool noRunIfEmpty=false,trace=false,haveDelim=false; char delim='\n'; long maxArgs=-1,maxLines=-1; std::string replaceStr,eofStr; std::string inputFile; std::vector<std::string> baseArgv; // option letters that take a value, attached ("-n32") or // separate ("-n 32"); others may be combined ("-0r") auto needsValue=[](char c){ return strchr("adEeIiLlnPs",c)!=nullptr; }; auto setOpt=[&](char c,const std::string &v)->bool{ switch(c){ case '0': haveDelim=true; delim='\0'; return true; case 'r': noRunIfEmpty=true; return true; case 't': trace=true; return true; case 'x': case 'p': return true; // limits/prompting: not needed here case 'd': if(!parseDelim(v,delim)){ std::cerr << "xargs: invalid delimiter '" << v << "'" << std::endl; return false; } haveDelim=true; return true; case 'I': case 'i': replaceStr=v.empty() ? "{}" : v; return true; case 'n': maxArgs=atol(v.c_str()); return true; case 'L': case 'l': maxLines=v.empty() ? 1 : atol(v.c_str()); return true; case 'a': inputFile=v; return true; case 'E': case 'e': eofStr=v; return true; case 'P': case 's': return true; // parallelism/size: run sequentially default: std::cerr << "xargs: invalid option -- '" << c << "'" << std::endl; return false; } }; int i=1; for(; i<argc; ++i){ std::string a=argv[i]; if(a=="-r" || a=="--no-run-if-empty"){ noRunIfEmpty=true; }else if(a=="-0" || a=="--null"){ nulSeparated=true; }else if(a=="-I"){ if(i+1>=argc){ std::cerr << "xargs: -I: option requires an argument" << std::endl; return 2; } replaceStr=argv[++i]; }else if(a.compare(0,2,"-I")==0 && a.size()>2){ replaceStr=a.substr(2); }else if(a=="-n"){ if(i+1>=argc){ std::cerr << "xargs: -n: option requires an argument" << std::endl; return 2; } maxArgs=atol(argv[++i]); }else if(a.compare(0,2,"-n")==0 && a.size()>2){ maxArgs=atol(a.c_str()+2); }else if(a.compare(0,11,"--max-args=")==0){ maxArgs=atol(a.c_str()+11); }else if(a=="-a"){ if(i+1>=argc){ std::cerr << "xargs: -a: option requires an argument" << std::endl; return 2; } inputFile=argv[++i]; }else if(a.compare(0,2,"-a")==0 && a.size()>2){ inputFile=a.substr(2); }else if(a=="--"){ ++i; break; }else if(!a.empty() && a[0]=='-' && a.size()>1){ // Unrecognized option: real xargs would error; this // build never passes one this applet doesn't already // understand, so treat it (conservatively) as already // being the command name rather than silently eating // an argument the caller meant for COMMAND. break; if(a=="--"){ ++i; break; } if(a.compare(0,2,"--")==0){ std::string name=a.substr(2),val; size_t eq=name.find('='); if(eq!=std::string::npos){ val=name.substr(eq+1); name=name.substr(0,eq); } if(name=="null"){ haveDelim=true; delim='\0'; } else if(name=="no-run-if-empty") noRunIfEmpty=true; else if(name=="verbose") trace=true; else if(name=="exit" || name=="interactive") {} else if(name=="max-args" || name=="max-lines" || name=="max-procs" || name=="max-chars" || name=="delimiter" || name=="replace" || name=="arg-file" || name=="eof"){ if(eq==std::string::npos && name!="replace" && name!="eof"){ if(i+1>=argc){ std::cerr << "xargs: --" << name << " requires an argument" << std::endl; return 1; } val=argv[++i]; } static const char *const longs[]={"max-args","max-lines","max-procs","max-chars","delimiter","replace","arg-file","eof"}; static const char shorts[]="nLPsdIaE"; for(int k=0;k<8;++k) if(name==longs[k] && !setOpt(shorts[k],val)) return 1; }else{ std::cerr << "xargs: unrecognized option '" << a << "'" << std::endl; return 1; } continue; } if(a.size()<2 || a[0]!='-') break; for(size_t k=1;k<a.size();++k){ char c=a[k]; if(needsValue(c)){ std::string v=a.substr(k+1); // -I/-i/-L/-l/-e/-E may stand alone; the rest need a value if(v.empty() && !strchr("iIlLeE",c)){ if(i+1>=argc){ std::cerr << "xargs: option requires an argument -- '" << c << "'" << std::endl; return 1; } v=argv[++i]; }else if(v.empty() && c=='I'){ if(i+1>=argc){ std::cerr << "xargs: option requires an argument -- 'I'" << std::endl; return 1; } v=argv[++i]; } if(!setOpt(c,v)) return 1; break; } if(!setOpt(c,"")) return 1; } } for(; i<argc; ++i) Loading @@ -203,7 +278,7 @@ namespace fresh { namespace cmd { std::ifstream f(inputFile.c_str()); if(!f.is_open()){ std::cerr << "xargs: " << inputFile << ": " << strerror(errno) << std::endl; return 2; return 1; } std::ostringstream ss; ss << f.rdbuf(); Loading @@ -214,43 +289,63 @@ namespace fresh { namespace cmd { input=ss.str(); } int overall=0; // -I: one invocation per input line (or delimited item) if(!replaceStr.empty()){ // "-I replstr": one invocation per LINE of input (never // further word-split), substituting every occurrence of // replstr in the base argv with that line's own text -- // matches real xargs' own "-I" semantics. std::istringstream lines(input); std::string line; std::vector<std::string> items=splitAt(input,haveDelim ? delim : '\n'); bool any=false; int status=0; while(std::getline(lines,line)){ if(line.empty()) continue; for(auto &item: items){ if(item.empty()) continue; if(!eofStr.empty() && item==eofStr) break; any=true; std::vector<std::string> tokens=baseArgv; for(size_t k=0;k<tokens.size();++k) replaceAll(tokens[k],replaceStr,line); status=Shell::instance().runCommandTokens(tokens); replaceAll(tokens[k],replaceStr,item); if(!invoke(tokens,trace,overall)) return overall; } if(!any && !noRunIfEmpty) return Shell::instance().runCommandTokens(baseArgv); return status; (void)any; return overall; } std::vector<std::string> items=nulSeparated ? splitNul(input) : tokenize(input); if(items.empty()) return noRunIfEmpty ? 0 : Shell::instance().runCommandTokens(baseArgv); std::vector<std::vector<std::string>> batches; if(maxLines>0 && !haveDelim){ // -L N: N non-blank input lines per invocation std::istringstream lines(input); std::string line; std::vector<std::string> cur; long n=0; while(std::getline(lines,line)){ std::vector<std::string> t=tokenize(line); if(t.empty()) continue; cur.insert(cur.end(),t.begin(),t.end()); if(++n==maxLines){ batches.push_back(cur); cur.clear(); n=0; } } if(!cur.empty()) batches.push_back(cur); }else{ std::vector<std::string> items=haveDelim ? splitAt(input,delim) : tokenize(input); if(!eofStr.empty()){ auto it=std::find(items.begin(),items.end(),eofStr); items.erase(it,items.end()); } size_t batchLimit=(maxArgs>0) ? (size_t)maxArgs : MAX_BATCH_ARGS; int status=0; for(size_t start=0; start<items.size(); start+=batchLimit){ size_t end=std::min(start+batchLimit,items.size()); batches.emplace_back(items.begin()+start,items.begin()+end); } } if(batches.empty()){ if(noRunIfEmpty) return 0; invoke(baseArgv,trace,overall); return overall; } for(auto &b: batches){ std::vector<std::string> tokens=baseArgv; for(size_t k=start;k<end;++k) tokens.push_back(items[k]); status=Shell::instance().runCommandTokens(tokens); tokens.insert(tokens.end(),b.begin(),b.end()); if(!invoke(tokens,trace,overall)) return overall; } return status; return overall; } }; Loading