目錄對比搜索工具:
我遇到需要對比兩個目錄結構很相似的目錄的結構差異,也可以對比多個目錄,所以寫了這個工具,可能有人也會有這樣的需要,所以發出來。需求是這樣的:如下表示a和b兩個目錄,我需要工具告訴我b目錄的d2目錄下有個不符合*f*.txt的文件0000.txt,並且告訴我b目錄多了個d3的目錄。
├───a
│ │ f0.txt
│ │ f1.txt
│ │
│ └───d1
│ ├───d1
│ │ abc_f1.txt
│ │ def_f2.txt
│ │
│ └───d2
│ 22_f1.txt
│ f2.txt
│
└───b
│ f2.txt
│ f3.txt
│
└───d1
├───d1
│ ccc_f1.txt
│ sss_f1.txt
│
├───d2
│ 0000.txt
│ 123_f1.txt
│
└───d3
我寫這個工具類輸入如下參數就可以告訴我有個不符合規律的文件0000.txt,b比a目錄多了個d3的目錄
參數:
--paths
D:\tmp\data\a,D:\tmp\data\b
--replace
*f*.txt,/a/,/b/,/d*/
--outfile
compare_result.txt
執行結果:
-----------------1-------------------
D:\tmp\data\a
count: 6
-----------------1-------------------
-----------------2-------------------
D:\tmp\data\b
count: 6
Only in current path (count: 1):
D:\tmp\d*\*\d*\d*\0000.txt (count: 1):
D:\tmp\data\b\d1\d2\0000.txt
D:\tmp\d*\*\d*\d3 (count: 1):
D:\tmp\data\b\d1\d3
-----------------2-------------------
搜索功能:
比如我需要搜索文件名以f1.txt結尾的文件:
參數:
--paths
D:\tmp\data\a,D:\tmp\data\b
--search
*f1.txt
--outfile
search_result.txt
不寫--outfile就直接打印到屏幕上
執行結果:
Found 6 items in all directory:
D:\tmp\data\a
Found 3 items in current directory:
D:\tmp\data\a\f1.txt
D:\tmp\data\a\d1\d1\abc_f1.txt
D:\tmp\data\a\d1\d2\22_f1.txt
D:\tmp\data\b
Found 3 items in current directory:
D:\tmp\data\b\d1\d1\ccc_f1.txt
D:\tmp\data\b\d1\d1\sss_f1.txt
D:\tmp\data\b\d1\d2\123_f1.txt
最後貼上代碼:
DirectoryCompareAndSearchTool.java
import java.io.File;
import java.io.PrintStream;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class DirectoryCompareAndSearchTool {
private final static String HELP_TEXT = "Options: --paths <path>[,<path>].. [--replace <str>[,<str>..] [--exclude <patten>[,<patten>..]] [--search <str>[,<str>..] [--outfile <path>]";
public static void main(String[] args) {
registerExceptionHandler(Thread.currentThread(), args);
InputArgs inputArgs = parseArgs(args);
List<String> result = doWork(inputArgs);
handleResult(result, inputArgs);
}
private static void registerExceptionHandler(Thread thread, String[] args) {
thread.setUncaughtExceptionHandler((t, e) -> {
if (e.getClass() != IllegalArgumentException.class) {
for (StackTraceElement element : e.getStackTrace()) {
if (DirectoryCompareAndSearchTool.class.getName().equals(element.getClassName())) {
System.err.println("Unknown error: " + e.getClass().getSimpleName() + ":" + e.getMessage() + ", on " + element);
break;
}
}
} else {
System.err.println("Error: " + e.getMessage());
}
System.out.println(HELP_TEXT);
if (args != null && args.length > 0) {
System.out.println("\ninput args:");
Arrays.asList(args).forEach(System.out::println);
}
});
}
private static void handleResult(List<String> result, InputArgs inputArgs) {
if (inputArgs.outFile != null) {
File file = new File(inputArgs.outFile);
try (PrintStream ps = new PrintStream(file)) {
for (String line : result) {
ps.println(line);
}
System.out.println("Result written to file: " + file.getAbsolutePath());
} catch (Exception e) {
System.err.println(e.getMessage());
}
} else {
result.forEach(System.out::println);
}
}
private static List<String> doWork(InputArgs inputArgs) {
List<List<String>> lists = new ArrayList<>();
for (String path : inputArgs.pathList) {
lists.add(getFiles(path, file -> {
if (inputArgs.excludeList == null) {
return true;
}
for (Pattern exclude : inputArgs.excludeList) {
if (exclude.matcher(file.getAbsolutePath()).matches()) {
return false;
}
}
return true;
}));
}
List<List<String[]>> pathInfoLists = new ArrayList<>();
for (List<String> list : lists) {
List<String[]> pathInfoList = new ArrayList<>();
pathInfoLists.add(pathInfoList);
int size = list.size();
for (int i = 0; i < size; i++) {
pathInfoList.add(new String[]{replacePath(list.get(i), inputArgs), list.get(i)});
}
Collections.sort(pathInfoList, Comparator.comparing(o -> o[0]));
}
List<String> result;
if (inputArgs.searchMode) {
result = doSearch(pathInfoLists, inputArgs);
} else {
result = doCompare(pathInfoLists, inputArgs);
}
return result;
}
private static List<String> doCompare(List<List<String[]>> pathInfoLists, InputArgs inputArgs) {
List<String> result = new ArrayList<>();
for (int i = 0; i < inputArgs.pathList.size(); i++) {
result.add("-----------------" + (i + 1) + "-------------------");
result.add(inputArgs.pathList.get(i));
result.add("count: " + pathInfoLists.get(i).size());
List<String[]> list = pathInfoLists.get(i);
boolean curFirstFind = true;
int specialCount = 0;
int firstIndex = result.size();
String lastPatten = null;
int lastPattenStart = -1;
int samePattenCount = 0;
for (String[] path : list) {
boolean find = false;
for (List<String[]> l : pathInfoLists) {
if (l == list) {
continue;
}
for (String[] item : l) {
if (item[0].equals(path[0])) {
find = true;
break;
}
}
}
if (!find) {
if (curFirstFind) {
curFirstFind = false;
result.add("In processing..");
}
if (!path[0].equals(lastPatten)) {
if (lastPattenStart != -1) {
result.set(lastPattenStart, System.lineSeparator() + lastPatten + " (count: " + samePattenCount + "):");
}
lastPatten = path[0];
if (!path[0].equals(path[1])) {
lastPattenStart = result.size();
samePattenCount = 1;
result.add("In processing..");
} else {
if (lastPattenStart != -1) {
result.add("");
}
lastPattenStart = -1;
}
} else {
++samePattenCount;
}
result.add(path[1]);
++specialCount;
}
}
if (lastPattenStart != -1) {
result.set(lastPattenStart, "\n" + lastPatten + " (count: " + samePattenCount + "):");
}
if (specialCount > 0) {
result.set(firstIndex, "Only in current path (count: " + specialCount + "):");
}
result.add("-----------------" + (i + 1) + "-------------------");
result.add(System.lineSeparator());
}
return result;
}
private static List<String> doSearch(List<List<String[]>> pathInfoLists, InputArgs inputArgs) {
List<String> result = new ArrayList<>();
result.add("Search ..");
int count = 0;
for (int i = 0; i < inputArgs.pathList.size(); i++) {
List<String[]> list = pathInfoLists.get(i);
result.add(System.lineSeparator() + inputArgs.pathList.get(i));
int index = result.size();
result.add("");
int currentCount = 0;
for (String[] path : list) {
if (!path[0].equals(path[1])) {
result.add(path[1]);
++count;
++currentCount;
}
}
result.set(index, "Found " + currentCount + " items in the current directory:");
}
result.set(0, "Found " + count + " items in the all directory:");
return result;
}
private static String replacePath(String path, InputArgs inputArgs) {
if (inputArgs.replaceList == null || inputArgs.replaceList.isEmpty()) {
return path;
}
for (String replace : inputArgs.replaceList) {
List<Integer> pointList = findReplacePosition(path, replace);
path = doReplace(path, pointList, getReplacement(replace));
}
return path;
}
private static String getReplacement(String replace) {
String regex = "[^" + (File.separator.equals("\\") ? "\\\\" : "/") + ",*]+";
String[] arr = replace.split(File.separator == "/" ? "/" : "\\\\", Integer.MAX_VALUE);
StringBuilder replaceSB = new StringBuilder();
for (int i = 0; i < arr.length; ++i) {
String s = arr[i];
if (!s.contains("*")) {
s = s.replaceAll(regex, "*");
}
replaceSB.append(s);
if (i < arr.length - 1) {
replaceSB.append(File.separator);
}
}
return replaceSB.toString();
}
private static String doReplace(String path, List<Integer> pointList, String replacement) {
if (!pointList.isEmpty()) {
int count = pointList.size() / 2;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < count; i++) {
if (pointList.get(i * 2) > 0) {
sb.append(replacement);
}
sb.append(path, pointList.get(i * 2), pointList.get(i * 2 + 1));
}
path = sb.toString().replace(File.separator + File.separator, File.separator);
}
return path;
}
private static List<Integer> findReplacePosition(String path, String replace) {
List<Integer> pointList = new ArrayList<>();
int start = 0;
int lastMatchEnd = 0;
for (int i = 0, j; i < path.length(); i++) {
boolean lastStar = false;
for (j = 0; j < replace.length(); j++) {
if (i == path.length()) {
break;
}
char rChar = Character.toLowerCase(replace.charAt(j));
char pChar = Character.toLowerCase(path.charAt(i));
if (rChar == '*') {
lastStar = true;
continue;
}
if (rChar != pChar) {
if (File.separatorChar == pChar || !lastStar) {
break;
}
--j;
}
lastStar = false;
++i;
}
if (j == replace.length()) {
if (start > 0) {
if (pointList.isEmpty()) {
pointList.add(0);
pointList.add(start + 1);
} else {
pointList.add(lastMatchEnd);
pointList.add(start + 1);
}
}
lastMatchEnd = i;
start = i - 1;
} else {
++start;
}
if (start > 0 && replace.startsWith(File.separator) && replace.endsWith(File.separator)) {
i = start - 1;
} else {
i = start;
}
}
if (!pointList.isEmpty()) {
pointList.add(lastMatchEnd);
pointList.add(path.length());
}
return pointList;
}
private static List<String> getFiles(String path, Predicate<File> filter) {
List<String> fileList = new ArrayList<>();
Stack<File[]> filesStack = new Stack<>();
Stack<Integer> indexStack = new Stack<>();
File[] files = new File[]{new File(path)};
int index = 0;
do {
for (int i = index; i < files.length; i++) {
File file = files[i];
if (!file.exists() || (filter != null && !filter.test(file))) {
continue;
}
if (file.isDirectory()) {
File[] listFiles = file.listFiles();
if (listFiles == null || listFiles.length == 0) {
fileList.add(file.getAbsolutePath());
} else {
filesStack.push(files);
indexStack.push(i + 1);
i = -1;
files = listFiles;
}
} else {
fileList.add(file.getAbsolutePath());
}
}
files = filesStack.pop();
index = indexStack.pop();
} while (!filesStack.isEmpty());
return fileList;
}
private static InputArgs parseArgs(String[] args) {
InputArgs inputArgs = new InputArgs();
inputArgs.argList = Arrays.asList(args);
inputArgs.searchMode = inputArgs.argList.contains("--search");
int length = args.length;
if (length == 1 && "--help".equalsIgnoreCase(args[0])) {
System.out.println(HELP_TEXT);
System.exit(0);
}
for (int i = 0; i < length; i++) {
String arg = args[i];
if ("--paths".equalsIgnoreCase(arg)) {
if (inputArgs.pathList == null) {
inputArgs.pathList = new ArrayList<>();
}
String[] paths = ++i < length ? args[i].split(",") : new String[0];
inputArgs.pathList.addAll(Arrays.stream(paths).filter(path -> {
File file = new File(path);
if (!file.exists()) {
throw new IllegalArgumentException("Not found file or directory:" + path);
}
return true;
}).collect(Collectors.toList()));
} else if ("--replace".equalsIgnoreCase(arg) || "--search".equalsIgnoreCase(arg)) {
if (inputArgs.searchMode && "--replace".equalsIgnoreCase(arg)) {
continue;
}
if (inputArgs.replaceList == null) {
inputArgs.replaceList = new ArrayList<>();
}
String[] replaces = ++i < length ? args[i].split(",") : new String[0];
inputArgs.replaceList.addAll(Arrays.stream(replaces)
.map(r -> replaceFileSeparator(r, false).replaceAll("[*]+", "*"))
.collect(Collectors.toList()));
} else if ("--exclude".equalsIgnoreCase(arg)) {
if (inputArgs.excludeList == null) {
inputArgs.excludeList = new ArrayList<>();
}
String[] pattens = ++i < length ? args[i].split(",") : new String[0];
inputArgs.excludeList.addAll(getPattenList(pattens));
} else if ("--outfile".equalsIgnoreCase(arg)) {
String path = Paths.get(((++i < length) ? args[i] : "")).normalize().toString();
if (!path.isEmpty()) {
inputArgs.outFile = path;
} else if (inputArgs.outFile == null) {
inputArgs.outFile = "";
}
} else {
throw new IllegalArgumentException("Unrecognized option: " + arg);
}
}
if (inputArgs.pathList == null || inputArgs.pathList.isEmpty()) {
throw new IllegalArgumentException("Invalid paths argument,correct format:--paths <path>[,<path>]..");
}
if (inputArgs.replaceList != null && inputArgs.replaceList.isEmpty()) {
throw new IllegalArgumentException("Invalid replace argument,correct format: --" + (inputArgs.searchMode ? "search" : "replace") + " <str>[,<str>]..");
}
if (inputArgs.excludeList != null && inputArgs.excludeList.isEmpty()) {
throw new IllegalArgumentException("Invalid exclude argument,correct format: --exclude <patten>[,<patten>]..");
}
if (inputArgs.outFile != null && inputArgs.outFile.isEmpty()) {
throw new IllegalArgumentException("Invalid outfile argument,correct format: --outfile str");
}
return inputArgs;
}
private static Collection<? extends Pattern> getPattenList(String[] pattens) {
try {
return Arrays.stream(pattens)
.map(patten -> Pattern.compile(replaceFileSeparator(patten, true)))
.collect(Collectors.toList());
} catch (Exception e) {
throw new IllegalArgumentException("Invalid regular");
}
}
private static String replaceFileSeparator(String str, boolean isRegular) {
String winSeparator = isRegular ? "\\\\" : "\\";
if (File.separator.equals("/")) {
str = str.replace(winSeparator, "/");
} else {
str = str.replace("/", winSeparator);
}
return str;
}
}
class InputArgs {
List<String> argList;
boolean searchMode;
List<String> pathList;
List<String> replaceList;
List<Pattern> excludeList;
String outFile;
}