/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.command.file; import java.io.BufferedReader; import java.io.Closeable; import java.io.File; import java.io.FileFilter; import java.io.Flushable; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.io.OutputStream; import java.io.PrintWriter; import java.io.Reader; import java.io.Writer; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.List; import java.util.regex.MatchResult; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import org.apache.log4j.Logger; import org.jnode.command.util.AbstractDirectoryWalker; import org.jnode.command.util.AbstractDirectoryWalker.PathnamePatternFilter; import org.jnode.command.util.IOUtils; import org.jnode.shell.AbstractCommand; import org.jnode.shell.syntax.Argument; import org.jnode.shell.syntax.FileArgument; import org.jnode.shell.syntax.FlagArgument; import org.jnode.shell.syntax.IntegerArgument; import org.jnode.shell.syntax.StringArgument; /** * TODO implement Fixed/Basic/Ext matchers * TODO implement --color (if/when possible) * @author peda * @author crawley@jnode.org * @author chris boertien */ public class GrepCommand extends AbstractCommand { private static final Logger log = Logger.getLogger(GrepCommand.class); private static final boolean DEBUG = false; private static final int BUFFER_SIZE = 8192; private static final String help_matcher_fixed = "Patterns are fixed strings, seperated by new lines. Any of " + "which is to be matched."; private static final String help_matcher_basic = "Use basic regular expressions (Default)."; private static final String help_matcher_ext = "Use extended regular expressions."; private static final String help_matcher_perl = "Use perl regular expressions."; private static final String help_case = "Ignore case in both the <pattern> and the input files."; private static final String help_invert = "Invert the matching to select non-matching lines."; private static final String help_match_word = "Select only those lines containing matches that form whole words." + "The test is that the matching substring must either be at the " + "beginning of the line, or preceded by a non-word character. It " + "also must be at the end of a line, or followed by a non-word " + "character. Word characters are letters, digits and the underscore."; private static final String help_match_line = "Select only those matches that match the whole line"; private static final String help_count = "Suppress normal output, instead printing a count of matching lines for " + "input file. With --invert-match, count non-matching lines."; private static final String help_file_nomatch = "Suppress normal output, instead printing the name of each input " + "file from which no output would normally be printed. Scanning " + "stops after making a single match."; private static final String help_file_match = "Suppress normal output, isntead printing the name of each input " + "file from which output would normally be printed. Scanning stops " + "after making a single match."; private static final String help_max = "Stop reading a file after n matches. If the input is stdin from a " + "regular file, and n matching lines are output, grep ensures that the " + "standard input is positioned, to just after the last matching line before " + "exiting, regardless of trailing context lines. When grep stops after n " + "matching lines, it will output trailing context lines. When used with " + "--count, count wil never be more than n. When used with --invert, grep " + "stops after n non-matching lines."; private static final String help_only_matching = "Print only the matched parts of the matching line. with each " + "part on a seperate output line."; private static final String help_quiet = "Do not write _anything_ to stdout. Exit immediately with zero status if" + " any match is found, even if an error was detected."; private static final String help_suppress = "Suppress error messages about nonexistant or unreadable files."; private static final String help_debug = "Output debug information."; private static final String help_prefix_byte = "Print the 0-based byte offset within the input file before each " + "line of output. If --only-matching is set, print the offset of " + "the matching part itself."; private static final String help_prefix_file = "Print the file name for each match. This is the default when " + "there are multiple files."; private static final String help_prefix_nofile = "Suppress the prefixing of file names on output. This is the " + "default when there is only one file, or only stdin to search."; private static final String help_prefix_label = "Display input coming from stdin as actually coming from the " + "given label."; private static final String help_prefix_line = "Prefix each line of output with the 1-based line number within " + "its input file."; private static final String help_prefix_tab = "Make sure that the first character of actual line content lies on " + "a tab stop so that the alignment looks normal. This also causes " + "the line number and byte offset, if output, to be printed in a " + "minimum size field width."; private static final String help_prefix_null = "Output a zero byte instead of the character that normally " + "a file name, which is normally a newline."; private static final String help_context_after = "Print n lines of trailing context lines after matching lines. " + "Places a line containing a group seperator '--' between " + "contiguous groups of matches. With --only-matching this has no " + "effect and a warning is given."; private static final String help_context_before = "Print n lines of leading context lines before matching lines. " + "Places a line containing a group seperator '--' between " + "contiguous groups of matches. With --only-matching this has " + "no effect and a waring is given."; private static final String help_context_both = "Print n lines of leading and trailing context. Places a line " + "containing a group seperator '--' between contiguous groups of " + "matches. With --only-matching this has no effect and a warning " + "is given."; private static final String help_mode_binary = "If the first few bytes of a file indicate that the file contains " + "binary data, assume the file is the specified type. By default " + "the type is 'binary' and grep outputs a one line message saying " + "that the binary file matches or nothing if there is no match. If " + "type is 'without-match' grep assumes that binary files do not " + "match. If type is 'text' grep processes a binary file as if it " + "were text. __Warning__: --binary text may output binary garbage " + "which can have side effects if the output is a terminal."; private static final String help_mode_binary_text = "Same as --binary text"; private static final String help_mode_binary_skip = "Same as --binary without-match"; private static final String help_mode_device = "If an input file is a device, fifo or socket, then set this to " + "'read' to read the data like a normal file, or 'skip' to not " + "read these types of files. The default is read."; private static final String help_mode_dir = "If an input file is a directory, then set this to 'read' to read " + "directory file itself. Set it to 'skip' to skip listed directories. " + "Set it to recurse in order to tell grep to search listed " + "directories for files to match recursively. The default is read."; private static final String help_mode_dir_recurse = "Same as --directories recurse."; private static final String help_exclude = "Skip processing files whose basename matches the given glob-pattern."; private static final String help_exclude_file = "Skip processing files whos basenames matches any of the " + "glob patterns in the given file, one per line."; private static final String help_exclude_dir = "Exclude directories matching the given glob-pattern."; private static final String help_include = "Only process files whose basename matches the given glob-pattern."; private static final String help_null_term = "Treat the input as a set of lines terminated by a null byte " + "instead of a newline."; private static final String help_patterns = "Match the given pattern(s) against the input files."; private static final String help_pattern_files = "File with patterns to match, one per line."; private static final String help_files = "The files to match against. If there are no files, or if any file is " + "'-' then match stdandard input."; private static final String err_ex_walker = "Exception while walking."; private class ContextLineWriter implements Closeable, Flushable { protected LineNumberReader reader; private PrintWriter writer; private Deque<String> contextStack; private int linesUntilFlush; private int linesForFlush; private int contextBefore; private int contextAfter; private boolean haveLine; private boolean firstFlush; public ContextLineWriter(Writer writer, int before, int after) { if (writer instanceof PrintWriter) { this.writer = (PrintWriter) writer; } else { this.writer = new PrintWriter(writer); } firstFlush = true; contextBefore = before; contextAfter = after; linesForFlush = before + after + 1; contextStack = new ArrayDeque<String>(); } public ContextLineWriter(OutputStream out, int before, int after) { this(new PrintWriter(out), before, after); } @Override public void close() { if (reader != null) { finish(); } IOUtils.close(true, writer); writer = null; } @Override public void flush() { doFlush(); while (contextStack.size() > 0) { writer.println(contextStack.removeFirst()); } haveLine = false; writer.flush(); } public void setIn(InputStream in) throws IOException { setIn(new InputStreamReader(in)); } public LineNumberReader setIn(Reader reader) throws IOException { if (isClosed()) { throw new IOException("Stream closed"); } if (this.reader != null) { finish(); } this.reader = new LineNumberReader(reader, BUFFER_SIZE) { @Override public String readLine() throws IOException { String line = super.readLine(); if (line != null) { if (!haveLine) { if (contextStack.size() > 0) { contextStack.addLast(doContextLine(contextStack.removeLast())); } if (contextStack.size() > contextBefore) { contextStack.removeFirst(); } } else { if (linesUntilFlush < (linesForFlush)) { contextStack.addLast(doContextLine(contextStack.removeLast())); } if (linesUntilFlush == 0) { String[] saveLines = new String[contextBefore]; for (int i = 0; i < contextBefore; i++) { saveLines[i] = contextStack.removeLast(); } contextStack.removeLast(); flush(); for (int i = contextBefore - 1; i >= 0; i--) { contextStack.addLast(saveLines[i]); } } else { linesUntilFlush--; } } contextStack.addLast(line); } else { finish(); } return line; } }; return this.reader; } public void addLine(String s) throws IOException { if (isClosed()) { throw new IOException("Stream closed"); } contextStack.addLast(s); writeLast(); } public void rewriteLast(String s) throws IOException { if (isClosed()) { throw new IOException("Stream closed"); } contextStack.removeLast(); contextStack.addLast(s); writeLast(); } public void writeLast() throws IOException { if (isClosed()) { throw new IOException("Stream closed"); } if (!haveLine) { haveLine = true; } linesUntilFlush = linesForFlush; } private void finish() { if (reader == null) return; if (!haveLine) { contextStack.clear(); } else { int excessLines = (linesForFlush - linesUntilFlush) - contextAfter; for (int i = 0; i < excessLines; i++) { contextStack.removeLast(); } flush(); } doFinish(); IOUtils.close(reader); reader = null; } private boolean isClosed() { return writer == null; } @SuppressWarnings("unused") private boolean haveLine() { return haveLine; } protected void doFlush() { if (!firstFlush) { contextStack.addFirst("-----"); } firstFlush = false; } protected void doFinish() { // no-op } protected String doContextLine(String s) { return prefixLine(s, currentFile, currentLine, currentByte, '-'); } } private final StringArgument Patterns; private final FileArgument PatternFiles; private final FileArgument Files; private final FlagArgument MatcherFixed; private final FlagArgument MatcherBasic; private final FlagArgument MatcherExt; private final FlagArgument MatcherPerl; private final FlagArgument IgnoreCase; private final FlagArgument Invert; private final FlagArgument MatchWord; private final FlagArgument MatchLine; private final FlagArgument ShowCount; private final FlagArgument ShowFileNoMatch; private final FlagArgument ShowFileMatch; private final FlagArgument ShowOnlyMatch; private final IntegerArgument MaxCount; private final FlagArgument Quiet; private final FlagArgument Suppress; private final FlagArgument PrefixByte; private final FlagArgument PrefixFile; private final FlagArgument PrefixNoFile; private final StringArgument PrefixLabel; private final FlagArgument PrefixLine; private final FlagArgument PrefixTab; private final FlagArgument PrefixNull; private final IntegerArgument ContextAfter; private final IntegerArgument ContextBefore; private final IntegerArgument ContextBoth; private final StringArgument ModeBinary; private final StringArgument ModeDevice; private final StringArgument ModeDir; private final FlagArgument ModeBinaryText; private final FlagArgument ModeBinarySkip; private final FlagArgument ModeDirRecurse; private final StringArgument Exclude; private final FileArgument ExcludeFile; private final StringArgument ExcludeDir; private final StringArgument Include; private final FlagArgument NullTerm; private final FlagArgument Debug; private static final int MATCHER_FIXED = 1; private static final int MATCHER_BASIC = 2; private static final int MATCHER_EXT = 3; private static final int MATCHER_PERL = 4; private static final int PREFIX_FILE = 0x01; private static final int PREFIX_LINE = 0x02; private static final int PREFIX_BYTE = 0x04; private static final int PREFIX_NOFILE = 0x08; private static final int PREFIX_TAB = 0x10; private static final int PREFIX_NULL = 0x20; @SuppressWarnings("unused") private static final int PREFIX_ALL = PREFIX_FILE | PREFIX_LINE | PREFIX_BYTE; @SuppressWarnings("unused") private static final int PREFIX_FL = PREFIX_FILE | PREFIX_LINE; @SuppressWarnings("unused") private static final int PREFIX_FB = PREFIX_FILE | PREFIX_BYTE; @SuppressWarnings("unused") private static final int PREFIX_LB = PREFIX_LINE | PREFIX_BYTE; private PrintWriter err; private PrintWriter out; private ContextLineWriter contextOut; private Reader in; private List<File> files; private List<Pattern> patterns; private String prefixLabel; private Matcher match; private String currentFile; private int matcher; private int prefix; private int maxCount = Integer.MAX_VALUE; private int contextBefore; private int contextAfter; @SuppressWarnings("unused") private int patternFlags; private int rc = 1; private int currentLine; private int currentByte; private boolean inverse; private boolean matchCase; private boolean matchWord; private boolean matchLine; private boolean showCount; private boolean showFileMatch; private boolean showFileNoMatch; private boolean showOnlyMatch; private boolean quiet; private boolean suppress; private boolean debug; private boolean recurse; @SuppressWarnings("unused") private boolean dirAsFile; @SuppressWarnings("unused") private boolean binaryAsText; @SuppressWarnings("unused") private boolean binaryAsBinary; @SuppressWarnings("unused") private boolean readDevice; private boolean exitOnFirstMatch; public GrepCommand() { super("Search for lines that match a string or regex"); MatcherFixed = new FlagArgument("matcher-fixed", 0, help_matcher_fixed); MatcherBasic = new FlagArgument("matcher-basic", 0, help_matcher_basic); MatcherExt = new FlagArgument("matcher-ext", 0, help_matcher_ext); MatcherPerl = new FlagArgument("matcher-perl", 0, help_matcher_perl); registerArguments(MatcherFixed, MatcherBasic, MatcherExt, MatcherPerl); IgnoreCase = new FlagArgument("ignore-case", 0, help_case); Invert = new FlagArgument("invert", 0, help_invert); MatchWord = new FlagArgument("word-match", 0, help_match_word); MatchLine = new FlagArgument("line-match", 0, help_match_line); MaxCount = new IntegerArgument("max-matches", 0, help_max); Quiet = new FlagArgument("quiet", 0, help_quiet); Suppress = new FlagArgument("suppress", 0, help_suppress); Debug = new FlagArgument("debug", 0, help_debug); registerArguments(IgnoreCase, Invert, MatchWord, MatchLine, MaxCount, Quiet, Suppress, Debug); ShowCount = new FlagArgument("show-count", 0, help_count); ShowFileNoMatch = new FlagArgument("show-files-nomatch", 0, help_file_nomatch); ShowFileMatch = new FlagArgument("show-files-match", 0, help_file_match); ShowOnlyMatch = new FlagArgument("show-only-match", 0, help_only_matching); registerArguments(ShowCount, ShowFileNoMatch, ShowFileMatch, ShowOnlyMatch); PrefixByte = new FlagArgument("prefix-byte", 0, help_prefix_byte); PrefixFile = new FlagArgument("prefix-file", 0, help_prefix_file); PrefixNoFile = new FlagArgument("prefix-nofile", 0, help_prefix_nofile); PrefixLabel = new StringArgument("prefix-label", 0, help_prefix_label); PrefixLine = new FlagArgument("prefix-line", 0, help_prefix_line); PrefixTab = new FlagArgument("prefix-tab", 0, help_prefix_tab); PrefixNull = new FlagArgument("prefix-null", 0, help_prefix_null); registerArguments(PrefixByte, PrefixFile, PrefixNoFile, PrefixLabel, PrefixLine, PrefixTab, PrefixNull); ContextAfter = new IntegerArgument("show-context-after", 0, help_context_after); ContextBefore = new IntegerArgument("show-context-before", 0, help_context_before); ContextBoth = new IntegerArgument("show-context-both", 0, help_context_both); registerArguments(ContextAfter, ContextBefore, ContextBoth); ModeBinary = new StringArgument("mode-binary", 0, help_mode_binary); ModeDevice = new StringArgument("mode-device", 0, help_mode_device); ModeDir = new StringArgument("mode-dir", 0, help_mode_dir); ModeBinaryText = new FlagArgument("mode-binary-text", 0, help_mode_binary_text); ModeBinarySkip = new FlagArgument("mode-binary-skip", 0, help_mode_binary_skip); ModeDirRecurse = new FlagArgument("mode-dir-recurse", 0, help_mode_dir_recurse); Exclude = new StringArgument("pattern-exclude", 0, help_exclude); ExcludeFile = new FileArgument("pattern-exclude-file", Argument.EXISTING, help_exclude_file); ExcludeDir = new StringArgument("pattern-exclude-dir", 0, help_exclude_dir); Include = new StringArgument("pattern-include", 0, help_include); registerArguments(ModeBinary, ModeBinaryText, ModeBinarySkip, ModeDevice, ModeDir, ModeDirRecurse); registerArguments(Exclude, ExcludeFile, ExcludeDir, Include); NullTerm = new FlagArgument("null-term", 0, help_null_term); Patterns = new StringArgument("patterns", Argument.MULTIPLE | Argument.MANDATORY, help_patterns); PatternFiles = new FileArgument("pattern-files", Argument.MULTIPLE | Argument.EXISTING, help_pattern_files); Files = new FileArgument("files", Argument.MULTIPLE | Argument.EXISTING | FileArgument.HYPHEN_IS_SPECIAL, help_files); registerArguments(Patterns, PatternFiles, Files, NullTerm); // Default matcher match = Pattern.compile(".*").matcher(""); } /** * main method, normally not used, use execute instead!! * @param args * @throws Exception */ public static void main(String[] args) throws Exception { new GrepCommand().execute(args); } /** * Primary entry point */ public void execute() throws Exception { err = getError().getPrintWriter(); in = getInput().getReader(); out = getOutput().getPrintWriter(); LineNumberReader reader; String name; try { parseOptions(); if ((contextBefore > 0) || (contextAfter > 0)) { contextOut = new ContextLineWriter(out, contextBefore, contextAfter); } for (File file : files) { reader = null; name = file.getPath(); try { if (name.equals("-")) { if (contextOut != null) { reader = contextOut.setIn(in); } else { reader = new LineNumberReader(in, BUFFER_SIZE); } name = prefixLabel; } else { if (contextOut != null) { reader = contextOut.setIn(IOUtils.openReader(file)); } else { reader = IOUtils.openLineReader(file, BUFFER_SIZE); } } currentFile = name; if (exitOnFirstMatch) { if (matchUntilOne(reader)) { rc = 0; break; } continue; } if (showFileMatch) { if (matchUntilOne(reader)) { printFile(name); } continue; } if (showFileNoMatch) { if (!matchUntilOne(reader)) { printFile(name); } continue; } if (showCount) { printFileCount(matchCount(reader)); continue; } matchNormal(reader); } catch (IOException e) { error("IOException greping file : " + file); } finally { IOUtils.close(reader); } } } catch (Exception e) { e.printStackTrace(); } finally { exit(rc); } } /* Each of the next few methods are inner loops for different conditions. This * is mostly to avoid a complex set of branches inside the inner loop. With any * luck they will get inlined anyway. */ /** * Matches lines in a file until a single match is made. */ private boolean matchUntilOne(LineNumberReader reader) throws IOException { String line; while ((line = reader.readLine()) != null) { if (match(line) != null) { return true; } } return false; } /** * Counts the number of matching or non-matching lines in a file. */ private int matchCount(LineNumberReader reader) throws IOException { String line; int ret = 0; while ((ret < maxCount) && ((line = reader.readLine()) != null)) { if (((match(line) != null) ^ inverse)) { ret++; } } return ret; } /** * Uses the MatchResult to only print the substring of the line that matched. */ @SuppressWarnings("unused") private void matchSubstring(LineNumberReader reader, String name) throws IOException { String line; MatchResult result; int byteCount = 0; int matches = 0; while ((matches < maxCount) && (line = reader.readLine()) != null) { result = match(line); if (result != null) { printMatch(line.substring(result.start(), result.end()), name, reader.getLineNumber(), byteCount + result.start()); rc = 0; matches++; } byteCount += line.length(); } } /** * Prints matching or non-matching lines to stdout with a possible prefix. */ private void matchNormal(LineNumberReader reader) throws IOException { String line; int matches = 0; while ((matches < maxCount) && ((line = reader.readLine()) != null)) { currentLine = reader.getLineNumber(); if ((match(line) != null) ^ inverse) { printMatch(line, currentFile, currentLine, currentByte); rc = 0; matches++; } currentByte += line.length() + 1; } } /** * Compares the line to a list of patterns, returing the result of the first one to match. */ private MatchResult match(String line) { match.reset(line); for (Pattern pattern : patterns) { if (match.reset().usePattern(pattern).find()) { return match.toMatchResult(); } } return null; } private String prefixLine(String line, String name, int lineCount, int byteCount, char fieldSep) { if (prefix == PREFIX_NOFILE) { return line; } StringBuilder sb = new StringBuilder(); if ((prefix & PREFIX_TAB) != 0) { if ((prefix & PREFIX_FILE) != 0) { sb.append(name); if ((prefix & (PREFIX_LINE | PREFIX_BYTE)) == 0) { sb.append('\t'); } sb.append(fieldSep); } if ((prefix & PREFIX_LINE) != 0) { sb.append(padNumber(lineCount, 4)); if ((prefix & PREFIX_BYTE) == 0) { sb.append('\t'); } sb.append(fieldSep); } if ((prefix & PREFIX_BYTE) != 0) { sb.append(padNumber(byteCount, 9)); sb.append('\t'); sb.append(fieldSep); } } else { if ((prefix & PREFIX_FILE) != 0) { sb.append(name); sb.append(fieldSep); } if ((prefix & PREFIX_LINE) != 0) { sb.append(lineCount); sb.append(fieldSep); } if ((prefix & PREFIX_BYTE) != 0) { sb.append(byteCount); sb.append(fieldSep); } } return sb.append(line).toString(); } /** * Outputs a matched string, in the given file, at the given line and the given byte offset. Outputs * to stdout the match string, along with any set prefix options. */ private void printMatch(String line, String name, int lineCount, int byteCount) throws IOException { if (quiet) return; String newLine = prefixLine(line, name, lineCount, byteCount, ':'); if (contextOut != null) { if (newLine == line) { contextOut.writeLast(); } else { contextOut.rewriteLast(newLine); } } else { out.println(newLine); } } /** * Outputs the name and count seperated by a colon or null byte */ private void printFileCount(int count) { if (quiet) return; out.print(currentFile); out.print(":"); out.println(count); } /** * Outputs a file name and appends a null byte if PREFIX_NULL is set, otherwise it * appends a newline. */ private void printFile(String name) { if (quiet) return; out.print(name); if ((prefix & PREFIX_NULL) != 0) { out.print('\u0000'); } else { out.println(); } } private String padNumber(int n, int size) { return String.format("%" + size + 'd', n); } /** * grep uses different mnemonics for character classes, need to convert grep-style * to java-style. * Perl regex is unformatted * Ext regex is formatted as follows * [:alnum:] = \p{Alnum} * [:alpha:] = \p{Alpha} * [:cntrl:] = \p{Cntrl} * [:digit:] = \p{Digit} * [:graph:] = ??? * [:lower:] = \p{Lower} * [:print:] = ??? * [:punct:] = \p{Punct} * [:upper:] = \p{Upper} * [:xdigit:] = [0-9A-Fa-f] * \< and \> = match word boundary * In basic */ private Pattern rewritePattern(String pattern) throws PatternSyntaxException { debug("Pattern before: " + pattern); int flags = 0; if (matcher == MATCHER_FIXED) { pattern = Pattern.quote(pattern); } if (!matchCase) { flags |= Pattern.CASE_INSENSITIVE; } StringBuilder sb = new StringBuilder(pattern); switch(matcher) { case MATCHER_BASIC : // de-sensitize some meta-characters // fall through case MATCHER_EXT : // perform class synax conversion ([:class:] -> \p{Class}) // fall through : case MATCHER_PERL : // nothing for now // fall through default : break; } if (matchWord) { if (!pattern.startsWith("\\b")) { sb.insert(0, "\\b"); } if (!pattern.endsWith("\\b")) { sb.append("\\b"); } } else if (matchLine) { if (!pattern.startsWith("^")) { sb.insert(0, '^'); } if (!pattern.endsWith("$")) { sb.append('$'); } } debug("Pattern after : " + sb); return Pattern.compile(sb.toString(), flags); } /*********************************************************/ /************** Command Line Parsing *********************/ /*********************************************************/ private void parseOptions() { // parse these early so they are enforced quiet = Quiet.isSet(); suppress = Suppress.isSet(); debug = DEBUG || Debug.isSet(); if (PrefixLabel.isSet()) prefixLabel = PrefixLabel.getValue(); else prefixLabel = "stdin"; if (PrefixByte.isSet()) prefix |= PREFIX_BYTE; if (PrefixFile.isSet()) prefix |= PREFIX_FILE; if (PrefixNoFile.isSet()) prefix |= PREFIX_NOFILE; if (PrefixLine.isSet()) prefix |= PREFIX_LINE; if (PrefixTab.isSet()) prefix |= PREFIX_TAB; if (PrefixNull.isSet()) prefix |= PREFIX_NULL; if ((prefix & (PREFIX_FILE | PREFIX_NOFILE)) == (PREFIX_FILE | PREFIX_NOFILE)) { prefix ^= PREFIX_NOFILE; } if (MatcherFixed.isSet()) matcher = MATCHER_FIXED; if (MatcherBasic.isSet()) matcher = MATCHER_BASIC; if (MatcherExt.isSet()) matcher = MATCHER_EXT; if (MatcherPerl.isSet()) matcher = MATCHER_PERL; if (matcher == 0) matcher = MATCHER_BASIC; matchWord = MatchWord.isSet(); matchLine = MatchLine.isSet(); matchCase = !IgnoreCase.isSet(); inverse = Invert.isSet(); parsePatterns(); // This requires the above options be parsed already. if (MaxCount.isSet()) maxCount = MaxCount.getValue(); showCount = ShowCount.isSet(); showFileMatch = ShowFileMatch.isSet(); showFileNoMatch = ShowFileNoMatch.isSet(); showOnlyMatch = ShowOnlyMatch.isSet(); // Setup for fast-path exit(0) on first match if (quiet || (inverse && showOnlyMatch)) { exitOnFirstMatch = true; } String s = " "; if (ModeBinary.isSet()) { s = ModeBinary.getValue(); } if (!(ModeBinarySkip.isSet() || s.equals("without-match"))) { if (ModeBinaryText.isSet() || s.equals("text")) { binaryAsText = true; } else { binaryAsBinary = true; } } s = " "; if (ModeDir.isSet()) { s = ModeDir.getValue(); } if (ModeDirRecurse.isSet() || s.equals("recurse")) { recurse = true; } if (!(ModeDevice.isSet() && ModeDevice.getValue().equals("skip"))) { readDevice = true; } parseFiles(); // This requires the above options be parsed already. if (files.size() > 1) { if ((prefix & PREFIX_NOFILE) == 0) { prefix |= PREFIX_FILE; } } else { if ((prefix & PREFIX_FILE) == 0) { prefix |= PREFIX_NOFILE; } } if (ContextBoth.isSet()) { contextAfter = contextBefore = ContextBoth.getValue(); } else if (ContextBefore.isSet()) { contextBefore = ContextBefore.getValue(); } else if (ContextAfter.isSet()) { contextAfter = ContextAfter.getValue(); } if ((contextAfter > 0 || contextBefore > 0) && showOnlyMatch) { contextAfter = contextBefore = 0; } } private void parsePatterns() { BufferedReader reader; String line; patterns = new ArrayList<Pattern>(); for (String s : Patterns.getValues()) { try { patterns.add(rewritePattern(s)); } catch (PatternSyntaxException e) { error("Invalid Pattern : " + s); exit(2); } } for (File file : PatternFiles.getValues()) { reader = null; try { reader = IOUtils.openBufferedReader(file, BUFFER_SIZE); while ((line = reader.readLine()) != null) { try { patterns.add(rewritePattern(line)); } catch (PatternSyntaxException e) { error("Invalid Pattern : " + line); exit(2); } } } catch (IOException e) { debug("IOException while parsing pattern file : " + file); error("Error reading file: " + file); exit(2); } finally { IOUtils.close(reader); } } } private class Walker extends AbstractDirectoryWalker { @Override public void handleFile(File file) { files.add(file); } @Override public void handleDir(File dir) { // no-op } @Override public void handleRestrictedFile(File file) { // no-op } private void doFile(File file) { if (notFiltered(file)) { files.add(file); } } } private void parseFiles() { BufferedReader reader; files = new ArrayList<File>(); if (!Files.isSet()) { files.add(new File("-")); return; } Walker walker = new Walker(); for (String s : Include.getValues()) { walker.addFilter(new PathnamePatternFilter(s, false)); } for (String s : Exclude.getValues()) { walker.addFilter(new PathnamePatternFilter(s, true)); } for (File file : ExcludeFile.getValues()) { reader = IOUtils.openBufferedReader(file, BUFFER_SIZE); List<String> lines = null; try { lines = IOUtils.readLines(reader); } finally { IOUtils.close(reader); } if (lines != null) { for (String s : lines) { walker.addFilter(new PathnamePatternFilter(s, true)); } } } for (final String s : ExcludeDir.getValues()) { walker.addDirectoryFilter(new FileFilter() { @Override public boolean accept(File file) { return !(file.isDirectory() && file.getName().equals(s)); } }); } List<File> dirs = new ArrayList<File>(); for (File file : Files.getValues()) { if (file.isDirectory()) { if (recurse) { dirs.add(file); } } else if (file.isFile()) { walker.doFile(file); } else { // skip special files } } try { if (dirs.size() > 0) { walker.walk(dirs); } } catch (IOException e) { // technically, the walker shouldn't let this propagate unless something // is really wrong. error(err_ex_walker); exit(2); } } private void error(String s) { if (!suppress) err.println(s); } private void debug(String s) { if (debug) log.debug(s); } @SuppressWarnings("unused") private void debugOptions() { debug("Files : " + files.size()); for (File file : files) { debug(" - " + file); } debug("Patterns : " + patterns.size()); for (Pattern p : patterns) { debug(" - " + p); } } }