diff --git a/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java b/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java index 0268dde45..a1a01a62f 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java +++ b/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java @@ -2,6 +2,7 @@ package com.taobao.arthas.core.command; import com.taobao.arthas.core.command.basic1000.CatCommand; import com.taobao.arthas.core.command.basic1000.ClsCommand; +import com.taobao.arthas.core.command.basic1000.GrepCommand; import com.taobao.arthas.core.command.basic1000.HelpCommand; import com.taobao.arthas.core.command.basic1000.HistoryCommand; import com.taobao.arthas.core.command.basic1000.KeymapCommand; @@ -99,5 +100,6 @@ public class BuiltinCommandPack implements CommandResolver { commands.add(Command.create(CatCommand.class)); commands.add(Command.create(PwdCommand.class)); commands.add(Command.create(MBeanCommand.class)); + commands.add(Command.create(GrepCommand.class)); } } 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 new file mode 100644 index 000000000..319c3150b --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/basic1000/GrepCommand.java @@ -0,0 +1,41 @@ +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.Description; +import com.taobao.middleware.cli.annotations.Name; +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" ) +@Description(Constants.EXAMPLE + + "sysprop | grep java \n" + + " sysenv | grep -v JAVA -n\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") +public class GrepCommand extends AnnotatedCommand { + @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.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 460bd25b7..88f0e8f90 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 @@ -1,52 +1,167 @@ package com.taobao.arthas.core.shell.command.internal; +import java.util.List; +import java.util.regex.Pattern; import com.taobao.arthas.core.shell.cli.CliToken; import com.taobao.middleware.cli.Argument; import com.taobao.middleware.cli.CLIs; import com.taobao.middleware.cli.CommandLine; import com.taobao.middleware.cli.Option; -import java.util.List; - /** * @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 + private final boolean invertMatch; + //-e, --regexp=PATTERN use PATTERN for matching + private final Pattern pattern; + // -n, --line-number 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; + 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); - return new GrepHandler(keyword, ignoreCase); + 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; + } + } + 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; } - private GrepHandler(String keyword, boolean ignoreCase) { - this.keyword = keyword; + private GrepHandler(String keyword, boolean ignoreCase, boolean invertMatch, boolean regexpMode + , boolean showLineNumber, int beforeLines, int afterLines,int maxCount) { this.ignoreCase = ignoreCase; + this.invertMatch = invertMatch; + this.showLineNumber = showLineNumber; + 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); + } else { + this.pattern = null; + } + this.keyword = ignoreCase ? keyword.toLowerCase() : keyword; } @Override public String apply(String input) { StringBuilder output = new StringBuilder(); String[] lines = input.split("\n"); - for (String line : lines) { - if (ignoreCase) { - line = line.toLowerCase(); - keyword = keyword.toLowerCase(); - } + 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]); + } + output.insert(lastStartPos, beforeSb); + } + } // end handle before lines - if (line.contains(keyword)) { - output.append(line).append("\n"); + 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) : ""; + return str; + } + + protected void appendLine(StringBuilder output, int lineNum, String line) { + if(showLineNumber) { + output.append(lineNum).append(':'); } - return output.toString(); + output.append(line).append('\n'); } } diff --git a/core/src/test/java/com/taobao/arthas/core/shell/command/interna/GrepHandlerTest.java b/core/src/test/java/com/taobao/arthas/core/shell/command/interna/GrepHandlerTest.java new file mode 100644 index 000000000..1b298ba3d --- /dev/null +++ b/core/src/test/java/com/taobao/arthas/core/shell/command/interna/GrepHandlerTest.java @@ -0,0 +1,160 @@ +package com.taobao.arthas.core.shell.command.interna; + +import java.io.File; +import java.lang.reflect.Constructor; +import java.nio.charset.Charset; +import org.junit.Assert; +import org.junit.Test; +import com.taobao.arthas.core.shell.command.internal.GrepHandler; +import com.taobao.arthas.core.util.FileUtils; + +public class GrepHandlerTest { + + private static final class Hold { + static final Constructor constructor; + static { + constructor = GrepHandler.class.getDeclaredConstructors()[0]; + constructor.setAccessible(true); + } + public static GrepHandler createInst(String keyword, boolean ignoreCase, boolean invertMatch, boolean regexpMode + , boolean showLineNumber, int beforeLines, int afterLines, String output) { + return createInst(keyword, ignoreCase, invertMatch, regexpMode, showLineNumber, beforeLines, afterLines, output, 0); + } + //new GrepHandler(keyword, ignoreCase, invertMatch, regexpMode, showLineNumber, beforeLines, afterLines,output) + public static GrepHandler createInst(String keyword, boolean ignoreCase, boolean invertMatch, boolean regexpMode + , boolean showLineNumber, int beforeLines, int afterLines, String output, int maxCount) { + try { + final Object[] initargs = new Object[] { keyword,ignoreCase,invertMatch,regexpMode,showLineNumber + ,beforeLines,afterLines,maxCount + }; + GrepHandler handler = (GrepHandler)constructor.newInstance(initargs); + return handler; + }catch(RuntimeException ex) { + throw ex; + }catch(Exception ex) { + throw new IllegalStateException(ex); + } + } + } + @Test + public void test4grep_ABC() { //-A -B -C + Object[][] samples = new Object[][] { + {"ABC\n1\n2\n3\n4\nc", "ABC", 0, 4, "ABC\n1\n2\n3\n4"}, + {"ABC\n1\n2\n3\n4\nABC\n5", "ABC", 2, 1, "ABC\n1\n3\n4\nABC\n5"}, + {"ABC\n1\n2\n3\n4\na", "ABC", 2, 1, "ABC\n1"}, + {"ABC\n1\n2\n3\n4\nb", "ABC", 0, 0, "ABC"}, + {"ABC\n1\n2\n3\n4\nc", "ABC", 0, 5, "ABC\n1\n2\n3\n4\nc"}, + {"ABC\n1\n2\n3\n4\nc", "ABC", 0, 10, "ABC\n1\n2\n3\n4\nc"}, + {"ABC\n1\n2\n3\n4\nc", "ABC", 0, 2, "ABC\n1\n2"}, + {"1\n2\n3\n4\nABC", "ABC", 5, 1, "1\n2\n3\n4\nABC"}, + {"1\n2\n3\n4\nABC", "ABC", 4, 1, "1\n2\n3\n4\nABC"}, + {"1\n2\n3\n4\nABC", "ABC", 2, 1, "3\n4\nABC"} + }; + + for(Object[] args : samples) { + String word = (String)args[1]; + int beforeLines = (Integer)args[2]; + int afterLines = (Integer)args[3]; + GrepHandler handler = Hold.createInst(word,false,false,true,false,beforeLines,afterLines,null); + String input = (String)args[0]; + final String ret = handler.apply(input); + final String expected = (String)args[4]; + Assert.assertEquals(expected, ret.substring(0,ret.length()-1)); + } + } + @Test + public void test4grep_v() {//-v + Object[][] samples = new Object[][] { + {"ABC\n1\n2\nc", "ABC", 0, 4, "1\n2\nc"}, + {"ABC\n1\n2\n", "ABC", 0, 0, "1\n2"}, + {"ABC\n1\n2\nc", "ABC", 0, 1, "1\n2\nc"} + }; + + for(Object[] args : samples) { + String word = (String)args[1]; + int beforeLines = (Integer)args[2]; + int afterLines = (Integer)args[3]; + GrepHandler handler = Hold.createInst(word,false,true,true,false,beforeLines,afterLines,null); + String input = (String)args[0]; + final String ret = handler.apply(input); + final String expected = (String)args[4]; + Assert.assertEquals(expected, ret.substring(0,ret.length()-1)); + } + } + + @Test + public void test4grep_e() {//-e + Object[][] samples = new Object[][] { + {"java\n1python\n2\nc", "java|python", "java\n1python"}, + {"java\n1python\n2\nc", "ja|py", "java\n1python"} + }; + + for(Object[] args : samples) { + String word = (String)args[1]; + GrepHandler handler = Hold.createInst(word,false,false,true,false,0,0,null); + String input = (String)args[0]; + final String ret = handler.apply(input); + final String expected = (String)args[2]; + Assert.assertEquals(expected, ret.substring(0,ret.length()-1)); + } + } + + @Test + public void test4grep_m() {//-e + Object[][] samples = new Object[][] { + {"java\n1python\n2\nc", "java|python", "java", 1}, + {"java\n1python\n2\nc", "ja|py", "java\n1python",2}, + {"java\n1python\n2\nc", "ja|py", "java\n1python",3} + }; + + for(Object[] args : samples) { + String word = (String)args[1]; + int maxCount = args.length > 3 ? (Integer)args[3] : 0; + GrepHandler handler = Hold.createInst(word,false,false,true,false,0,0,null, maxCount); + String input = (String)args[0]; + final String ret = handler.apply(input); + final String expected = (String)args[2]; + Assert.assertEquals(expected, ret.substring(0,ret.length()-1)); + } + } + + @Test + public void test4grep_n() {//-n + Object[][] samples = new Object[][] { + {"java\n1\npython\n2\nc","1:java\n3:python", "java|python" }, + {"java\n1\npython\njava\nc","1:java\n4:java", "java",false } + }; + + for(Object[] args : samples) { + String word = (String)args[2]; + boolean regexpMode = args.length >3 ? (Boolean)args[3] : true; + GrepHandler handler = Hold.createInst(word,false,false,regexpMode,true,0,0,null); + String input = (String)args[0]; + final String ret = handler.apply(input); + final String expected = (String)args[1]; + Assert.assertEquals(expected, ret.substring(0,ret.length()-1)); + } + } + +// @Test +// public void test4grep_f() throws Exception {//-f +// Object[][] samples = new Object[][] { +// {"java\n1\npython\n2\nc","1:java\n3:python", "java|python" }, +// {"java\n1\npython\njava\nc","1:java\n4:java", "java",false } +// }; +// String output = File.createTempFile("arthas_test", ".log").getAbsolutePath(); +// final Charset charset = Charset.forName("UTF-8"); +// for(Object[] args : samples) { +// String word = (String)args[2]; +// boolean regexpMode = args.length >3 ? (Boolean)args[3] : true; +// GrepHandler handler = Hold.createInst(word,false,false,regexpMode,true,0,0,output+":false"); +// String input = (String)args[0]; +// final String ret = handler.apply(input); +// final String expected = (String)args[1]; +// Assert.assertEquals(expected, ret.substring(0,ret.length()-1)); +// final String ret2 = FileUtils.readFileToString(new File(output), charset); +// Assert.assertEquals(expected, ret.substring(0,ret2.length()-1)); +// } +// } + +}