From 46dfd1884aee0faffa3d5f95f56bc3deab4823d9 Mon Sep 17 00:00:00 2001 From: GillesDuvert Date: Sat, 30 Nov 2024 19:24:13 +0100 Subject: [PATCH 1/2] solved #1906 --- src/widget.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/widget.cpp b/src/widget.cpp index 94325fed9..336bba135 100644 --- a/src/widget.cpp +++ b/src/widget.cpp @@ -2511,9 +2511,7 @@ BaseGDL* widget_info( EnvT* e ) { id = (*static_cast (ev->GetTag(idIx, 0)))[0]; // get its id for (SizeT i = 0; i < widgetIDList.size(); i++) { //is ID corresponding to any widget in list? if (widgetIDList.at(i) == id) { //if yes - //IMPORTANT: return ev immediately. This is what permits #1685: an event trapped by WIDGET_EVENT does not behave - //like the same event processed in the evenloop. - return ev; + goto endwait; } } GDLWidget::widgetEventQueue.PushBack(ev); From 49597420660df75443a2a9d2d520fa9aadaf8c6c Mon Sep 17 00:00:00 2001 From: GillesDuvert Date: Tue, 3 Dec 2024 18:01:06 +0100 Subject: [PATCH 2/2] file_search to expand shell variables... without breaking the other options. example: given that my $EDITOR is emacs, I have the files .emacs .emacs-places .emacs.d/ .emacs~ in $HOME. So ff=file_search('${HOME}/.${EDITOR}*',count=nf,/mark,/match_initial_dot) gives /home/gildas/.emacs /home/gildas/.emacs-places /home/gildas/.emacs.d/ /home/gildas/.emacs~ --- src/file.cpp | 482 ++++++++++++++++++++++++--------------------------- src/str.cpp | 35 ++++ src/str.hpp | 1 + 3 files changed, 261 insertions(+), 257 deletions(-) diff --git a/src/file.cpp b/src/file.cpp index 2a45cb376..189ef4c95 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -1081,10 +1081,11 @@ static void FileSearch( FileListT& fileList, const DString& pathSpec, if ( period ) // n/a on OS X globflags |= GLOB_PERIOD; #endif - if( fold_case) - st=makeInsensitive(pathSpec); - else - st=pathSpec; + st=pathSpec; + // expand ONLY shell variables (crudely, also, since ExpandShellVariables is not fully shell compliant) + // this because glob() must be used (with all the above useful modifiers) and glob() does not explan shell variables + if ( environment ) ExpandShellVariables(st); + if(fold_case) st=makeInsensitive(st); glob_t p; int gRes; if ( !forceAbsPath ) { @@ -1421,278 +1422,245 @@ static void PathSearch( FileListT& fileList, const DString& pathSpec, } return res; } -/* - Result = FILE_SEARCH(Path_Specification) (Standard) - or for recursive searching, - Result = FILE_SEARCH(Dir_Specification, Recur_Pattern) - Standard: When called with a single Path_Specification argument, FILE_SEARCH returns - all files that match that specification. This is the same operation, sometimes - referred to as file globbing, performed by most operating system command interpreters - when wildcard characters are used in file specifications. - Recursive: When called with two arguments, FILE_SEARCH performs recursive searching - of directory hierarchies. In a recursive search, FILE_SEARCH looks recursively for - any and all subdirectories in the file hierarchy rooted at the Dir_Specification argument. - Within each of these subdirectories, it returns the names of all files that match the - pattern in the Recur_Pattern argument. - This operation is similar to that performed by the UNIX find(1) command. - NOTE: in order to avoid infinite reference loops, - IDL policy states that symlinks are not followed. - symlnk references should therefore be returned as found, without resolution, and - can be processed by the FILE_READLINK function. - NOTE: Contrary to above documented intention, acutal IDL behavior is that - when called with two arguments the Dir_specification argument could itself be a pattern-search; - hence the dance below where, for Nparam > 1, first duty is to search on the 1st parameter for directories. -// modifications : 2014, 2015 by Greg Jung + + /* + Result = FILE_SEARCH(Path_Specification) (Standard) + or for recursive searching, + Result = FILE_SEARCH(Dir_Specification, Recur_Pattern) + Standard: When called with a single Path_Specification argument, FILE_SEARCH returns + all files that match that specification. This is the same operation, sometimes + referred to as file globbing, performed by most operating system command interpreters + when wildcard characters are used in file specifications. + Recursive: When called with two arguments, FILE_SEARCH performs recursive searching + of directory hierarchies. In a recursive search, FILE_SEARCH looks recursively for + any and all subdirectories in the file hierarchy rooted at the Dir_Specification argument. + Within each of these subdirectories, it returns the names of all files that match the + pattern in the Recur_Pattern argument. + This operation is similar to that performed by the UNIX find(1) command. + NOTE: in order to avoid infinite reference loops, + IDL policy states that symlinks are not followed. + symlnk references should therefore be returned as found, without resolution, and + can be processed by the FILE_READLINK function. + NOTE: Contrary to above documented intention, acutal IDL behavior is that + when called with two arguments the Dir_specification argument could itself be a pattern-search; + hence the dance below where, for Nparam > 1, first duty is to search on the 1st parameter for directories. + // modifications : 2014, 2015 by Greg Jung */ BaseGDL* file_search(EnvT* e) { - enum { - testregular = 3, testdir, testzero, testsymlink - }; - - bool tests[NTEST_SEARCH]; - for (SizeT i = 0; i < NTEST_SEARCH; i++) tests[i] = false; - // keywords - bool tilde = true; - bool fold_case = false; - bool environment = true; - bool noexpand_path = false; - bool accErr = false; - bool mark = false; - bool noSort = false; - bool quote = false; - bool match_dot = false; - bool match_all_dot = false; - bool forceAbsPath = false; - - // common with FINDFILE - static int countIx = e->KeywordIx("COUNT"); - bool countKW = e->KeywordPresent(countIx); - bool isFindFile=(e->GetProName() == "FINDFILE"); - - if (!isFindFile) { - static int TEST_READIx = e->KeywordIx("TEST_READ"); - static int TEST_WRITEIx = e->KeywordIx("TEST_WRITE"); - static int TEST_EXECUTABLEIx = e->KeywordIx("TEST_EXECUTABLE"); - static int TEST_REGULARIx = e->KeywordIx("TEST_REGULAR"); - static int TEST_DIRECTORYIx = e->KeywordIx("TEST_DIRECTORY"); - static int TEST_ZERO_LENGTHIx = e->KeywordIx("TEST_ZERO_LENGTH"); - static int TEST_SYMLINKIx = e->KeywordIx("TEST_SYMLINK"); - static int REGULARIx = e->KeywordIx("REGULAR"); - static int DIRECTORYIx = e->KeywordIx("DIRECTORY"); - static int ZERO_LENGTHIx = e->KeywordIx("ZERO_LENGTH"); - static int SYMLINKIx = e->KeywordIx("SYMLINK"); - const int test_kwIx[] = { - TEST_READIx, TEST_WRITEIx, TEST_EXECUTABLEIx, - TEST_REGULARIx, TEST_DIRECTORYIx, TEST_ZERO_LENGTHIx, - TEST_SYMLINKIx - }; - - for (SizeT i = 0; i < NTEST_SEARCH; i++) { - if (e->KeywordPresent(test_kwIx[i])) tests[i] = e->KeywordSet(test_kwIx[i]); - else tests[i] = false; - } - - // extra options for convenience: - if (e->KeywordSet(DIRECTORYIx)) tests[testdir] = true; - if (e->KeywordSet(SYMLINKIx)) tests[testsymlink] = true; - if (e->KeywordSet(REGULARIx)) tests[testregular] = true; - if (e->KeywordSet(ZERO_LENGTHIx)) tests[testzero] = true; - - // next three have default behaviour - static int tildeIx = e->KeywordIx("EXPAND_TILDE"); - bool tildeKW = e->KeywordPresent(tildeIx); - if (tildeKW) tilde = e->KeywordSet(tildeIx); - - static int environmentIx = e->KeywordIx("EXPAND_ENVIRONMENT"); - bool environmentKW = e->KeywordPresent(environmentIx); - if (environmentKW) { - bool Set = e->KeywordSet(environmentIx); - if (Set) { - environment = true;} - else environment = false; - } - - bool noexpand_path = !environment; - - static int fold_caseIx = e->KeywordIx("FOLD_CASE"); - bool fold_caseKW = e->KeywordPresent(fold_caseIx); - if (fold_caseKW) fold_case = e->KeywordSet(fold_caseIx); - - static int accerrIx = e->KeywordIx("ISSUE_ACCESS_ERROR"); - accErr = e->KeywordSet( accerrIx); - - static int markIx = e->KeywordIx("MARK_DIRECTORY"); - mark = e->KeywordSet( markIx); - - static int nosortIx = e->KeywordIx("NOSORT"); - noSort = e->KeywordSet( nosortIx); - - static int quoteIx = e->KeywordIx("QUOTE"); - quote = e->KeywordSet( quoteIx); - - static int match_dotIx = e->KeywordIx("MATCH_INITIAL_DOT"); - match_dot = e->KeywordSet( match_dotIx); - - static int match_all_dotIx = e->KeywordIx("MATCH_ALL_INITIAL_DOT"); - match_all_dot = e->KeywordSet( match_all_dotIx); - - static int fully_qualified_pathIx = e->KeywordIx("FULLY_QUALIFY_PATH"); - forceAbsPath = e->KeywordSet( fully_qualified_pathIx); + enum { + testregular = 3, testdir, testzero, testsymlink + }; + + bool tests[NTEST_SEARCH]; + for (SizeT i = 0; i < NTEST_SEARCH; i++) tests[i] = false; + // keywords + bool tilde = true; + bool fold_case = false; + bool environment = true; + bool noexpand_path = false; + bool accErr = false; + bool mark = false; + bool noSort = false; + bool quote = false; + bool match_dot = false; + bool match_all_dot = false; + bool forceAbsPath = false; + + // common with FINDFILE + static int countIx = e->KeywordIx("COUNT"); + bool countKW = e->KeywordPresent(countIx); + + static int TEST_READIx = e->KeywordIx("TEST_READ"); + static int TEST_WRITEIx = e->KeywordIx("TEST_WRITE"); + static int TEST_EXECUTABLEIx = e->KeywordIx("TEST_EXECUTABLE"); + static int TEST_REGULARIx = e->KeywordIx("TEST_REGULAR"); + static int TEST_DIRECTORYIx = e->KeywordIx("TEST_DIRECTORY"); + static int TEST_ZERO_LENGTHIx = e->KeywordIx("TEST_ZERO_LENGTH"); + static int TEST_SYMLINKIx = e->KeywordIx("TEST_SYMLINK"); + static int REGULARIx = e->KeywordIx("REGULAR"); + static int DIRECTORYIx = e->KeywordIx("DIRECTORY"); + static int ZERO_LENGTHIx = e->KeywordIx("ZERO_LENGTH"); + static int SYMLINKIx = e->KeywordIx("SYMLINK"); + const int test_kwIx[] = { + TEST_READIx, TEST_WRITEIx, TEST_EXECUTABLEIx, + TEST_REGULARIx, TEST_DIRECTORYIx, TEST_ZERO_LENGTHIx, + TEST_SYMLINKIx + }; + + for (SizeT i = 0; i < NTEST_SEARCH; i++) { + if (e->KeywordPresent(test_kwIx[i])) tests[i] = e->KeywordSet(test_kwIx[i]); + else tests[i] = false; + } - if (match_all_dot) - Warning("FILE_SEARCH: MATCH_ALL_INITIAL_DOT keyword ignored (not supported)."); + // extra options for convenience: + if (e->KeywordSet(DIRECTORYIx)) tests[testdir] = true; + if (e->KeywordSet(SYMLINKIx)) tests[testsymlink] = true; + if (e->KeywordSet(REGULARIx)) tests[testregular] = true; + if (e->KeywordSet(ZERO_LENGTHIx)) tests[testzero] = true; + + // next three have default behaviour + static int tildeIx = e->KeywordIx("EXPAND_TILDE"); + bool tildeKW = e->KeywordPresent(tildeIx); + if (tildeKW) tilde = e->KeywordSet(tildeIx); + + static int environmentIx = e->KeywordIx("EXPAND_ENVIRONMENT"); + bool environmentKW = e->KeywordPresent(environmentIx); + if (environmentKW) { + bool Set = e->KeywordSet(environmentIx); + if (Set) { + environment = true; + } else environment = false; + } - } else { -#ifndef _WIN32 - //Under Windows, FINDFILE appends a "\" character to the end of the returned file name if the file is a directory. - mark=true; -#endif - } - // SYNTAX: - // Result = FILE_SEARCH(Path_Specification) - // or for recursive searching, - // Result = FILE_SEARCH(Dir_Specification, Recur_Pattern) - SizeT nParam = e->NParam(); // 0 -> "*" - - DStringGDL* pathSpec; - SizeT nPath = 0; - bool recursive_dirsearch = true; - bool leading_nullst = true; - DString Pattern = ""; - if (nParam > 0) { - BaseGDL* p0 = e->GetParDefined(0); - pathSpec = dynamic_cast (p0); - if (pathSpec == NULL) - e->Throw("String expression required in this context."); - - nPath = pathSpec->N_Elements(); - leading_nullst = ((*pathSpec)[0] == ""); - if (leading_nullst) Pattern = "*"; - // Path_Specification A scalar or array variable of string type, containing file paths to match. - // If Path_Specification is not supplied, or if it is supplied as an empty string, - // FILE_SEARCH uses a default pattern of '*', which matches all files in the current directory - if (nParam > 1) { - e->AssureScalarPar< DStringGDL>(1, Pattern); - // Dir_Specification A scalar or array variable of string type, containing directory paths - // within which FILE_SEARCH will perform recursive searching for files matching the - // Recur_Pattern argument. FILE_SEARCH examines Dir_Specification, and any directory found below it, - // and returns the paths of any files in those directories that match Recur_Pattern. - // 'If Dir_Specification is supplied as an empty string, FILE_SEARCH searches the current directory.' - if ((nPath == 1) && leading_nullst) recursive_dirsearch = false; - } - } + noexpand_path = !environment; + + static int fold_caseIx = e->KeywordIx("FOLD_CASE"); + bool fold_caseKW = e->KeywordPresent(fold_caseIx); + if (fold_caseKW) fold_case = e->KeywordSet(fold_caseIx); + + + static int accerrIx = e->KeywordIx("ISSUE_ACCESS_ERROR"); + accErr = e->KeywordSet(accerrIx); + + static int markIx = e->KeywordIx("MARK_DIRECTORY"); + mark = e->KeywordSet(markIx); + + static int nosortIx = e->KeywordIx("NOSORT"); + noSort = e->KeywordSet(nosortIx); + + static int quoteIx = e->KeywordIx("QUOTE"); + quote = e->KeywordSet(quoteIx); + + static int match_dotIx = e->KeywordIx("MATCH_INITIAL_DOT"); + match_dot = e->KeywordSet(match_dotIx); + + static int match_all_dotIx = e->KeywordIx("MATCH_ALL_INITIAL_DOT"); + match_all_dot = e->KeywordSet(match_all_dotIx); + + static int fully_qualified_pathIx = e->KeywordIx("FULLY_QUALIFY_PATH"); + forceAbsPath = e->KeywordSet(fully_qualified_pathIx); + + if (match_all_dot) + Warning("FILE_SEARCH: MATCH_ALL_INITIAL_DOT keyword ignored (not supported)."); + + + // SYNTAX: + // Result = FILE_SEARCH(Path_Specification) + // or for recursive searching, + // Result = FILE_SEARCH(Dir_Specification, Recur_Pattern) + SizeT nParam = e->NParam(); // 0 -> "*" + + DStringGDL* pathSpec; + SizeT nPath = 0; + bool recursive_dirsearch = true; + bool leading_nullst = true; + DString Pattern = ""; + if (nParam > 0) { + BaseGDL* p0 = e->GetParDefined(0); + pathSpec = dynamic_cast (p0); + if (pathSpec == NULL) + e->Throw("String expression required in this context."); + + nPath = pathSpec->N_Elements(); + leading_nullst = ((*pathSpec)[0] == ""); + if (leading_nullst) Pattern = "*"; + // Path_Specification A scalar or array variable of string type, containing file paths to match. + // If Path_Specification is not supplied, or if it is supplied as an empty string, + // FILE_SEARCH uses a default pattern of '*', which matches all files in the current directory + if (nParam > 1) { + e->AssureScalarPar< DStringGDL>(1, Pattern); + // Dir_Specification A scalar or array variable of string type, containing directory paths + // within which FILE_SEARCH will perform recursive searching for files matching the + // Recur_Pattern argument. FILE_SEARCH examines Dir_Specification, and any directory found below it, + // and returns the paths of any files in those directories that match Recur_Pattern. + // 'If Dir_Specification is supplied as an empty string, FILE_SEARCH searches the current directory.' + if ((nPath == 1) && leading_nullst) recursive_dirsearch = false; + } + } - bool onlyDir = nParam > 1; + bool onlyDir = nParam > 1; - FileListT fileList; + FileListT fileList; - DLong count; + DLong count; #ifndef _WIN32 - // The alternative can be used in Linux, also. - // replace above with #if 0 to unify methods. - // Differences? please notify me (GVJ) - //#if 0 - if (nPath == 0) - FileSearch(fileList, "", - environment, tilde, - accErr, mark, noSort, quote, - match_dot, forceAbsPath, fold_case, - onlyDir, tests); - else - if (!recursive_dirsearch) fileList.push_back(string("./")); - else // it appears glob is incapable of returning a symlink.! - for (SizeT f = 0; f < nPath; ++f) - FileSearch(fileList, (*pathSpec)[f], - environment, tilde, - accErr, mark, noSort, quote, - match_dot, forceAbsPath, fold_case, - onlyDir, tests); + // The alternative can be used in Linux, also. + // replace above with #if 0 to unify methods. + // Differences? please notify me (GVJ) + //#if 0 + if (nPath == 0) + FileSearch(fileList, "", + environment, tilde, + accErr, mark, noSort, quote, + match_dot, forceAbsPath, fold_case, + onlyDir, tests); + else + if (!recursive_dirsearch) fileList.push_back(string("./")); + else // it appears glob is incapable of returning a symlink.! + for (SizeT f = 0; f < nPath; ++f) + FileSearch(fileList, (*pathSpec)[f], + environment, tilde, + accErr, mark, noSort, quote, + match_dot, forceAbsPath, fold_case, + onlyDir, tests); #else - // if(trace_me) std::cout << "file_search: nPath=" << nPath <<" nParam=" - // << nParam << std::endl; - if (nPath == 0 or (leading_nullst and nParam == 1)) - // PathSearch( fileList, "./*", true, false, - PatternSearch(fileList, "./", "*", false, - accErr, mark, quote, match_dot, forceAbsPath, fold_case, - onlyDir, tests); - else if (!recursive_dirsearch) fileList.push_back(string("./")); - else - for (SizeT f = 0; f < nPath; ++f) { - PathSearch(fileList, (*pathSpec)[f], true, false, - // PatternSearch( fileList, "", (*pathSpec)[f], false, - accErr, mark, quote, match_dot, forceAbsPath, fold_case, - onlyDir, tests); - } + // if(trace_me) std::cout << "file_search: nPath=" << nPath <<" nParam=" + // << nParam << std::endl; + if (nPath == 0 or (leading_nullst and nParam == 1)) + // PathSearch( fileList, "./*", true, false, + PatternSearch(fileList, "./", "*", false, + accErr, mark, quote, match_dot, forceAbsPath, fold_case, + onlyDir, tests); + else if (!recursive_dirsearch) fileList.push_back(string("./")); + else + for (SizeT f = 0; f < nPath; ++f) { + PathSearch(fileList, (*pathSpec)[f], true, false, + // PatternSearch( fileList, "", (*pathSpec)[f], false, + accErr, mark, quote, match_dot, forceAbsPath, fold_case, + onlyDir, tests); + } #endif - onlyDir = false; - count = fileList.size(); + onlyDir = false; + count = fileList.size(); + + + FileListT fileOut; + for (SizeT f = 0; f < count; ++f) { + if (nParam > 1) + PatternSearch(fileOut, fileList[f], Pattern, recursive_dirsearch, + accErr, mark, quote, + match_dot, forceAbsPath, fold_case, + onlyDir, tests); + else + fileOut.push_back(fileList[f]); + } + DLong pCount = fileOut.size(); - FileListT fileOut; - for (SizeT f = 0; f < count; ++f) { - if (nParam > 1) - PatternSearch(fileOut, fileList[f], Pattern, recursive_dirsearch, - accErr, mark, quote, - match_dot, forceAbsPath, fold_case, - onlyDir, tests); - else - fileOut.push_back(fileList[f]); - } + if (countKW) + e->SetKW(countIx, new DLongGDL(pCount)); - DLong pCount = fileOut.size(); - - //special trick for findfile returning a single directory: list contents - if (isFindFile && pCount == 1) { - struct stat64 statStruct; - bool isaDir, isaSymLink; - int actStat = filestat(fileList[0].c_str(), statStruct, isaDir, isaSymLink); - if (actStat == 0 && isaDir) { - DIR* dir = opendir(fileList[0].c_str()); - if (dir != NULL) { - pCount = 0; - struct dirent* entry; - fileOut.clear(); - while ((entry = readdir(dir)) != NULL) { - //avoid copying twice in a string, first in entryStr, then in fileOut //small speedup? -// DString entryStr( entry->d_name); -// if( entryStr == "." || entryStr == "..") continue; -// pCount++; -// fileOut.push_back(entryStr); - char* name=entry->d_name; //note d_name is supposedly 256 chars max, but this is not really true.. - size_t len=strlen(name); - if ((len==1 && (strncmp(name,".",1)==0)) || (len==2 && (strncmp(name,"..",2)==0) )) continue; - pCount++; - fileOut.push_back(name); - } - } - closedir(dir); - } - } - - if (countKW) - e->SetKW(countIx, new DLongGDL(pCount)); - - if (pCount == 0) { - return new DStringGDL(""); - } -//bad idea: -// if (nParam > 2) { // provision for a third parameter = filecount return. -// e->AssureGlobalPar(2); -// e->SetPar(2, new DLongGDL(pCount)); -// } // use this only for interactive sessions: not an IDL feature. + if (pCount == 0) { + return new DStringGDL(""); + } + //bad idea: + // if (nParam > 2) { // provision for a third parameter = filecount return. + // e->AssureGlobalPar(2); + // e->SetPar(2, new DLongGDL(pCount)); + // } // use this only for interactive sessions: not an IDL feature. - if (!noSort) - sort(fileOut.begin(), fileOut.end()); + if (!noSort) + sort(fileOut.begin(), fileOut.end()); - // fileOut -> res - DStringGDL* res = new DStringGDL(dimension(pCount), BaseGDL::NOZERO); - for (SizeT r = 0; r < pCount; ++r) - (*res)[r] = fileOut[ r]; + // fileOut -> res + DStringGDL* res = new DStringGDL(dimension(pCount), BaseGDL::NOZERO); + for (SizeT r = 0; r < pCount; ++r) + (*res)[r] = fileOut[ r]; - return res; + return res; } BaseGDL* file_basename( EnvT* e) diff --git a/src/str.cpp b/src/str.cpp index 77cfcd415..26e56c72b 100644 --- a/src/str.cpp +++ b/src/str.cpp @@ -376,6 +376,41 @@ void WordExp(std::string& s) #ifndef _WIN32 +// this is probably not a polished full expansion of environment variables under unix. +void ExpandShellVariables(std::string& s) +{ + if (s.length() == 0) return; + bool trace_me = false; //lib::trace_arg(); + + std::string sEsc = ""; + int i = 0; + for (; i < s.length(); ++i) { + char achar = s[i]; + if (achar != '$') sEsc.push_back(achar); + else { // $ + std::string name = ""; + bool inside=false; + for (int ind = i + 1; i < s.length(); ++ind, ++i) { + char tchar = s[ind]; + if (tchar == '{' ) {inside= true; continue;} //ignore { + if (inside) { + if (isalnum(tchar) || tchar=='_' ) name.push_back(tchar); + else if (tchar == '}' ) {inside= false; continue;} + else throw GDLException("Bad environment variable substitution syntax."); + } else { + if (isalnum(tchar) || tchar=='_' ) name.push_back(tchar); + else break; + } + } + char* subst = getenv(name.c_str()); + if (subst != NULL) { + sEsc += std::string(subst); + } else sEsc.push_back(achar); + } + } + s=std::string(sEsc); +} + void WordExp(std::string& s) { //AC 2018-04-25 : because crash of : // openr, unit, '', ERROR=error,/get_lun diff --git a/src/str.hpp b/src/str.hpp index 04236ff77..72b027f38 100644 --- a/src/str.hpp +++ b/src/str.hpp @@ -50,6 +50,7 @@ inline void AppendIfNeeded(std::string &s, const std::string& a) if( s.substr(s.size()-a.size(),a.size()) != a) s += a; } +void ExpandShellVariables(std::string& s); // wrapper for wordexp void WordExp( std::string& s);