Last active
May 6, 2020 05:24
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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