diff --git a/core/src/main/java/com/taobao/arthas/core/command/basic1000/GrepCommand.java b/core/src/main/java/com/taobao/arthas/core/command/basic1000/GrepCommand.java index 319c3150b..5eec534b6 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/basic1000/GrepCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/basic1000/GrepCommand.java @@ -3,39 +3,172 @@ package com.taobao.arthas.core.command.basic1000; import com.taobao.arthas.core.command.Constants; import com.taobao.arthas.core.shell.command.AnnotatedCommand; import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.middleware.cli.annotations.Argument; +import com.taobao.middleware.cli.annotations.DefaultValue; import com.taobao.middleware.cli.annotations.Description; import com.taobao.middleware.cli.annotations.Name; +import com.taobao.middleware.cli.annotations.Option; import com.taobao.middleware.cli.annotations.Summary; /** * @see com.taobao.arthas.core.shell.command.internal.GrepHandler */ @Name("grep") -@Summary("grep command for pipes (-e -m -n -v -A -B -C -f )\n" ) +@Summary("grep command for pipes.\n" ) @Description(Constants.EXAMPLE + - "sysprop | grep java \n" + - " sysenv | grep -v JAVA -n\n" + + " sysprop | grep java \n" + + " sysprop | grep java -n\n" + + " sysenv | grep -v JAVA\n" + " sysenv | grep -e \"(?i)(JAVA|sun)\" -m 3 -C 2\n" + - " sysenv | grep -v JAVA -A2 -B3\n" + - " sysenv | grep -e JAVA -f /tmp/t.log \n" + - " thread | grep -m 10 -e \"TIMED_WAITING|WAITING\"\n\n" - +"-e, --regexp use PATTERN for matching\n" - +"-m, --max-count=NUM stop after NUM selected lines\n" - +"-n, --line-number print line number with output lines\n" - +"-v, --invert-match select non-matching lines\n" - +"-A, --after-context=NUM print NUM lines of trailing context\n" - +"-B, --before-context=NUM print NUM lines of leading context\n" - +"-C, --context=NUM print NUM lines of output context\n" -// +"-f, --output=File output result to file, filename endsWith :false for disable append mode\n" - + Constants.WIKI + Constants.WIKI_HOME + "grep") + " sysenv | grep JAVA -A2 -B3\n" + + " thread | grep -m 10 -e \"TIMED_WAITING|WAITING\"\n" + + Constants.WIKI + Constants.WIKI_HOME + "grep") public class GrepCommand extends AnnotatedCommand { + private String pattern; + private boolean ignoreCase; + + /** + * select non-matching lines + */ + private boolean invertMatch; + + private boolean isRegEx = false; + + /** + * print line number with output lines + */ + private boolean showLineNumber = false; + + private boolean trimEnd; + + /** + * print NUM lines of leading context + */ + private int beforeLines; + + /** + * print NUM lines of trailing context + */ + private int afterLines; + + /** + * print NUM lines of output context + */ + private int context; + + /** + * stop after NUM selected lines + */ + private int maxCount; + + @Argument(index = 0, argName = "pattern", required = true) + @Description("Pattern") + public void setOptionName(String pattern) { + this.pattern = pattern; + } + + @Option(shortName = "e", longName = "regex", flag = true) + @Description("Enable regular expression to match") + public void setRegEx(boolean regEx) { + isRegEx = regEx; + } + + @Option(shortName = "i", longName = "ignore-case", flag = true) + @Description("Perform case insensitive matching. By default, grep is case sensitive.") + public void setIgnoreCase(boolean ignoreCase) { + this.ignoreCase = ignoreCase; + } + + @Option(shortName = "v", longName = "invert-match", flag = true) + @Description("Select non-matching lines") + public void setInvertMatch(boolean invertMatch) { + this.invertMatch = invertMatch; + } + + @Option(shortName = "n", longName = "line-number", flag = true) + @Description("Print line number with output lines") + public void setShowLineNumber(boolean showLineNumber) { + this.showLineNumber = showLineNumber; + } + + @Option(longName = "trim-end", flag = true) + @DefaultValue("true") + @Description("Remove whitespaces at the end of the line") + public void setTrimEnd(boolean trimEnd) { + this.trimEnd = trimEnd; + } + + @Option(shortName = "B", longName = "before-context") + @Description("Print NUM lines of leading context)") + public void setBeforeLines(int beforeLines) { + this.beforeLines = beforeLines; + } + + @Option(shortName = "A", longName = "after-context") + @Description("Print NUM lines of trailing context)") + public void setAfterLines(int afterLines) { + this.afterLines = afterLines; + } + + @Option(shortName = "C", longName = "context") + @Description("Print NUM lines of output context)") + public void setContext(int context) { + this.context = context; + } + + @Option(shortName = "m", longName = "max-count") + @Description("stop after NUM selected lines)") + public void setMaxCount(int maxCount) { + this.maxCount = maxCount; + } + + public String getPattern() { + return pattern; + } + + public void setPattern(String pattern) { + this.pattern = pattern; + } + + public boolean isIgnoreCase() { + return ignoreCase; + } + + public boolean isInvertMatch() { + return invertMatch; + } + + public boolean isRegEx() { + return isRegEx; + } + + public boolean isShowLineNumber() { + return showLineNumber; + } + + public boolean isTrimEnd() { + return trimEnd; + } + + public int getBeforeLines() { + return beforeLines; + } + + public int getAfterLines() { + return afterLines; + } + + public int getContext() { + return context; + } + + public int getMaxCount() { + return maxCount; + } + @Override public void process(CommandProcess process) { - process.write("The grep command only for pipes ").write("\n"); - final Description ann = GrepCommand.class.getAnnotation(Description.class); - if (ann != null) { - process.write(ann.value()).write("\n"); - } + process.write("The grep command only for pipes. See 'grep --help'").write("\n"); process.end(); } } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/command/internal/GrepHandler.java b/core/src/main/java/com/taobao/arthas/core/shell/command/internal/GrepHandler.java index 88f0e8f90..0d0b32add 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/command/internal/GrepHandler.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/command/internal/GrepHandler.java @@ -2,166 +2,169 @@ package com.taobao.arthas.core.shell.command.internal; import java.util.List; import java.util.regex.Pattern; + +import com.taobao.arthas.core.command.basic1000.GrepCommand; import com.taobao.arthas.core.shell.cli.CliToken; -import com.taobao.middleware.cli.Argument; -import com.taobao.middleware.cli.CLIs; +import com.taobao.arthas.core.util.StringUtils; +import com.taobao.middleware.cli.CLI; import com.taobao.middleware.cli.CommandLine; -import com.taobao.middleware.cli.Option; +import com.taobao.middleware.cli.annotations.CLIConfigurator; /** * @author beiwei30 on 12/12/2016. */ public class GrepHandler extends StdoutHandler { public static final String NAME = "grep"; - private static final Pattern TRIM_PATTERN; - static { - //默认删除右边的空白字符是为了解决-n 因空白字符导致显示换行的问题 - //ie: sysprop | grep -n java - final String p = System.getProperty("arthas_grep_trim_pattern", "[ \\f\t\\v]+$"); - TRIM_PATTERN = "NOP".equals(p) ? null : Pattern.compile(p); - } private String keyword; private boolean ignoreCase; - // -v, --invert-match select non-matching lines + /** + * select non-matching lines + */ private final boolean invertMatch; - //-e, --regexp=PATTERN use PATTERN for matching + private final Pattern pattern; - // -n, --line-number print line number with output lines + + /** + * print line number with output lines + */ private final boolean showLineNumber; -/* - -B, --before-context=NUM print NUM lines of leading context - -A, --after-context=NUM print NUM lines of trailing context - -C, --context=NUM print NUM lines of output context - */ - private final int beforeLines; - private final int afterLines; - //-m, --max-count=NUM stop after NUM selected lines - private final int maxCount; - + + private boolean trimEnd; + + /** + * print NUM lines of leading context + */ + private final Integer beforeLines; + /** + * print NUM lines of trailing context + */ + private final Integer afterLines; + + /** + * stop after NUM selected lines + */ + private final Integer maxCount; + public static StdoutHandler inject(List tokens) { List args = StdoutHandler.parseArgs(tokens, NAME); - CommandLine commandLine = CLIs.create(NAME) - .addOption(new Option().setShortName("i").setLongName("ignore-case").setFlag(true)) - .addOption(new Option().setShortName("v").setLongName("invert-match").setFlag(true)) - .addOption(new Option().setShortName("n").setLongName("line-number").setFlag(true)) - .addOption(new Option().setShortName("B").setLongName("before-context").setSingleValued(true)) - .addOption(new Option().setShortName("A").setLongName("after-context").setSingleValued(true)) - .addOption(new Option().setShortName("C").setLongName("context").setSingleValued(true)) - .addOption(new Option().setShortName("e").setLongName("regexp").setFlag(true)) - .addOption(new Option().setShortName("f").setLongName("output").setSingleValued(true)) - .addOption(new Option().setShortName("m").setLongName("max-count").setSingleValued(true)) - .addArgument(new Argument().setArgName("keyword").setIndex(0)) - .parse(args); - Boolean ignoreCase = commandLine.isFlagEnabled("ignore-case"); - String keyword = commandLine.getArgumentValue(0); - final boolean invertMatch = commandLine.isFlagEnabled("invert-match"); - final boolean regexpMode = commandLine.isFlagEnabled("regexp"); - final boolean showLineNumber = commandLine.isFlagEnabled("line-number"); - int context = getInt(commandLine, "context", 0); - int beforeLines = getInt(commandLine, "before-context", 0); - int afterLines = getInt(commandLine, "after-context", 0); - if (context > 0) { - if (beforeLines < 1) { - beforeLines = context; - } - if (afterLines < 1 ){ - afterLines = context; - } + + GrepCommand grepCommand = new GrepCommand(); + CLI cli = CLIConfigurator.define(GrepCommand.class); + CommandLine commandLine = cli.parse(args); + + try { + CLIConfigurator.inject(commandLine, grepCommand); + } catch (Throwable e) { + throw new RuntimeException(e); } - final int maxCount = getInt(commandLine, "max-count", 0); - return new GrepHandler(keyword, ignoreCase, invertMatch, regexpMode, showLineNumber - , beforeLines, afterLines, maxCount); - } - - private static final int getInt(CommandLine cmdline, String name , int defaultValue) { - final String v = cmdline.getOptionValue(name); - final int ret = v== null ? defaultValue : Integer.parseInt(v); - return ret; + + int context = grepCommand.getContext(); + int beforeLines = grepCommand.getBeforeLines(); + int afterLines = grepCommand.getAfterLines(); + if (context > 0) { + if (beforeLines < 1) { + beforeLines = context; + } + if (afterLines < 1) { + afterLines = context; + } + } + return new GrepHandler(grepCommand.getPattern(), grepCommand.isIgnoreCase(), grepCommand.isInvertMatch(), + grepCommand.isRegEx(), grepCommand.isShowLineNumber(), grepCommand.isTrimEnd(), beforeLines, + afterLines, grepCommand.getMaxCount()); } - private GrepHandler(String keyword, boolean ignoreCase, boolean invertMatch, boolean regexpMode - , boolean showLineNumber, int beforeLines, int afterLines,int maxCount) { + private GrepHandler(String keyword, boolean ignoreCase, boolean invertMatch, boolean regexpMode, + boolean showLineNumber, boolean trimEnd, int beforeLines, int afterLines, int maxCount) { this.ignoreCase = ignoreCase; this.invertMatch = invertMatch; this.showLineNumber = showLineNumber; + this.trimEnd = trimEnd; this.beforeLines = beforeLines > 0 ? beforeLines : 0; this.afterLines = afterLines > 0 ? afterLines : 0; this.maxCount = maxCount > 0 ? maxCount : 0; if (regexpMode) { - final int flags = ignoreCase ? Pattern.CASE_INSENSITIVE : 0; - this.pattern = Pattern.compile(keyword, flags); + final int flags = ignoreCase ? Pattern.CASE_INSENSITIVE : 0; + this.pattern = Pattern.compile(keyword, flags); } else { - this.pattern = null; + this.pattern = null; } - this.keyword = ignoreCase ? keyword.toLowerCase() : keyword; + this.keyword = ignoreCase ? keyword.toLowerCase() : keyword; } @Override public String apply(String input) { StringBuilder output = new StringBuilder(); String[] lines = input.split("\n"); - int continueCount = 0 ; - int lastStartPos = 0 ; + int continueCount = 0; + int lastStartPos = 0; int lastContinueLineNum = -1; int matchCount = 0; - for (int lineNum = 0 ; lineNum < lines.length ;) { - String line = TRIM_PATTERN.matcher(lines[lineNum++]).replaceAll(""); - final boolean match; - if (pattern == null) { - match = (ignoreCase ? line.toLowerCase() : line).contains(keyword); - } else { - match = pattern.matcher(line).find(); - } - if (invertMatch ? !match : match) { - matchCount++; - if (beforeLines > continueCount) { - int n = lastContinueLineNum == -1 ? ( beforeLines >= lineNum ? 1 : lineNum - beforeLines ) - : lineNum - beforeLines - continueCount; - if ( n >= lastContinueLineNum || lastContinueLineNum == -1 ) { - StringBuilder beforeSb = new StringBuilder(); - for (int i = n ; i < lineNum ; i++) { - appendLine(beforeSb, i, lines[i - 1]); + for (int lineNum = 0; lineNum < lines.length;) { + String line = null; + if (this.trimEnd) { + line = StringUtils.stripEnd(lines[lineNum], null); + } else { + line = lines[lineNum]; + } + lineNum++; + + final boolean match; + if (pattern == null) { + match = (ignoreCase ? line.toLowerCase() : line).contains(keyword); + } else { + match = pattern.matcher(line).find(); + } + if (invertMatch ? !match : match) { + matchCount++; + if (beforeLines > continueCount) { + int n = lastContinueLineNum == -1 ? (beforeLines >= lineNum ? 1 : lineNum - beforeLines) + : lineNum - beforeLines - continueCount; + if (n >= lastContinueLineNum || lastContinueLineNum == -1) { + StringBuilder beforeSb = new StringBuilder(); + for (int i = n; i < lineNum; i++) { + appendLine(beforeSb, i, lines[i - 1]); + } + output.insert(lastStartPos, beforeSb); + } + } // end handle before lines + + lastStartPos = output.length(); + appendLine(output, lineNum, line); + + if (afterLines > continueCount) { + int last = lineNum + afterLines - continueCount; + if (last > lines.length) { + last = lines.length; + } + for (int i = lineNum; i < last; i++) { + appendLine(output, i + 1, lines[i]); + lineNum++; + continueCount++; + lastStartPos = output.length(); + } + } // end handle afterLines + + continueCount++; + if (maxCount > 0 && matchCount >= maxCount) { + break; + } + } else { // not match + if (continueCount > 0) { + lastContinueLineNum = lineNum - 1; + continueCount = 0; } - output.insert(lastStartPos, beforeSb); - } - } // end handle before lines - - lastStartPos = output.length(); - appendLine(output, lineNum, line); - - if (afterLines > continueCount) { - int last = lineNum + afterLines - continueCount; - if (last > lines.length) { - last = lines.length; - } - for(int i = lineNum ; i < last ; i++) { - appendLine(output, i+1, lines[i]); - lineNum ++; - continueCount++; - lastStartPos = output.length(); - } - } //end handle afterLines - - continueCount++; - if(maxCount > 0 && matchCount >= maxCount) { - break; } - } else { // not match - if(continueCount > 0) { - lastContinueLineNum = lineNum -1 ; - continueCount = 0; - } - } } - final String str = output.toString();// output.length() > 0 ? output.substring(0, output.length()-1) : ""; + final String str = output.toString(); return str; } protected void appendLine(StringBuilder output, int lineNum, String line) { - if(showLineNumber) { - output.append(lineNum).append(':'); + if (showLineNumber) { + output.append(lineNum).append(':'); } - output.append(line).append('\n'); + output.append(line).append('\n'); } }