diff --git a/Base/usr/share/man/man1/grep.md b/Base/usr/share/man/man1/grep.md index c2152777db..ba55b02b08 100644 --- a/Base/usr/share/man/man1/grep.md +++ b/Base/usr/share/man/man1/grep.md @@ -5,13 +5,14 @@ grep ## Synopsis ```sh -$ grep [--recursive] [--extended-regexp] [--regexp Pattern] [-i] [--line-numbers] [--invert-match] [--quiet] [--no-messages] [--binary-mode ] [--text] [-I] [--color WHEN] [--count] [file...] +$ grep [--recursive] [--extended-regexp] [--fixed-strings] [--regexp Pattern] [-i] [--line-numbers] [--invert-match] [--quiet] [--no-messages] [--binary-mode ] [--text] [-I] [--color WHEN] [--count] [file...] ``` ## Options: * `-r`, `--recursive`: Recursively scan files * `-E`, `--extended-regexp`: Extended regular expressions +* `-F`, `--fixed-strings`: Treat pattern as a string, not a regexp * `-e Pattern`, `--regexp Pattern`: Pattern * `-i`: Make matches case-insensitive * `-n`, `--line-numbers`: Output line-numbers diff --git a/Userland/Utilities/CMakeLists.txt b/Userland/Utilities/CMakeLists.txt index fc131dc1d2..21e1d1534e 100644 --- a/Userland/Utilities/CMakeLists.txt +++ b/Userland/Utilities/CMakeLists.txt @@ -1,7 +1,7 @@ file(GLOB CMD_SOURCES CONFIGURE_DEPENDS "*.cpp") list(APPEND SPECIAL_TARGETS test install) list(APPEND REQUIRED_TARGETS - arp base64 basename cat chmod chown clear comm cp cut date dd df diff dirname dmesg du echo env expr false fgrep + arp base64 basename cat chmod chown clear comm cp cut date dd df diff dirname dmesg du echo env expr false file find grep groups head host hostname id ifconfig kill killall ln logout ls mkdir mount mv nproc pgrep pidof ping pkill pmap ps readlink realpath reboot rm rmdir route seq shutdown sleep sort stat stty su tail test touch tr true umount uname uniq uptime w wc which whoami xargs yes @@ -65,6 +65,7 @@ if (ENABLE_JAKT) endif() install(CODE "file(CREATE_LINK grep ${CMAKE_INSTALL_PREFIX}/bin/egrep SYMBOLIC)") +install(CODE "file(CREATE_LINK grep ${CMAKE_INSTALL_PREFIX}/bin/fgrep SYMBOLIC)") install(CODE "file(CREATE_LINK grep ${CMAKE_INSTALL_PREFIX}/bin/rgrep SYMBOLIC)") target_link_libraries(abench PRIVATE LibAudio) diff --git a/Userland/Utilities/fgrep.cpp b/Userland/Utilities/fgrep.cpp deleted file mode 100644 index 2866b548d8..0000000000 --- a/Userland/Utilities/fgrep.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include -#include - -ErrorOr serenity_main(Main::Arguments arguments) -{ - if (arguments.strings.size() < 2) { - warnln("usage: fgrep "); - return 1; - } - for (;;) { - char buffer[4096]; - fgets(buffer, sizeof(buffer), stdin); - auto str = StringView { buffer, strlen(buffer) }; - if (str.contains(arguments.strings[1])) - TRY(Core::System::write(1, str.bytes())); - if (feof(stdin)) - return 0; - VERIFY(str.to_deprecated_string().characters()); - } -} diff --git a/Userland/Utilities/grep.cpp b/Userland/Utilities/grep.cpp index 2eba86e520..33b88a66fc 100644 --- a/Userland/Utilities/grep.cpp +++ b/Userland/Utilities/grep.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,21 @@ void fail(StringView format, Ts... args) abort(); } +constexpr StringView ere_special_characters = ".^$*+?()[{\\|"sv; +constexpr StringView basic_special_characters = ".^$*[\\"sv; + +static DeprecatedString escape_characters(StringView string, StringView characters) +{ + StringBuilder builder; + for (auto ch : string) { + if (characters.contains(ch)) + builder.append('\\'); + + builder.append(ch); + } + return builder.to_deprecated_string(); +} + ErrorOr serenity_main(Main::Arguments args) { TRY(Core::System::pledge("stdio rpath")); @@ -43,6 +59,7 @@ ErrorOr serenity_main(Main::Arguments args) bool recursive = (program_name == "rgrep"sv); bool use_ere = (program_name == "egrep"sv); + bool fixed_strings = (program_name == "fgrep"sv); Vector patterns; BinaryFileMode binary_mode { BinaryFileMode::Binary }; bool case_insensitive = false; @@ -58,6 +75,7 @@ ErrorOr serenity_main(Main::Arguments args) Core::ArgsParser args_parser; args_parser.add_option(recursive, "Recursively scan files", "recursive", 'r'); args_parser.add_option(use_ere, "Extended regular expressions", "extended-regexp", 'E'); + args_parser.add_option(fixed_strings, "Treat pattern as a string, not a regexp", "fixed-strings", 'F'); args_parser.add_option(Core::ArgsParser::Option { .argument_mode = Core::ArgsParser::OptionArgumentMode::Required, .help_string = "Pattern", @@ -144,6 +162,7 @@ ErrorOr serenity_main(Main::Arguments args) auto grep_logic = [&](auto&& regular_expressions) { for (auto& re : regular_expressions) { if (re.parser_result.error != regex::Error::NoError) { + warnln("regex parse error: {}", regex::get_error_string(re.parser_result.error)); return 1; } } @@ -296,14 +315,17 @@ ErrorOr serenity_main(Main::Arguments args) if (use_ere) { Vector> regular_expressions; for (auto pattern : patterns) { - regular_expressions.append(Regex(pattern, options)); + auto escaped_pattern = (fixed_strings) ? escape_characters(pattern, ere_special_characters) : pattern; + regular_expressions.append(Regex(escaped_pattern, options)); } return grep_logic(regular_expressions); } Vector> regular_expressions; for (auto pattern : patterns) { - regular_expressions.append(Regex(pattern, options)); + auto escaped_pattern = (fixed_strings) ? escape_characters(pattern, basic_special_characters) : pattern; + dbgln("'{}'", escaped_pattern); + regular_expressions.append(Regex(escaped_pattern, options)); } return grep_logic(regular_expressions); }