Skip to content

Instantly share code, notes, and snippets.

@awwsmm
Last active May 6, 2020 05:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save awwsmm/7d947dc26688227225a6942467e6e1e5 to your computer and use it in GitHub Desktop.
Save awwsmm/7d947dc26688227225a6942467e6e1e5 to your computer and use it in GitHub Desktop.
Expand paths with globs '*', recursive double-globs '**', leading ~ or ~user home directories, or $ENV %VARS% (even on Windows!) to lists of files
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class ExpandPath {
/// SURROUND ALL COMMAND-LINE ARGUMENTS WITH "DOUBLE QUOTES"
public static void main (String[] args) {
for (String arg : args) {
System.out.printf("%narg: '%s'%n%n", arg);
expandPath(arg).stream().forEach(e -> System.out.println(" " + e));
}
// List<String> examples = Arrays.asList(
// "~/test/*.txt", "~/test/**.txt", "~/test/*/*.txt", "~/test/*/d/*.txt",
// "~/test/**a.txt", "~/test/**{a,b}.txt", "%HOME%/test/*.txt", "%HOME%/test/**.txt",
// "~/test/\\*.txt" );
// for (String example : examples) {
// System.out.printf("%narg: '%s'%n%n", example);
// expandPath(example).stream().forEach(e -> System.out.println(" " + e));
// }
} // */ //
///---------------------------------------------------------------------------
///
/// expandPath():
/// expands paths with globs (Windows or Linux), $ENVIRONMENT_VARIABLES
/// (Linux), %ENVIRONMENT_VARIABLES% (Windows), and leading ~ or ~user
/// directories (Windows or Linux).
//
// Note the expanded ability to use globs and the '~' shortcut in cmd.exe
// on Windows. (Usually only available in Powershell.)
//
// * When arguments are passed throught the CL, the shell will auto-
// expand any $ENV_VARS, even within double-quotes. The user should
// check that they're using valid environment variables; otherwise,
// they might be expanded to "".
//
// RECOMMENDATIONS:
// > put all command-line path arguments in quotes
// > check that $ENV_VARS used are valid
// > don't put literal asterisks in file names!
// > PUT ALL COMMAND-LINE PATH ARGUMENTS IN QUOTES
//
///---------------------------------------------------------------------------
// non-current user home directories here
protected static Map<String, String> userHomeDirs = new HashMap<String, String>(5);
// system environment variables here
protected static final Map<String, String> ENV = System.getenv();
// OS name
protected static final String OSNAME = System.getProperty("os.name");
// actual method
public static Set<String> expandPath (String path) {
// if path is null, abort
if (path == null) return new HashSet<String>(0);
// assuming argument was quoted, could have leading / trailing whitespace
String pathTrimmed = (OSNAME.contains("indows")
? path.trim().replaceAll("\\\\", "/") : path.trim());
int pathTrimmedLen = pathTrimmed.length();
char initChar = pathTrimmed.charAt(0);
// is path is null, empty, or begins with a '-' (is actually a flag), abort
if (pathTrimmedLen < 1 || initChar == '-') return new HashSet<String>(0);
// otherwise, create empty list for files
HashSet<String> fileList = new HashSet<String>(10);
//------------------------------------------------------
//
// ENVIRONMENT VARIABLES AND SPECIAL CHARACTERS
//
// LINUX:
// Linux has the special characters ~ and $, which denote user home
// directories and environment variables. They are expanded as follows
// when passed as command-line arguments to Java:
//
// $ ./app $HOME -> /home/user \_ any number of valid env vars
// $ ./app "$HOME" -> /home/user / can be expanded in this way
// $ ./app ~ -> /home/user
// $ ./app "~" -> ~
// $ ./app ~other -> /home/other
// $ ./app "~other" -> ~other
//
// Directories / file names with spaces in them need to have the spaces
// escaped "\ ", or have the entire path surrounded with quotes. The
// surrounding quotes are stripped before the path is handed to Java, so
// the user might want an initial '~' to be expanded, but the path has a
// space in it somewhere, so they entered:
//
// $ ./app "~/a b/c" -> ~/a b/c
//
// ...as mentioned previously, environment variables will be expanded
// within or without quotes, but '~' won't be expanded within quotes.
// Note that only the initial '~' should be expanded:
//
// $ ./app ~/a/~ -> /home/user/a/~
// $ ./app "~/a/~" -> ~/a/~
//
// If an env var doesn't exist on Linux, an empty string is returned:
//
// $ ./app $HOME -> /home/user
// $ ./app $HOOME ->
//
// WINDOWS:
// Windows only has environment variables surrounded by % signs, and no
// '~' shortcut for the user's home directory (%HOME%). Windows expands
// environment variables whether they're inside or outside or quotes, but
// if an environment variable is not valid, it leaves it as-is:
//
// C:|> ./app %HOME% -> C:|Users|user
// C:|> ./app %HOOME% -> %HOOME%
// * | should be \ here
// ("Illegal unicode escape")
//
// Globs have some caveats in Windows (explained below).
//
// !! NOTE THAT ALL OF THE ABOVE ONLY APPLIES TO COMMAND-LINE ARGUMENTS.
// !! If arguments are passed as Strings through a Java environment, none
// !! of the above auto-expansions take place. This method assumes that
// !! the path passed to it hasn't been auto-expanded.
//______________________________________________________
//
// METHOD:
//
// LINUX: expand initial ~ or ~user, if it exists within the path
// expand any $VARS if they exist, if not, continue
// automatically expand any globs, if present (PathMatcher)
//
// WINDOWS: expand any %VARS% if they exist, if not, throw an error
// manually expand any globs, if present
//
//------------------------------------------------------
String pathCleaned = null;
if (initChar == '~') { // expand home directory
// note: this could be the user's home directory or another user's
// home directory. If the next character is not '/', we need to
// open a shell and run an "ls" (Linux only)
// works on Linux only
if (!OSNAME.contains("indows")) {
if (pathTrimmedLen > 1 && pathTrimmed.charAt(1) != '/') {
// find next slash
int userHomeEnd = pathTrimmed.indexOf('/', 2);
// if no next slash, entire path is ~anotherUsersHomeDir
if (userHomeEnd < 0) userHomeEnd = pathTrimmedLen;
String userHome = pathTrimmed.substring(0, userHomeEnd);
String userHomeExpanded = null;
// check that we haven't already found this user's home directory
if (!userHomeDirs.containsKey(userHome)) {
// define command to list the given directory without showing its contents
String command = "ls -d " + userHome;
try {
// open a bash shell, execute the command, and return the result
Process shellExec = Runtime.getRuntime().exec(new String[]{"bash", "-c", command});
BufferedReader reader = new BufferedReader(new InputStreamReader(shellExec.getInputStream()));
userHomeExpanded = reader.readLine() + "/";
} catch (IOException ex) {
System.err.println("expandPath() : IOException");
return null;
}
// add this user to the map to save time in the future
userHomeDirs.put(userHome, userHomeExpanded);
// if we have seen this user before...
} else { userHomeExpanded = userHomeDirs.get(userHome); }
// update the path
pathCleaned = userHomeExpanded + ((userHomeEnd < (pathTrimmedLen)) ?
pathTrimmed.substring(userHomeEnd+1) : "");
} // if "~user" in path on Windows... we can't do anything
// if just ~ or ~/, it's this user's home directory... way easier:
} else { // WORKS ON LINUX OR WINDOWS!
if (!userHomeDirs.containsKey("~")) // change Windows backslash to forward slash
userHomeDirs.put("~", System.getenv("HOME").replaceAll("\\\\", "/"));
pathCleaned = pathTrimmed.replaceFirst("^~", userHomeDirs.get("~"));
}
// if initial character is not '~', "clean" path is the same as "trimmed" path, for now
} else { pathCleaned = pathTrimmed; }
//------------------------------------------------------
//
// ENVIRONMENT VARIABLES
//
// LINUX:
// POSIX environment variables can only contain characters [a-zA-Z0-9_]+
// and must begin with [a-zA-Z_]. So to find the bounds of an environment
// variable in Linux, we have to find the first invalid character.
// '$' aren't restricted to definining environment variables in Linux:
// they can be used in file names. So if the given "environment variable
// doesn't exist, just skip it. Assume the user knows what they're doing.
//
// WINDOWS:
// Windows environment variables are crazy.
// see: https://ss64.com/nt/syntax-variables.html, http://bit.ly/2ODYi4K
// They can contain tons of POSIX-unallowed characters, including both
// slashes. So, assume anything within %...% is the env var name. Like
// Linux, '%' is a valid character in Windows file names. So if we don't
// find the environment variable, assume it's part of the name.
//
// CONCLUSION:
// '%' and '$' are valid parts of both Windows and Linux filenames. So we
// first need to determine what OS we're running on, then only expand the
// appropriate environment variables for that system. If any environment
// variable doesn't exist, leave it %AS% $IS within the path.
//
//------------------------------------------------------
int pathCleanedLen = pathCleaned.length();
int currCharIdx = 0;
Pattern nonWordPattern = Pattern.compile("[^a-zA-Z0-9_]");
if (OSNAME.contains("indows")) { // running on a Windows OS
// find next pair of %% (surrounding an env var)
int nextPctSignIdx = pathCleaned.indexOf('%', currCharIdx);
int nextnextPctSignIdx = pathCleaned.indexOf('%', nextPctSignIdx+1);
// loop until we run out of '%'s
while (nextnextPctSignIdx > -1) {
String varKey = pathCleaned.substring(nextPctSignIdx+1, nextnextPctSignIdx);
String varVal = ENV.get(varKey);
if (varVal != null) { // add expanded env var to path, if it exists
pathCleaned = pathCleaned.substring(0, nextPctSignIdx) + varVal +
((nextnextPctSignIdx < pathCleanedLen) ?
pathCleaned.substring(nextnextPctSignIdx + 1) : "");
pathCleanedLen = pathCleaned.length();
currCharIdx = 0;
} else { // if it doesn't exist, move on with your life
currCharIdx = nextnextPctSignIdx + 1;
}
// find the next '%', if it exists
nextPctSignIdx = pathCleaned.indexOf('%', currCharIdx);
nextnextPctSignIdx = pathCleaned.indexOf('%', nextPctSignIdx+1);
}
} else { // otherwise, Linux
// find the next '$' (beginning an env var)
int nextDollaSignIdx = pathCleaned.indexOf('$', currCharIdx);
// loop until we run out of '$'s
while (nextDollaSignIdx > -1) {
// if the character after the '$' is a number, it can't be an env var
char nextChar = pathCleaned.charAt(nextDollaSignIdx+1);
if (nextChar >= '0' && nextChar <= '9') {
currCharIdx = nextDollaSignIdx + 2;
} else { // we could have an env var here...
// find next non-word character using regex
String RHS = pathCleaned.substring(nextDollaSignIdx+1);
Matcher matcher = nonWordPattern.matcher(RHS);
// if we don't find anything, the entire path is an env var
int envVarEnd = (matcher.find()) ? (matcher.start() + nextDollaSignIdx+1) : pathCleaned.length();
String varKey = pathCleaned.substring(currCharIdx+1, envVarEnd);
String varVal = ENV.get(varKey);
if (varVal != null) { // add expanded env var to path, if it exists
pathCleaned = pathCleaned.substring(0, nextDollaSignIdx) + varVal +
pathCleaned.substring(envVarEnd);
pathCleanedLen = pathCleaned.length();
currCharIdx = 0;
} else { // if it doesn't exist, move on with your life
currCharIdx = envVarEnd + 1;
}
}
// find the next '$', if it exists
nextDollaSignIdx = pathCleaned.indexOf('$', currCharIdx);
currCharIdx = nextDollaSignIdx;
}
} // end Linux loop
// if there are no globs '*' in the path, we're done!
int nextGlobIdx = pathCleaned.indexOf('*');
if (nextGlobIdx < 0) {
try {
fileList.add((new File(pathCleaned)).getCanonicalPath());
return fileList;
} catch (IOException ex) {
System.err.println("expandPath() : IOException");
return null;
}
} else { // otherwise...
//------------------------------------------------------
//
// GLOBS
//
// LINUX:
// Next, we have to expand any globs '*' in the path. On Linux, '*'
// characters are allowed in file names, but in Windows they aren't.
//
// (COMMAND LINE: $ java -jar package.jar a*b)
// $ ls . -> a*b acb adb (first file has a literal '*' in name)
// $ ./app a*b -> a*b acb adb (glob expanded to list of files)
// $ ./app "a*b" -> a*b (user intended to use as glob?)
// $ ./app a\*b -> a*b (literal '*' escaped)
// $ ./app "a\*b" -> a\*b (literal '*' escaped)
// $ ./app **/a -> [correctly expands to list of files, if they exist]
// $ ./app "**/a" -> **/a (globs unexpanded)
//
// If a glob appears in the output, it could be because the user *wants*
// there to be an asterisk there, or it could be because the argument was
// in quotes and so the globs are unexpanded. Since there's no way to tell
// which was the case, leave that up to the user.
//
// If we recommend that the user send all command line paths in within
// quotes, we can expand globs here and add extra functionality for the
// "double glob" (a/*_*/a.txt matches a/a/b/a.txt, a/a.txt, a/c/a.txt, etc.)
// But if the filename has a literal '*' in it, we leave that file
// unaccessible unless the user escapes it ("\*"). Which is more important:
// the ability to reach files with '*' in the name? Or the added function-
// ality of being able to easily reach files recursively, at any level of
// the directory tree? Maybe we should go for the latter.
//
// WINDOWS:
// Globs can only be used in the very last element of a path in Windows:
//
// C:|> ./app *\a.txt -> *\a.txt (doesn't expand to correct file list)
// C:|> ./app *\*.txt -> *\*.txt
// C:|> ./app a\*.txt -> [correctly expands to list of matching files]
// C:|> ./app *.xqj -> *.xqj (if no files match)
//
// So, if we're on a Windows system and we get an argument with exactly one
// glob in it, it could be that the glob is not in the last element of the
// path (in which case, we want to expand it); OR it could be that the glob
// *is* in the last element of the path, but no files match it.
//
// So, we check if the system is Windows, and fail fast if the glob appears
// after the final backslash in the path. If it appears before the final
// backslash, we have to manually expand the path and check all of the
// possibilities ourselves, for each asterisk that appears.
//
//------------------------------------------------------
// loop through the path from the beginning and stop at each '*' character
// if '*' is preceeded by a forward slash, skip it
// if '*' is followed by another '*', we look at ALL future filetree branches
// if '*' is followed by a '/', expand file list to all matches
currCharIdx = 0;
while (nextGlobIdx > -1) {
// path/inter\*rupted -- continue, literal '*' character
if ((nextGlobIdx > 0) && (pathCleaned.charAt(nextGlobIdx-1) == '\\')) {
nextGlobIdx = pathCleaned.indexOf('*', nextGlobIdx+1);
currCharIdx = nextGlobIdx;
continue;
// use a SimpleFileVisitor to get everything else
} else {
// the entire path (globs and all) goes in "glob"
// everything before the first glob goes in "start"
String glob = "glob:" + pathCleaned;
String start = pathCleaned.substring(0, nextGlobIdx);
// create pathMatcher and walk the file tree to find matching paths
PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher(glob);
try {
Files.walkFileTree(Paths.get(start), new SimpleFileVisitor<Path>() {
@Override
// if a file matched...
public FileVisitResult visitFile(Path path,
BasicFileAttributes attrs) throws IOException {
// add canonical path to file to list of files
if (pathMatcher.matches(path))
fileList.add((new File(path.toString())).getCanonicalPath());
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException ex)
throws IOException {
return FileVisitResult.CONTINUE;
}
});
} catch (IOException ex) {
System.err.println("expandPath() : IOException");
return null;
}
break;
} // end else (globs "*" and "**", not "\*")
} // end while (nextGlobIdx > -1)
} // end else (globs present in path)
return fileList;
} // end expandPath()
} // end class ExpandPath
//========================================================================================================
//
// USAGE AND EXAMPLES
//
//========================================================================================================
//
// For a directory that looks like this:
//
// $ tree ~/test
// /Users/aww/test
// ├── *.txt
// ├── 0.txt
// ├── 1.txt
// ├── a
// │   ├── a.txt
// │   └── d
// │   ├── a.txt
// │   └── d.txt
// ├── a.txt
// ├── b
// │   ├── a.txt
// │   ├── b.txt
// │   └── d
// │   ├── a.txt
// │   ├── e.txt
// │   └── f.txt
// └── c
// └── c.txt
//
// ...running ExpandPath via the command line results in:
// $ java ExpandPath <arg>
//
//
// <------------ arguments passed via command line ------------------><---- arguments passed as ---->
// (Running bash 3.2.57(1)-release on macOS Terminal.)
// (Only bash 4+ have support for "double globbing" "**".) <---- function parameters (as ---->
//
// <-------- with quotes ---------><--------- without quotes --------><---- Strings within code) ---->
//
//====== EX 01 ===========================================================================================
//
// < "~/test/*.txt" > < ~/test/*.txt > "~/test/*.txt"
// (with quotes) (without quotes) (passed as String to function)
//
// arg: '~/test/*.txt' arg: '/Users/aww/test/*.txt' arg: '~/test/*.txt'
//
// /Users/aww/test/0.txt /Users/aww/test/0.txt /Users/aww/test/0.txt
// /Users/aww/test/1.txt /Users/aww/test/1.txt /Users/aww/test/1.txt
// /Users/aww/test/*.txt /Users/aww/test/*.txt /Users/aww/test/*.txt
// /Users/aww/test/a.txt /Users/aww/test/a.txt /Users/aww/test/a.txt
//
// arg: '/Users/aww/test/0.txt'
//
// /Users/aww/test/0.txt
//
// arg: '/Users/aww/test/1.txt'
//
// /Users/aww/test/1.txt
//
// arg: '/Users/aww/test/a.txt'
//
// /Users/aww/test/a.txt
//
//====== EX 02 ===========================================================================================
//
// < "~/test/**.txt" > < ~/test/**.txt > "~/test/**.txt"
//
// arg: '~/test/**.txt' arg: '/Users/aww/test/*.txt' arg: '~/test/**.txt'
//
// /Users/aww/test/a/d/d.txt /Users/aww/test/0.txt /Users/aww/test/a/d/d.txt
// /Users/aww/test/b/d/f.txt /Users/aww/test/1.txt /Users/aww/test/b/d/f.txt
// /Users/aww/test/1.txt /Users/aww/test/*.txt /Users/aww/test/1.txt
// /Users/aww/test/*.txt /Users/aww/test/a.txt /Users/aww/test/*.txt
// /Users/aww/test/b/b.txt /Users/aww/test/b/b.txt
// /Users/aww/test/a.txt arg: '/Users/aww/test/0.txt' /Users/aww/test/a.txt
// /Users/aww/test/c/c.txt /Users/aww/test/c/c.txt
// /Users/aww/test/b/d/e.txt /Users/aww/test/0.txt /Users/aww/test/b/d/e.txt
// /Users/aww/test/b/a.txt /Users/aww/test/b/a.txt
// /Users/aww/test/0.txt arg: '/Users/aww/test/1.txt' /Users/aww/test/0.txt
// /Users/aww/test/a/d/a.txt /Users/aww/test/a/d/a.txt
// /Users/aww/test/b/d/a.txt /Users/aww/test/1.txt /Users/aww/test/b/d/a.txt
// /Users/aww/test/a/a.txt /Users/aww/test/a/a.txt
// arg: '/Users/aww/test/a.txt'
//
// /Users/aww/test/a.txt
//
//====== EX 03 ===========================================================================================
//
// < "~/test/*/*.txt" > < ~/test/*/*.txt > "~/test/*/*.txt"
//
// arg: '~/test/*/*.txt' arg: '/Users/aww/test/a/a.txt' arg: '~/test/*/*.txt'
//
// /Users/aww/test/b/a.txt /Users/aww/test/a/a.txt /Users/aww/test/b/a.txt
// /Users/aww/test/b/b.txt /Users/aww/test/b/b.txt
// /Users/aww/test/c/c.txt arg: '/Users/aww/test/b/a.txt' /Users/aww/test/c/c.txt
// /Users/aww/test/a/a.txt /Users/aww/test/a/a.txt
// /Users/aww/test/b/a.txt
//
// arg: '/Users/aww/test/b/b.txt'
//
// /Users/aww/test/b/b.txt
//
// arg: '/Users/aww/test/c/c.txt'
//
// /Users/aww/test/c/c.txt
//
//====== EX 04 ===========================================================================================
//
// < "~/test/*/d/*.txt" > < ~/test/*/d/*.txt > "~/test/*/d/*.txt"
//
// arg: '~/test/*/d/*.txt' arg: '/Users/aww/test/a/d/a.txt' arg: '~/test/*/d/*.txt'
//
// /Users/aww/test/a/d/d.txt /Users/aww/test/a/d/a.txt /Users/aww/test/a/d/d.txt
// /Users/aww/test/b/d/e.txt /Users/aww/test/b/d/e.txt
// /Users/aww/test/b/d/f.txt arg: '/Users/aww/test/a/d/d.txt' /Users/aww/test/b/d/f.txt
// /Users/aww/test/a/d/a.txt /Users/aww/test/a/d/a.txt
// /Users/aww/test/b/d/a.txt /Users/aww/test/a/d/d.txt /Users/aww/test/b/d/a.txt
//
// arg: '/Users/aww/test/b/d/a.txt'
//
// /Users/aww/test/b/d/a.txt
//
// arg: '/Users/aww/test/b/d/e.txt'
//
// /Users/aww/test/b/d/e.txt
//
// arg: '/Users/aww/test/b/d/f.txt'
//
// /Users/aww/test/b/d/f.txt
//
//====== EX 05 ===========================================================================================
//
// < "~/test/**a.txt" > < ~/test/**a.txt > "~/test/**a.txt"
//
// arg: '~/test/**a.txt' arg: '/Users/aww/test/a.txt' arg: '~/test/**a.txt'
//
// /Users/aww/test/b/a.txt /Users/aww/test/a.txt /Users/aww/test/b/a.txt
// /Users/aww/test/a/d/a.txt /Users/aww/test/a/d/a.txt
// /Users/aww/test/a.txt /Users/aww/test/a.txt
// /Users/aww/test/b/d/a.txt /Users/aww/test/b/d/a.txt
// /Users/aww/test/a/a.txt /Users/aww/test/a/a.txt
//
//====== EX 06 ===========================================================================================
//
// < "~/test/**{a,b}.txt" > < ~/test/**{a,b}.txt > "~/test/**{a,b}.txt"
//
// arg: '~/test/**{a,b}.txt' arg: '/Users/aww/test/a.txt' arg: '~/test/**{a,b}.txt'
//
// /Users/aww/test/b/a.txt /Users/aww/test/a.txt /Users/aww/test/b/a.txt
// /Users/aww/test/a/d/a.txt /Users/aww/test/a/d/a.txt
// /Users/aww/test/b/b.txt arg: '/Users/aww/test/**b.txt' /Users/aww/test/b/b.txt
// /Users/aww/test/a.txt /Users/aww/test/a.txt
// /Users/aww/test/b/d/a.txt /Users/aww/test/b/b.txt /Users/aww/test/b/d/a.txt
// /Users/aww/test/a/a.txt /Users/aww/test/a/a.txt
//
//====== EX 07 ===========================================================================================
//
// < "$HOME/test/*.txt" > < $HOME/test/*.txt > "$HOME/test/*.txt"
//
// arg: '/Users/aww/test/*.txt' arg: '/Users/aww/test/*.txt' arg: '$HOME/test/*.txt'
//
// /Users/aww/test/0.txt /Users/aww/test/0.txt /Users/aww/test/0.txt
// /Users/aww/test/1.txt /Users/aww/test/1.txt /Users/aww/test/1.txt
// /Users/aww/test/*.txt /Users/aww/test/*.txt /Users/aww/test/*.txt
// /Users/aww/test/a.txt /Users/aww/test/a.txt /Users/aww/test/a.txt
//
// arg: '/Users/aww/test/0.txt'
//
// /Users/aww/test/0.txt
//
// arg: '/Users/aww/test/1.txt'
//
// /Users/aww/test/1.txt
//
// arg: '/Users/aww/test/a.txt'
//
// /Users/aww/test/a.txt
//
//====== EX 08 ===========================================================================================
//
// < "$HOME/test/**.txt" > < $HOME/test/**.txt > "$HOME/test/**.txt"
//
// arg: '/Users/aww/test/**.txt' arg: '/Users/aww/test/*.txt' arg: '$HOME/test/**.txt'
//
// /Users/aww/test/a/d/d.txt /Users/aww/test/0.txt /Users/aww/test/a/d/d.txt
// /Users/aww/test/b/d/f.txt /Users/aww/test/1.txt /Users/aww/test/b/d/f.txt
// /Users/aww/test/1.txt /Users/aww/test/*.txt /Users/aww/test/1.txt
// /Users/aww/test/*.txt /Users/aww/test/a.txt /Users/aww/test/*.txt
// /Users/aww/test/b/b.txt /Users/aww/test/b/b.txt
// /Users/aww/test/a.txt arg: '/Users/aww/test/0.txt' /Users/aww/test/a.txt
// /Users/aww/test/c/c.txt /Users/aww/test/c/c.txt
// /Users/aww/test/b/d/e.txt /Users/aww/test/0.txt /Users/aww/test/b/d/e.txt
// /Users/aww/test/b/a.txt /Users/aww/test/b/a.txt
// /Users/aww/test/0.txt arg: '/Users/aww/test/1.txt' /Users/aww/test/0.txt
// /Users/aww/test/a/d/a.txt /Users/aww/test/a/d/a.txt
// /Users/aww/test/b/d/a.txt /Users/aww/test/1.txt /Users/aww/test/b/d/a.txt
// /Users/aww/test/a/a.txt /Users/aww/test/a/a.txt
// arg: '/Users/aww/test/a.txt'
//
// /Users/aww/test/a.txt
//
//====== EX 09 ===========================================================================================
//
// The added ability to expand double-globs means that files with literal '*' in their names
// cannot be referenced by themselves (only in groups with other files):
//
// < "~/test/\*.txt" > < ~/test/\*.txt > "~/test/\*.txt"
//
// arg: '~/test/\*.txt' arg: '/Users/aww/test/*.txt' arg: '~/test/\*.txt'
//
// /Users/aww/test/0.txt
// /Users/aww/test/1.txt
// /Users/aww/test/*.txt
// /Users/aww/test/a.txt
//
//====== WINDOWS =========================================================================================
//
// We get the *SAME RESULTS* as above on Windows machines, excluding the "*.txt" results (asterisks are
// not allowed to be a part of filenames in Windows), and making sure to change the $HOME variable to
// %HOME%. Note that Windows users can use '\' OR '/' separators in their paths, because internally, any
// '\' character (from Windows only) is converted to a '/'. The escape character in Windows is '^' (not
// '\', like in Linux) so this doesn't affect escape sequences.
//
// In conclusion, this method can expand any path with a valid leading '~' or '~user' variable, any number
// of globs '*' or double-globs '**' (on Windows or UNIX), and any $ENVIRONMENT %VARIABLES% (on UNIX and
// Windows, respectively).
//
//========================================================================================================
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment