Skip to content

Commit 8677dbf

Browse files
alec8023linusg
authored andcommitted
Utilities: Add strings
1 parent e3112a3 commit 8677dbf

File tree

3 files changed

+180
-1
lines changed

3 files changed

+180
-1
lines changed

Base/usr/share/man/man1/strings.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
## Name
2+
3+
strings - find printable strings in files
4+
5+
## Synopsis
6+
7+
```**sh
8+
$ strings [-n NUMBER] [-p] [-t FORMAT] [PATHS...]
9+
```
10+
11+
## Description
12+
13+
`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.
14+
15+
## Options
16+
17+
* `-n NUMBER`: Specify the minimum string length (4 is default).
18+
* `-p`: Write the pathname for each file specified in `PATHS` to standard output.
19+
* `-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).
20+
21+
## Examples
22+
23+
Display the printable strings in /bin/strings with a minimum length of 8 characters:
24+
25+
```sh
26+
$ strings -n 8 /bin/strings
27+
```
28+
29+
Display the printable strings in a binary file, preceded by their byte offset in hexadecimal format:
30+
31+
```sh
32+
$ strings -t x ~/Videos/test.webm
33+
```
34+
35+
Display the printable strings in all .txt files in the current directory, preceded by their pathname:
36+
37+
```sh
38+
$ strings -p *.txt
39+
```

Userland/Utilities/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ list(APPEND REQUIRED_TARGETS
88
)
99
list(APPEND RECOMMENDED_TARGETS
1010
adjtime aplay abench asctl bt checksum chres cksum copy fortune gunzip gzip init install keymap lsirq lsof lspci man mknod mktemp
11-
nc netstat notify ntpquery open passwd pls printf pro shot tar tt unzip wallpaper zip
11+
nc netstat notify ntpquery open passwd pls printf pro shot strings tar tt unzip wallpaper zip
1212
)
1313

1414
# FIXME: Support specifying component dependencies for utilities (e.g. WebSocket for telws)

Userland/Utilities/strings.cpp

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Copyright (c) 2022, the SerenityOS developers.
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#include <AK/CharacterTypes.h>
8+
#include <AK/Forward.h>
9+
#include <LibCore/ArgsParser.h>
10+
#include <LibCore/Stream.h>
11+
#include <LibCore/System.h>
12+
#include <LibMain/Main.h>
13+
#include <unistd.h>
14+
15+
enum class StringOffsetFormat {
16+
None = 0,
17+
Decimal,
18+
Octal,
19+
Hexadecimal
20+
};
21+
22+
// NOTE: This is similar to how the cat utility works in the sense of aggregating
23+
// data in 32K buffer.
24+
static constexpr size_t buffer_read_size = 32768;
25+
26+
static bool should_print_characters(Vector<u8> const& characters)
27+
{
28+
for (u8 ch : characters) {
29+
if (is_ascii_printable(ch) && !is_ascii_space(ch))
30+
return true;
31+
}
32+
return false;
33+
}
34+
35+
static void print_characters(Vector<u8> const& characters, StringOffsetFormat string_offset_format, size_t string_offset_position)
36+
{
37+
switch (string_offset_format) {
38+
case StringOffsetFormat::Decimal:
39+
out("{:>7d} ", string_offset_position);
40+
break;
41+
case StringOffsetFormat::Octal:
42+
out("{:>7o} ", string_offset_position);
43+
break;
44+
case StringOffsetFormat::Hexadecimal:
45+
out("{:>7x} ", string_offset_position);
46+
break;
47+
default:
48+
break;
49+
}
50+
outln("{:s}", characters.span());
51+
}
52+
53+
static int process_characters_in_span(Vector<u8>& characters, ReadonlyBytes span)
54+
{
55+
int processed_characters = 0;
56+
for (u8 ch : span) {
57+
++processed_characters;
58+
if (is_ascii_printable(ch) || ch == '\t')
59+
characters.append(ch);
60+
else
61+
break;
62+
}
63+
return processed_characters;
64+
}
65+
66+
static ErrorOr<void> process_strings_in_file(StringView path, bool show_paths, StringOffsetFormat string_offset_format, size_t minimum_string_length)
67+
{
68+
Array<u8, buffer_read_size> buffer;
69+
Vector<u8> output_characters;
70+
auto file = TRY(Core::Stream::File::open_file_or_standard_stream(path, Core::Stream::OpenMode::Read));
71+
size_t processed_characters = 0;
72+
size_t string_offset_position = 0;
73+
bool did_show_path = false;
74+
while (!file->is_eof()) {
75+
auto buffer_span = TRY(file->read(buffer));
76+
while (!buffer_span.is_empty()) {
77+
string_offset_position += processed_characters;
78+
processed_characters = process_characters_in_span(output_characters, buffer_span);
79+
if (show_paths && !did_show_path) {
80+
outln("path {}:", path);
81+
did_show_path = true;
82+
}
83+
if (output_characters.size() >= minimum_string_length && should_print_characters(output_characters)) {
84+
print_characters(output_characters, string_offset_format, string_offset_position);
85+
}
86+
buffer_span = buffer_span.slice(processed_characters);
87+
output_characters.clear();
88+
}
89+
}
90+
return {};
91+
}
92+
93+
ErrorOr<int> serenity_main(Main::Arguments arguments)
94+
{
95+
TRY(Core::System::pledge("stdio rpath"));
96+
97+
Vector<StringView> paths;
98+
size_t minimum_string_length = 4;
99+
bool show_paths = false;
100+
101+
StringOffsetFormat string_offset_format { StringOffsetFormat::None };
102+
103+
Core::ArgsParser args_parser;
104+
args_parser.add_option(minimum_string_length, "Specify the minimum string length.", nullptr, 'n', "number");
105+
args_parser.add_option(show_paths, "Display the path for each matched file.", nullptr, 'p');
106+
args_parser.add_option({ Core::ArgsParser::OptionArgumentMode::Required,
107+
"Write offset relative to start of each file in (d)ec, (o)ct, or he(x) format.",
108+
nullptr,
109+
't',
110+
"format",
111+
[&string_offset_format](char const* s) {
112+
StringView value = { s, strlen(s) };
113+
if (value == "d") {
114+
string_offset_format = StringOffsetFormat::Decimal;
115+
} else if (value == "o") {
116+
string_offset_format = StringOffsetFormat::Octal;
117+
} else if (value == "x") {
118+
string_offset_format = StringOffsetFormat::Hexadecimal;
119+
} else {
120+
return false;
121+
}
122+
return true;
123+
} });
124+
args_parser.set_general_help("Write the sequences of printable characters in files or pipes to stdout.");
125+
args_parser.add_positional_argument(paths, "File path", "path", Core::ArgsParser::Required::No);
126+
args_parser.parse(arguments);
127+
128+
if (minimum_string_length < 1) {
129+
warnln("Invalid minimum string length {}", minimum_string_length);
130+
return 1;
131+
}
132+
133+
if (paths.is_empty())
134+
paths.append("-"sv);
135+
136+
for (auto const& path : paths)
137+
TRY(process_strings_in_file(path, show_paths, string_offset_format, minimum_string_length));
138+
139+
return 0;
140+
}

0 commit comments

Comments
 (0)