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 1fc17cb8f32..11fcaf7fd22 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 @@ -104,6 +104,7 @@ private void initCommands(List disabledCommands) { commandClassList.add(PwdCommand.class); commandClassList.add(MBeanCommand.class); commandClassList.add(GrepCommand.class); + commandClassList.add(WordCountCommand.class); commandClassList.add(TeeCommand.class); commandClassList.add(ProfilerCommand.class); commandClassList.add(VmToolCommand.class); diff --git a/core/src/main/java/com/taobao/arthas/core/command/basic1000/WordCountCommand.java b/core/src/main/java/com/taobao/arthas/core/command/basic1000/WordCountCommand.java new file mode 100644 index 00000000000..5c1da46a16f --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/basic1000/WordCountCommand.java @@ -0,0 +1,38 @@ +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.Option; +import com.taobao.middleware.cli.annotations.Summary; + +/** + * @see com.taobao.arthas.core.shell.command.internal.WordCountHandler + */ +@Name("wc") +@Summary("Count lines in command output.") +@Description(Constants.EXAMPLE + + " thread | wc -l\n" + + " sysprop | wc --lines\n" + + Constants.WIKI + Constants.WIKI_HOME + "wc") +public class WordCountCommand extends AnnotatedCommand { + + private boolean lineMode; + + @Option(shortName = "l", longName = "lines", flag = true) + @Description("Count newline characters") + public void setLineMode(boolean lineMode) { + this.lineMode = lineMode; + } + + public boolean isLineMode() { + return lineMode; + } + + @Override + public void process(CommandProcess process) { + process.end(-1, "The wc command is only for pipes. See 'wc --help'\n"); + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/shell/command/internal/WordCountHandler.java b/core/src/main/java/com/taobao/arthas/core/shell/command/internal/WordCountHandler.java index 90742911beb..2bfae608dbe 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/command/internal/WordCountHandler.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/command/internal/WordCountHandler.java @@ -1,12 +1,13 @@ package com.taobao.arthas.core.shell.command.internal; +import com.taobao.arthas.core.command.basic1000.WordCountCommand; import com.taobao.arthas.core.shell.cli.CliToken; -import com.taobao.middleware.cli.CLIs; +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; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; /** * @author ralf0131 2017-02-23 23:28. @@ -15,16 +16,16 @@ public class WordCountHandler extends StdoutHandler implements StatisticsFunctio public static final String NAME = "wc"; + private static final CLI cli = CLIConfigurator.define(WordCountCommand.class, true); + private boolean lineMode; private String result = null; - private final AtomicInteger total = new AtomicInteger(0); + private final AtomicLong total = new AtomicLong(0); public static StdoutHandler inject(List tokens) { List args = StdoutHandler.parseArgs(tokens, NAME); - CommandLine commandLine = CLIs.create(NAME) - .addOption(new Option().setShortName("l").setFlag(true)) - .parse(args); + CommandLine commandLine = cli.parse(args); boolean lineMode = commandLine.isFlagEnabled("l"); return new WordCountHandler(lineMode); } @@ -39,8 +40,14 @@ public String apply(String input) { // TODO the default behavior should be equivalent to `wc -l -w -c` result = "wc currently only support wc -l!\n"; } else { - if (input != null && !"".equals(input.trim())) { - total.getAndAdd(input.split("\n").length); + if (input != null) { + int lines = 0; + for (int i = 0; i < input.length(); i++) { + if (input.charAt(i) == '\n') { + lines++; + } + } + total.addAndGet(lines); } } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/impl/BuiltinCommandResolver.java b/core/src/main/java/com/taobao/arthas/core/shell/impl/BuiltinCommandResolver.java index 120773843d5..d666f2d91f9 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/impl/BuiltinCommandResolver.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/impl/BuiltinCommandResolver.java @@ -7,7 +7,6 @@ import com.taobao.arthas.core.shell.command.ShellInternalCommandResolver; import com.taobao.arthas.core.shell.command.internal.GrepHandler; import com.taobao.arthas.core.shell.command.internal.PlainTextHandler; -import com.taobao.arthas.core.shell.command.internal.WordCountHandler; import com.taobao.arthas.core.shell.handlers.Handler; import com.taobao.arthas.core.shell.handlers.NoOpHandler; @@ -34,7 +33,6 @@ public List commands() { CommandBuilder.command("bg").processHandler(handler).build(), CommandBuilder.command("kill").processHandler(handler).build(), CommandBuilder.command(PlainTextHandler.NAME).processHandler(handler).build(), - CommandBuilder.command(GrepHandler.NAME).processHandler(handler).build(), - CommandBuilder.command(WordCountHandler.NAME).processHandler(handler).build()); + CommandBuilder.command(GrepHandler.NAME).processHandler(handler).build()); } } diff --git a/core/src/test/java/com/taobao/arthas/core/shell/command/internal/WordCountHandlerTest.java b/core/src/test/java/com/taobao/arthas/core/shell/command/internal/WordCountHandlerTest.java new file mode 100644 index 00000000000..66a8b2d26d6 --- /dev/null +++ b/core/src/test/java/com/taobao/arthas/core/shell/command/internal/WordCountHandlerTest.java @@ -0,0 +1,139 @@ +package com.taobao.arthas.core.shell.command.internal; + +import com.taobao.arthas.core.shell.cli.CliTokens; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +public class WordCountHandlerTest { + + @Test + public void testShortAndLongOptionsCountLines() { + for (String option : new String[] { "-l", "--lines" }) { + StdoutHandler handler = StdoutHandler.inject(CliTokens.tokenize("wc " + option)); + + Assert.assertTrue(handler instanceof StatisticsFunction); + Assert.assertNull(handler.apply("first\nsecond\n")); + Assert.assertNull(handler.apply("third\n")); + Assert.assertEquals("3\n", ((StatisticsFunction) handler).result()); + } + } + + @Test + public void testEmptyInput() { + StdoutHandler handler = WordCountHandler.inject(CliTokens.tokenize("wc -l")); + + Assert.assertNull(handler.apply(null)); + Assert.assertNull(handler.apply("")); + Assert.assertEquals("0\n", ((StatisticsFunction) handler).result()); + } + + @Test + public void testCountsBlankAndWhitespaceOnlyLines() { + StdoutHandler handler = WordCountHandler.inject(CliTokens.tokenize("wc -l")); + + handler.apply("\n"); + handler.apply(" \n\t\n"); + + Assert.assertEquals("3\n", ((StatisticsFunction) handler).result()); + } + + @Test + public void testCountsTrailingBlankLines() { + StdoutHandler handler = WordCountHandler.inject(CliTokens.tokenize("wc -l")); + + handler.apply("first\n\n\n"); + + Assert.assertEquals("3\n", ((StatisticsFunction) handler).result()); + } + + @Test + public void testDoesNotCountUnterminatedText() { + StdoutHandler handler = WordCountHandler.inject(CliTokens.tokenize("wc -l")); + + handler.apply("first"); + Assert.assertEquals("0\n", ((StatisticsFunction) handler).result()); + handler.apply("\nsecond"); + Assert.assertEquals("1\n", ((StatisticsFunction) handler).result()); + } + + @Test + public void testCountDoesNotDependOnWriteBoundaries() { + String output = "first\r\n\nsecond\nlast"; + for (int split = 0; split <= output.length(); split++) { + StdoutHandler handler = WordCountHandler.inject(CliTokens.tokenize("wc --lines")); + handler.apply(output.substring(0, split)); + handler.apply(output.substring(split)); + + Assert.assertEquals("split at " + split, "3\n", ((StatisticsFunction) handler).result()); + } + + StdoutHandler handler = WordCountHandler.inject(CliTokens.tokenize("wc -l")); + for (int i = 0; i < output.length(); i++) { + handler.apply(output.substring(i, i + 1)); + } + Assert.assertEquals("3\n", ((StatisticsFunction) handler).result()); + } + + @Test + public void testCarriageReturnDoesNotAddALine() { + StdoutHandler handler = WordCountHandler.inject(CliTokens.tokenize("wc -l")); + + handler.apply("first\rsecond\r"); + + Assert.assertEquals("0\n", ((StatisticsFunction) handler).result()); + } + + @Test + public void testConcurrentWritesPreserveTheTotal() throws Exception { + final StdoutHandler handler = WordCountHandler.inject(CliTokens.tokenize("wc -l")); + ExecutorService executor = Executors.newFixedThreadPool(4); + try { + List> writes = new ArrayList>(); + for (int thread = 0; thread < 4; thread++) { + writes.add(executor.submit(new Callable() { + @Override + public Void call() { + for (int i = 0; i < 1000; i++) { + handler.apply("first\n\n"); + } + return null; + } + })); + } + for (Future write : writes) { + write.get(5, TimeUnit.SECONDS); + } + Assert.assertEquals("8000\n", ((StatisticsFunction) handler).result()); + } finally { + executor.shutdownNow(); + } + } + + @Test + public void testLineModeMustBeRequested() { + StdoutHandler handler = WordCountHandler.inject(CliTokens.tokenize("wc")); + + Assert.assertNull(handler.apply("first\n")); + Assert.assertEquals("wc currently only support wc -l!\n", ((StatisticsFunction) handler).result()); + } + + @Test + public void testCountersAreIndependent() { + StdoutHandler first = WordCountHandler.inject(CliTokens.tokenize("wc -l")); + StdoutHandler second = WordCountHandler.inject(CliTokens.tokenize("wc --lines")); + + first.apply("first\nsecond\n"); + second.apply("third\n"); + + Assert.assertEquals("2\n", ((StatisticsFunction) first).result()); + Assert.assertEquals("1\n", ((StatisticsFunction) second).result()); + } +} diff --git a/core/src/test/java/com/taobao/arthas/core/shell/impl/WordCountCommandTest.java b/core/src/test/java/com/taobao/arthas/core/shell/impl/WordCountCommandTest.java new file mode 100644 index 00000000000..c80b2455abd --- /dev/null +++ b/core/src/test/java/com/taobao/arthas/core/shell/impl/WordCountCommandTest.java @@ -0,0 +1,112 @@ +package com.taobao.arthas.core.shell.impl; + +import com.taobao.arthas.core.command.BuiltinCommandPack; +import com.taobao.arthas.core.command.basic1000.HelpCommand; +import com.taobao.arthas.core.command.basic1000.WordCountCommand; +import com.taobao.arthas.core.command.model.CommandOptionVO; +import com.taobao.arthas.core.command.model.CommandVO; +import com.taobao.arthas.core.command.model.HelpModel; +import com.taobao.arthas.core.shell.command.Command; +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.arthas.core.shell.command.CommandResolver; +import com.taobao.arthas.core.shell.session.Session; +import com.taobao.arthas.core.shell.system.impl.InternalCommandManager; +import com.taobao.middleware.cli.CommandLine; +import com.taobao.middleware.cli.annotations.CLIConfigurator; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class WordCountCommandTest { + + private List resolvers; + private Command command; + + @Before + public void setUp() { + resolvers = Arrays.asList(new BuiltinCommandResolver(), + new BuiltinCommandPack(Collections.emptyList())); + command = new InternalCommandManager(resolvers).getCommand("wc"); + Assert.assertNotNull(command); + } + + @Test + public void testHelpListsWcOnce() { + HelpModel help = help(null); + int count = 0; + for (CommandVO entry : help.getCommands()) { + if ("wc".equals(entry.getName())) { + Assert.assertNotNull(entry.getSummary()); + count++; + } + } + Assert.assertEquals(1, count); + } + + @Test + public void testHelpWcDescribesOptionsAndExamples() { + CommandVO detail = help("wc").getDetailCommand(); + + Assert.assertEquals("wc", detail.getName()); + Assert.assertNotNull(detail.cli()); + Assert.assertTrue(detail.getDescription().contains("thread | wc -l")); + Assert.assertTrue(detail.getDescription().contains("https://arthas.aliyun.com/doc/wc")); + Assert.assertTrue(detail.getUsage().contains("-l")); + Assert.assertTrue(detail.getUsage().contains("-h")); + boolean linesFound = false; + boolean helpFound = false; + for (CommandOptionVO option : detail.getOptions()) { + if ("l".equals(option.getShortName())) { + Assert.assertEquals("lines", option.getLongName()); + linesFound = true; + } + if ("h".equals(option.getShortName())) { + Assert.assertEquals("help", option.getLongName()); + helpFound = true; + } + } + Assert.assertTrue(linesFound); + Assert.assertTrue(helpFound); + } + + @Test + public void testLineOptionsUseTheSameCommandDefinition() { + for (String option : new String[] { "-l", "--lines" }) { + WordCountCommand instance = new WordCountCommand(); + CommandLine commandLine = command.cli().parse(Collections.singletonList(option)); + CLIConfigurator.inject(commandLine, instance); + Assert.assertTrue(instance.isLineMode()); + } + } + + @Test + public void testStandaloneCommandExplainsPipeUsage() { + CommandProcess process = Mockito.mock(CommandProcess.class); + + new WordCountCommand().process(process); + + Mockito.verify(process).end(-1, "The wc command is only for pipes. See 'wc --help'\n"); + } + + private HelpModel help(String name) { + Session session = Mockito.mock(Session.class); + Mockito.when(session.getCommandResolvers()).thenReturn(resolvers); + CommandProcess process = Mockito.mock(CommandProcess.class); + Mockito.when(process.session()).thenReturn(session); + HelpCommand help = new HelpCommand(); + help.setCmd(name); + + help.process(process); + + ArgumentCaptor result = ArgumentCaptor.forClass(HelpModel.class); + Mockito.verify(process).appendResult(result.capture()); + Mockito.verify(process).end(); + return result.getValue(); + } +} diff --git a/core/src/test/java/com/taobao/arthas/core/shell/system/impl/WordCountProcessTest.java b/core/src/test/java/com/taobao/arthas/core/shell/system/impl/WordCountProcessTest.java new file mode 100644 index 00000000000..79c85f6561d --- /dev/null +++ b/core/src/test/java/com/taobao/arthas/core/shell/system/impl/WordCountProcessTest.java @@ -0,0 +1,56 @@ +package com.taobao.arthas.core.shell.system.impl; + +import com.taobao.arthas.core.command.BuiltinCommandPack; +import com.taobao.arthas.core.command.model.HelpModel; +import com.taobao.arthas.core.command.model.ResultModel; +import com.taobao.arthas.core.command.model.StatusModel; +import com.taobao.arthas.core.distribution.ResultDistributor; +import com.taobao.arthas.core.shell.cli.CliTokens; +import com.taobao.arthas.core.shell.command.Command; +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.arthas.core.shell.handlers.Handler; +import com.taobao.arthas.core.shell.system.ExecStatus; +import com.taobao.arthas.core.shell.term.Tty; +import io.termd.core.function.Function; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +import java.util.Collections; + +public class WordCountProcessTest { + + @Test + public void testHelpFlagsDisplayHelpWithoutExecutingTheCommand() { + Command command = new InternalCommandManager(new BuiltinCommandPack(Collections.emptyList())) + .getCommand("wc"); + Assert.assertNotNull(command); + + for (String option : new String[] { "-h", "--help" }) { + Tty tty = Mockito.mock(Tty.class); + ResultDistributor distributor = Mockito.mock(ResultDistributor.class); + Handler handler = new Handler() { + @Override + public void handle(CommandProcess process) { + Assert.fail("Help must not execute the pipe-only command"); + } + }; + ProcessImpl process = new ProcessImpl(command, CliTokens.tokenize(option), handler, + new ProcessImpl.ProcessOutput(Collections.>emptyList(), null, tty), + distributor); + process.setTty(tty); + + process.run(); + + ArgumentCaptor result = ArgumentCaptor.forClass(ResultModel.class); + Mockito.verify(distributor, Mockito.times(2)).appendResult(result.capture()); + Assert.assertTrue(result.getAllValues().get(0) instanceof HelpModel); + HelpModel help = (HelpModel) result.getAllValues().get(0); + Assert.assertEquals("wc", help.getDetailCommand().getName()); + Assert.assertTrue(help.getDetailCommand().getUsage().contains("-l")); + Assert.assertTrue(result.getAllValues().get(1) instanceof StatusModel); + Assert.assertEquals(ExecStatus.TERMINATED, process.status()); + } + } +} diff --git a/site/docs/.vuepress/configs/sidebar/en.js b/site/docs/.vuepress/configs/sidebar/en.js index 20d3fd26981..1240b6d54b7 100644 --- a/site/docs/.vuepress/configs/sidebar/en.js +++ b/site/docs/.vuepress/configs/sidebar/en.js @@ -62,6 +62,7 @@ export const sidebarEN = { "/en/doc/vmoption.md", "/en/doc/vmtool.md", "/en/doc/watch.md", + "/en/doc/wc.md", ], }, { diff --git a/site/docs/.vuepress/configs/sidebar/zh.js b/site/docs/.vuepress/configs/sidebar/zh.js index 9cd3183d3d5..5c58e82ac26 100644 --- a/site/docs/.vuepress/configs/sidebar/zh.js +++ b/site/docs/.vuepress/configs/sidebar/zh.js @@ -62,6 +62,7 @@ export const sidebarZH = { "/doc/vmoption.md", "/doc/vmtool.md", "/doc/watch.md", + "/doc/wc.md", ], }, { diff --git a/site/docs/doc/commands.md b/site/docs/doc/commands.md index 4191f5488c6..281f162c9ff 100644 --- a/site/docs/doc/commands.md +++ b/site/docs/doc/commands.md @@ -65,7 +65,7 @@ Arthas 支持使用管道对上述命令的结果进行进一步的处理,如` - [grep](grep.md) - 搜索满足条件的结果 - plaintext - 将命令的结果去除 ANSI 颜色 -- wc - 按行统计输出结果 +- [wc](wc.md) - 按行统计输出结果 ## 后台异步任务 diff --git a/site/docs/doc/help.md b/site/docs/doc/help.md index f86689e0cac..8e51583785a 100644 --- a/site/docs/doc/help.md +++ b/site/docs/doc/help.md @@ -57,6 +57,7 @@ $ help pwd Return working directory name mbean Display the mbean information grep grep command for pipes. + wc Count lines in command output. tee tee command for pipes. profiler Async Profiler. https://github.com/jvm-profiling-tools/async-profiler stop Stop/Shutdown Arthas server and exit the console. diff --git a/site/docs/doc/wc.md b/site/docs/doc/wc.md new file mode 100644 index 00000000000..b9244b4dd86 --- /dev/null +++ b/site/docs/doc/wc.md @@ -0,0 +1,55 @@ +# wc + +通过管道统计命令输出的行数。目前只支持行数统计,不支持单词数、字节数统计或直接读取文件。 + +## 参数说明 + +| 参数 | 说明 | +| --------------- | ---------------------------------------- | +| `-l`、`--lines` | 统计换行符数量,使用管道时需要指定 | +| `-h`、`--help` | 单独执行 `wc -h` 或 `wc --help` 查看帮助 | + +## 使用示例 + +统计输出行数: + +```text +$ echo 'hello' | wc -l +1 +``` + +使用长参数,统计过滤后的输出: + +```bash +sysprop | grep java | wc --lines +``` + +统计线程命令的输出: + +```bash +thread | wc -l +``` + +`wc` 统计的是文本输出,表头和分隔线也会影响结果,因此结果不等于线程数、类数量或方法调用次数。统计结果在上游命令结束后输出;对于 `watch` 等持续输出的命令,可以用上游命令的 `-n` 参数限制执行次数。 + +## 计数规则 + +`-l` 和 `--lines` 按 `\n` 换行符累计计数,与系统的 `wc -l` 一致。空行计入总数,CRLF 按一个换行计算,末尾没有换行的文本不额外计数。同一行分多次输出不会重复计数。 + +| 输出内容 | 计数 | +| ----------- | ---- | +| `hello` | 0 | +| `hello\n` | 1 | +| `hello\n\n` | 2 | +| `\n` | 1 | + +## 查看帮助 + +查看帮助时,单独执行以下命令,不放在管道右侧: + +```bash +help wc +wc --help +``` + +单独执行 `wc` 或 `wc -l` 不会读取标准输入,而是提示使用管道。 diff --git a/site/docs/en/doc/commands.md b/site/docs/en/doc/commands.md index 2b24da87f24..a97a9e4aea0 100644 --- a/site/docs/en/doc/commands.md +++ b/site/docs/en/doc/commands.md @@ -65,7 +65,7 @@ Arthas provides `pipe` to process the result returned from commands further, e.g - [grep](grep.md)- filter the result with the given keyword - plaintext - remove the ANSI color -- wc - count lines +- [wc](wc.md) - count lines ## async jobs diff --git a/site/docs/en/doc/help.md b/site/docs/en/doc/help.md index 99bf0eda931..ae41c27ea2a 100644 --- a/site/docs/en/doc/help.md +++ b/site/docs/en/doc/help.md @@ -57,6 +57,7 @@ $ help pwd Return working directory name mbean Display the mbean information grep grep command for pipes. + wc Count lines in command output. tee tee command for pipes. profiler Async Profiler. https://github.com/jvm-profiling-tools/async-profiler stop Stop/Shutdown Arthas server and exit the console. diff --git a/site/docs/en/doc/wc.md b/site/docs/en/doc/wc.md new file mode 100644 index 00000000000..efdbd59c09d --- /dev/null +++ b/site/docs/en/doc/wc.md @@ -0,0 +1,55 @@ +# wc + +Count lines in command output through a pipe. Only line counting is supported; word counts, byte counts, and reading files directly are not supported. + +## Options + +| Option | Description | +| --------------- | ------------------------------------------------------ | +| `-l`, `--lines` | Count newline characters; required when using a pipe | +| `-h`, `--help` | Show help by running `wc -h` or `wc --help` on its own | + +## Examples + +Count output lines: + +```text +$ echo 'hello' | wc -l +1 +``` + +Use the long option to count filtered output: + +```bash +sysprop | grep java | wc --lines +``` + +Count the output of the thread command: + +```bash +thread | wc -l +``` + +`wc` counts text output, including headers and separators, so the result is not a count of threads, classes, or method calls. The result is printed when the upstream command finishes. For commands that keep producing output, such as `watch`, use the upstream command's `-n` option to limit the number of executions. + +## Counting rules + +`-l` and `--lines` count `\n` newline characters, matching the system `wc -l` command. Blank lines count, CRLF counts as one newline, and text without a final newline does not add another line. Splitting a line across multiple writes does not change the result. + +| Output | Count | +| ----------- | ----- | +| `hello` | 0 | +| `hello\n` | 1 | +| `hello\n\n` | 2 | +| `\n` | 1 | + +## Help + +Run either command on its own to view help, rather than on the right side of a pipe: + +```bash +help wc +wc --help +``` + +Running `wc` or `wc -l` on its own does not read standard input; it displays a message explaining that a pipe is required.