mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 13:18:13 +00:00
Utilities: Add strings
This commit is contained in:
parent
e3112a3d2e
commit
8677dbfc7f
3 changed files with 180 additions and 1 deletions
39
Base/usr/share/man/man1/strings.md
Normal file
39
Base/usr/share/man/man1/strings.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
## Name
|
||||
|
||||
strings - find printable strings in files
|
||||
|
||||
## Synopsis
|
||||
|
||||
```**sh
|
||||
$ strings [-n NUMBER] [-p] [-t FORMAT] [PATHS...]
|
||||
```
|
||||
|
||||
## Description
|
||||
|
||||
`strings` looks for printable strings in each file specified in `PATHS` and writes them to standard output. If `PATHS` is not specified, input is read from standard input.
|
||||
|
||||
## Options
|
||||
|
||||
* `-n NUMBER`: Specify the minimum string length (4 is default).
|
||||
* `-p`: Write the pathname for each file specified in `PATHS` to standard output.
|
||||
* `-t FORMAT`: Write each string preceded by its byte offset from the start of the file in the specified `FORMAT`, where `FORMAT` matches one of the following: `d` (decimal), `o` (octal), or `x` (hexidecimal).
|
||||
|
||||
## Examples
|
||||
|
||||
Display the printable strings in /bin/strings with a minimum length of 8 characters:
|
||||
|
||||
```sh
|
||||
$ strings -n 8 /bin/strings
|
||||
```
|
||||
|
||||
Display the printable strings in a binary file, preceded by their byte offset in hexadecimal format:
|
||||
|
||||
```sh
|
||||
$ strings -t x ~/Videos/test.webm
|
||||
```
|
||||
|
||||
Display the printable strings in all .txt files in the current directory, preceded by their pathname:
|
||||
|
||||
```sh
|
||||
$ strings -p *.txt
|
||||
```
|
|
@ -8,7 +8,7 @@ list(APPEND REQUIRED_TARGETS
|
|||
)
|
||||
list(APPEND RECOMMENDED_TARGETS
|
||||
adjtime aplay abench asctl bt checksum chres cksum copy fortune gunzip gzip init install keymap lsirq lsof lspci man mknod mktemp
|
||||
nc netstat notify ntpquery open passwd pls printf pro shot tar tt unzip wallpaper zip
|
||||
nc netstat notify ntpquery open passwd pls printf pro shot strings tar tt unzip wallpaper zip
|
||||
)
|
||||
|
||||
# FIXME: Support specifying component dependencies for utilities (e.g. WebSocket for telws)
|
||||
|
|
140
Userland/Utilities/strings.cpp
Normal file
140
Userland/Utilities/strings.cpp
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright (c) 2022, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/CharacterTypes.h>
|
||||
#include <AK/Forward.h>
|
||||
#include <LibCore/ArgsParser.h>
|
||||
#include <LibCore/Stream.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibMain/Main.h>
|
||||
#include <unistd.h>
|
||||
|
||||
enum class StringOffsetFormat {
|
||||
None = 0,
|
||||
Decimal,
|
||||
Octal,
|
||||
Hexadecimal
|
||||
};
|
||||
|
||||
// NOTE: This is similar to how the cat utility works in the sense of aggregating
|
||||
// data in 32K buffer.
|
||||
static constexpr size_t buffer_read_size = 32768;
|
||||
|
||||
static bool should_print_characters(Vector<u8> const& characters)
|
||||
{
|
||||
for (u8 ch : characters) {
|
||||
if (is_ascii_printable(ch) && !is_ascii_space(ch))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void print_characters(Vector<u8> const& characters, StringOffsetFormat string_offset_format, size_t string_offset_position)
|
||||
{
|
||||
switch (string_offset_format) {
|
||||
case StringOffsetFormat::Decimal:
|
||||
out("{:>7d} ", string_offset_position);
|
||||
break;
|
||||
case StringOffsetFormat::Octal:
|
||||
out("{:>7o} ", string_offset_position);
|
||||
break;
|
||||
case StringOffsetFormat::Hexadecimal:
|
||||
out("{:>7x} ", string_offset_position);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
outln("{:s}", characters.span());
|
||||
}
|
||||
|
||||
static int process_characters_in_span(Vector<u8>& characters, ReadonlyBytes span)
|
||||
{
|
||||
int processed_characters = 0;
|
||||
for (u8 ch : span) {
|
||||
++processed_characters;
|
||||
if (is_ascii_printable(ch) || ch == '\t')
|
||||
characters.append(ch);
|
||||
else
|
||||
break;
|
||||
}
|
||||
return processed_characters;
|
||||
}
|
||||
|
||||
static ErrorOr<void> process_strings_in_file(StringView path, bool show_paths, StringOffsetFormat string_offset_format, size_t minimum_string_length)
|
||||
{
|
||||
Array<u8, buffer_read_size> buffer;
|
||||
Vector<u8> output_characters;
|
||||
auto file = TRY(Core::Stream::File::open_file_or_standard_stream(path, Core::Stream::OpenMode::Read));
|
||||
size_t processed_characters = 0;
|
||||
size_t string_offset_position = 0;
|
||||
bool did_show_path = false;
|
||||
while (!file->is_eof()) {
|
||||
auto buffer_span = TRY(file->read(buffer));
|
||||
while (!buffer_span.is_empty()) {
|
||||
string_offset_position += processed_characters;
|
||||
processed_characters = process_characters_in_span(output_characters, buffer_span);
|
||||
if (show_paths && !did_show_path) {
|
||||
outln("path {}:", path);
|
||||
did_show_path = true;
|
||||
}
|
||||
if (output_characters.size() >= minimum_string_length && should_print_characters(output_characters)) {
|
||||
print_characters(output_characters, string_offset_format, string_offset_position);
|
||||
}
|
||||
buffer_span = buffer_span.slice(processed_characters);
|
||||
output_characters.clear();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||
{
|
||||
TRY(Core::System::pledge("stdio rpath"));
|
||||
|
||||
Vector<StringView> paths;
|
||||
size_t minimum_string_length = 4;
|
||||
bool show_paths = false;
|
||||
|
||||
StringOffsetFormat string_offset_format { StringOffsetFormat::None };
|
||||
|
||||
Core::ArgsParser args_parser;
|
||||
args_parser.add_option(minimum_string_length, "Specify the minimum string length.", nullptr, 'n', "number");
|
||||
args_parser.add_option(show_paths, "Display the path for each matched file.", nullptr, 'p');
|
||||
args_parser.add_option({ Core::ArgsParser::OptionArgumentMode::Required,
|
||||
"Write offset relative to start of each file in (d)ec, (o)ct, or he(x) format.",
|
||||
nullptr,
|
||||
't',
|
||||
"format",
|
||||
[&string_offset_format](char const* s) {
|
||||
StringView value = { s, strlen(s) };
|
||||
if (value == "d") {
|
||||
string_offset_format = StringOffsetFormat::Decimal;
|
||||
} else if (value == "o") {
|
||||
string_offset_format = StringOffsetFormat::Octal;
|
||||
} else if (value == "x") {
|
||||
string_offset_format = StringOffsetFormat::Hexadecimal;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} });
|
||||
args_parser.set_general_help("Write the sequences of printable characters in files or pipes to stdout.");
|
||||
args_parser.add_positional_argument(paths, "File path", "path", Core::ArgsParser::Required::No);
|
||||
args_parser.parse(arguments);
|
||||
|
||||
if (minimum_string_length < 1) {
|
||||
warnln("Invalid minimum string length {}", minimum_string_length);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (paths.is_empty())
|
||||
paths.append("-"sv);
|
||||
|
||||
for (auto const& path : paths)
|
||||
TRY(process_strings_in_file(path, show_paths, string_offset_format, minimum_string_length));
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue