1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-09 17:44:56 +09: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:
Fabian Dellwing 2024-02-13 09:42:23 +01:00 committed by Jelle Raaijmakers
parent 7beabc8e91
commit e8bdb7e5f8
Notes: sideshowbarker 2024-07-17 04:41:05 +09:00

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2024, Fabian Dellwing <fabian@dellwing.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -20,27 +21,39 @@ static ErrorOr<void> tail_from_pos(Core::File& file, off_t startline)
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;
for (; pos >= 1; pos--) {
TRY(file.seek(pos - 1, SeekMode::SetPosition));
if (start_from_end) {
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>());
if (ch == '\n' && (end - pos) > 0) {
if (ch == '\n') {
lines++;
if (lines == wanted_lines)
break;
}
}
return pos;
return pos + 1;
}
ErrorOr<int> serenity_main(Main::Arguments arguments)
@ -49,12 +62,35 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
bool follow = false;
size_t wanted_line_count = DEFAULT_LINE_COUNT;
bool start_from_end = true;
StringView file;
Core::ArgsParser args_parser;
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(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.parse(arguments);
@ -81,6 +117,26 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
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++) {
auto ch = bytes.at(i);
line.append(ch);
@ -98,7 +154,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
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));
if (follow) {