mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 05:07:45 +00:00
tail: Support skip from start mode
This commit adds the possibility to skip the first NUM lines of a file by calling `tail -n +NUM filename`.
This commit is contained in:
parent
7beabc8e91
commit
e8bdb7e5f8
1 changed files with 68 additions and 12 deletions
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||||
|
* Copyright (c) 2024, Fabian Dellwing <fabian@dellwing.net>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -20,27 +21,39 @@ static ErrorOr<void> tail_from_pos(Core::File& file, off_t startline)
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
static ErrorOr<off_t> find_seek_pos(Core::File& file, int wanted_lines)
|
static ErrorOr<off_t> find_seek_pos(Core::File& file, int wanted_lines, bool start_from_end)
|
||||||
{
|
{
|
||||||
// Rather than reading the whole file, start at the end and work backwards,
|
|
||||||
// stopping when we've found the number of lines we want.
|
|
||||||
off_t pos = TRY(file.seek(0, SeekMode::FromEndPosition));
|
|
||||||
|
|
||||||
off_t end = pos;
|
|
||||||
int lines = 0;
|
int lines = 0;
|
||||||
|
|
||||||
for (; pos >= 1; pos--) {
|
if (start_from_end) {
|
||||||
TRY(file.seek(pos - 1, SeekMode::SetPosition));
|
off_t pos = TRY(file.seek(0, SeekMode::FromEndPosition));
|
||||||
|
off_t end = pos;
|
||||||
|
for (; pos >= 1; pos--) {
|
||||||
|
TRY(file.seek(pos - 1, SeekMode::SetPosition));
|
||||||
|
|
||||||
|
auto ch = TRY(file.read_value<u8>());
|
||||||
|
if (ch == '\n' && (end - pos) > 0) {
|
||||||
|
lines++;
|
||||||
|
if (lines == wanted_lines)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t file_size = TRY(file.size());
|
||||||
|
off_t pos = 0;
|
||||||
|
|
||||||
|
for (; pos < file_size; pos++) {
|
||||||
auto ch = TRY(file.read_value<u8>());
|
auto ch = TRY(file.read_value<u8>());
|
||||||
if (ch == '\n' && (end - pos) > 0) {
|
if (ch == '\n') {
|
||||||
lines++;
|
lines++;
|
||||||
if (lines == wanted_lines)
|
if (lines == wanted_lines)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pos;
|
return pos + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorOr<int> serenity_main(Main::Arguments arguments)
|
ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
|
@ -49,12 +62,35 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
|
|
||||||
bool follow = false;
|
bool follow = false;
|
||||||
size_t wanted_line_count = DEFAULT_LINE_COUNT;
|
size_t wanted_line_count = DEFAULT_LINE_COUNT;
|
||||||
|
bool start_from_end = true;
|
||||||
StringView file;
|
StringView file;
|
||||||
|
|
||||||
Core::ArgsParser args_parser;
|
Core::ArgsParser args_parser;
|
||||||
args_parser.set_general_help("Print the end ('tail') of a file.");
|
args_parser.set_general_help("Print the end ('tail') of a file.");
|
||||||
args_parser.add_option(follow, "Output data as it is written to the file", "follow", 'f');
|
args_parser.add_option(follow, "Output data as it is written to the file", "follow", 'f');
|
||||||
args_parser.add_option(wanted_line_count, "Fetch the specified number of lines", "lines", 'n', "number");
|
args_parser.add_option(Core::ArgsParser::Option {
|
||||||
|
.argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
|
||||||
|
.help_string = "output the last NUM lines, instead of the last 10;"
|
||||||
|
" or use -n +NUM to output starting with line NUM",
|
||||||
|
.long_name = "lines",
|
||||||
|
.short_name = 'n',
|
||||||
|
.value_name = "[+]NUM",
|
||||||
|
.accept_value = [&](StringView lines) -> ErrorOr<bool> {
|
||||||
|
Optional<size_t> value;
|
||||||
|
if (lines.starts_with('+')) {
|
||||||
|
value = lines.substring_view(1, lines.length() - 1).to_number<size_t>();
|
||||||
|
start_from_end = false;
|
||||||
|
} else {
|
||||||
|
value = lines.to_number<size_t>();
|
||||||
|
}
|
||||||
|
if (!value.has_value()) {
|
||||||
|
warnln("Invalid number: {}", lines);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
wanted_line_count = value.value();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
args_parser.add_positional_argument(file, "File path", "file", Core::ArgsParser::Required::No);
|
args_parser.add_positional_argument(file, "File path", "file", Core::ArgsParser::Required::No);
|
||||||
args_parser.parse(arguments);
|
args_parser.parse(arguments);
|
||||||
|
|
||||||
|
@ -81,6 +117,26 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!start_from_end) {
|
||||||
|
if (wanted_line_count > line_count) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (wanted_line_count == 0) {
|
||||||
|
out("{}", StringView { bytes });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < bytes.size(); i++) {
|
||||||
|
auto ch = bytes.at(i);
|
||||||
|
if (ch == '\n') {
|
||||||
|
line_index++;
|
||||||
|
}
|
||||||
|
if (line_index >= wanted_line_count)
|
||||||
|
line.append(ch);
|
||||||
|
}
|
||||||
|
out("{}", line.to_byte_string().substring_view(1, line.length() - 1));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < bytes.size(); i++) {
|
for (size_t i = 0; i < bytes.size(); i++) {
|
||||||
auto ch = bytes.at(i);
|
auto ch = bytes.at(i);
|
||||||
line.append(ch);
|
line.append(ch);
|
||||||
|
@ -98,7 +154,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pos = TRY(find_seek_pos(*f, wanted_line_count));
|
auto pos = TRY(find_seek_pos(*f, wanted_line_count, start_from_end));
|
||||||
TRY(tail_from_pos(*f, pos));
|
TRY(tail_from_pos(*f, pos));
|
||||||
|
|
||||||
if (follow) {
|
if (follow) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue