diff --git a/Base/usr/share/man/man1/grep.md b/Base/usr/share/man/man1/grep.md index ad1ae076b9..2a8a4d291f 100644 --- a/Base/usr/share/man/man1/grep.md +++ b/Base/usr/share/man/man1/grep.md @@ -5,7 +5,7 @@ grep ## Synopsis ```sh -$ grep [--recursive] [--extended-regexp] [--regexp Pattern] [-i] [--invert-match] [--quiet] [--no-messages] [--binary-mode ] [--text] [-I] [--color WHEN] [file...] +$ grep [--recursive] [--extended-regexp] [--regexp Pattern] [-i] [--line-numbers] [--invert-match] [--quiet] [--no-messages] [--binary-mode ] [--text] [-I] [--color WHEN] [file...] ``` ## Options: @@ -16,6 +16,7 @@ $ grep [--recursive] [--extended-regexp] [--regexp Pattern] [-i] [--invert-match * `-E`, `--extended-regexp`: Extended regular expressions * `-e Pattern`, `--regexp Pattern`: Pattern * `-i`: Make matches case-insensitive +* `-n`, `--line-numbers`: Output line-numbers * `-v`, `--invert-match`: Select non-matching lines * `-q`, `--quiet`: Do not write anything to standard output * `-s`, `--no-messages`: Suppress error messages for nonexistent or unreadable files diff --git a/Userland/Utilities/grep.cpp b/Userland/Utilities/grep.cpp index 91de6dc2f0..7f67535e9e 100644 --- a/Userland/Utilities/grep.cpp +++ b/Userland/Utilities/grep.cpp @@ -46,6 +46,7 @@ int main(int argc, char** argv) const char* pattern = nullptr; BinaryFileMode binary_mode { BinaryFileMode::Binary }; bool case_insensitive = false; + bool line_numbers = false; bool invert_match = false; bool quiet_mode = false; bool suppress_errors = false; @@ -56,6 +57,7 @@ int main(int argc, char** argv) args_parser.add_option(use_ere, "Extended regular expressions", "extended-regexp", 'E'); args_parser.add_option(pattern, "Pattern", "regexp", 'e', "Pattern"); args_parser.add_option(case_insensitive, "Make matches case-insensitive", nullptr, 'i'); + args_parser.add_option(line_numbers, "Output line-numbers", "line-numbers", 'n'); args_parser.add_option(invert_match, "Select non-matching lines", "invert-match", 'v'); args_parser.add_option(quiet_mode, "Do not write anything to standard output", "quiet", 'q'); args_parser.add_option(suppress_errors, "Suppress error messages for nonexistent or unreadable files", "no-messages", 's'); @@ -129,7 +131,7 @@ int main(int argc, char** argv) return 1; } - auto matches = [&](StringView str, StringView filename, bool print_filename, bool is_binary) { + auto matches = [&](StringView str, StringView filename, size_t line_number, bool print_filename, bool is_binary) { size_t last_printed_char_pos { 0 }; if (is_binary && binary_mode == BinaryFileMode::Skip) return false; @@ -144,6 +146,8 @@ int main(int argc, char** argv) } else { if ((result.matches.size() || invert_match) && print_filename) out(colored_output ? "\x1B[34m{}:\x1B[0m" : "{}:", filename); + if ((result.matches.size() || invert_match) && line_numbers) + out(colored_output ? "\x1B[35m{}:\x1B[0m" : "{}:", line_number); for (auto& match : result.matches) { out(colored_output ? "{}\x1B[32m{}\x1B[0m" : "{}{}", @@ -168,11 +172,11 @@ int main(int argc, char** argv) return false; } - while (file->can_read_line()) { + for (size_t line_number = 1; file->can_read_line(); ++line_number) { auto line = file->read_line(); auto is_binary = memchr(line.characters(), 0, line.length()) != nullptr; - if (matches(line, filename, print_filename, is_binary) && is_binary && binary_mode == BinaryFileMode::Binary) + if (matches(line, filename, line_number, print_filename, is_binary) && is_binary && binary_mode == BinaryFileMode::Binary) return true; } return true; @@ -197,17 +201,20 @@ int main(int argc, char** argv) size_t line_len = 0; ssize_t nread = 0; ScopeGuard free_line = [line] { free(line); }; + size_t line_number = 0; while ((nread = getline(&line, &line_len, stdin)) != -1) { VERIFY(nread > 0); if (line[nread - 1] == '\n') --nread; + // Human-readable indexes start at 1, so it's fine to increment already. + line_number += 1; StringView line_view(line, nread); bool is_binary = line_view.contains(0); if (is_binary && binary_mode == BinaryFileMode::Skip) return 1; - auto matched = matches(line_view, "stdin", false, is_binary); + auto matched = matches(line_view, "stdin", line_number, false, is_binary); did_match_something = did_match_something || matched; if (matched && is_binary && binary_mode == BinaryFileMode::Binary) return 0;