summaryrefslogtreecommitdiff
path: root/skiphead.re
diff options
context:
space:
mode:
Diffstat (limited to 'skiphead.re')
-rw-r--r--skiphead.re193
1 files changed, 193 insertions, 0 deletions
diff --git a/skiphead.re b/skiphead.re
new file mode 100644
index 0000000..f7f48e5
--- /dev/null
+++ b/skiphead.re
@@ -0,0 +1,193 @@
+// Copyright 2022 Benjamin Barenblat
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <array>
+#include <ios>
+#include <iostream>
+#include <string_view>
+
+#include "goldfishlocale.h"
+
+namespace {
+
+constexpr std::string_view kShortUsage =
+ "Usage: skiphead [OPTION...] COMMAND [ARGS...]\n";
+
+constexpr std::u8string_view kHelp =
+ u8R"(
+With no options, copy the first line of standard input to standard output, and
+then execute the specified command. This is useful for processing the output of
+commands that emit headers; for example, “ps -ef | skiphead grep systemd” will
+print the headers from “ps” before grepping for “systemd” in the remaining
+output.
+
+Like head(1), skiphead accepts -NUM, -n, and --lines arguments to control the
+number of lines printed before invoking COMMAND.
+
+If your COMMAND starts with -, you may terminate skiphead’s option processing
+with --.
+
+Options:
+ -NUM, -n NUM, --lines=NUM print NUM header lines instead of 1
+ -- terminate option processing
+ --help display this help and exit
+ --version display version information and exit
+
+Please report bugs to Benjamin Barenblat <bbarenblat@gmail.com>.
+)";
+
+constexpr std::u8string_view kAskForHelp =
+ u8"Try “skiphead --help” for more information.\n";
+
+constexpr std::string_view kVersionInfo = R"(skiphead 1.0.0
+Copyright 2022 Benjamin Barenblat
+Licensed under the Apache License, Version 2.0
+)";
+
+int ParseOptionsAndAdvanceArgv(char**& argv) {
+ int lines_to_skip = 1;
+ bool expect_bare_number = false;
+ for (++argv; argv[0] != nullptr; ++argv) {
+ const char* YYCURSOR = argv[0];
+ const char* a;
+ /*!stags:re2c format = "const char* @@;"; */
+
+ /*!re2c
+ re2c:define:YYCTYPE = char;
+ re2c:flags:tags = 1;
+ re2c:yyfill:enable = 0;
+
+ ("-" | "-n" | "--lines=") @a [0-9]+ {
+ lines_to_skip = atoi(a);
+ continue;
+ }
+
+ "-n" | "--lines" {
+ expect_bare_number = true;
+ continue;
+ }
+
+ [0-9]+ {
+ if (expect_bare_number) {
+ lines_to_skip = atoi(argv[0]);
+ expect_bare_number = false;
+ continue;
+ }
+ break;
+ }
+
+ "--help" {
+ std::cout << kShortUsage << goldfishlocale::ToSystem(kHelp);
+ exit(0);
+ }
+
+ "--version" {
+ std::cout << kVersionInfo;
+ exit(0);
+ }
+
+ "--" {
+ ++argv;
+ break;
+ }
+
+ "-" [^\x00]+ {
+ std::clog << goldfishlocale::ToSystem(
+ u8"skiphead: Unrecognized option “")
+ << argv[0] << goldfishlocale::ToSystem(u8"”\n")
+ << goldfishlocale::ToSystem(kAskForHelp);
+ exit(1);
+ }
+
+ * {
+ break;
+ }
+ */
+ }
+ return lines_to_skip;
+}
+
+int Read(int fd, void* buf, size_t count) {
+ ssize_t bytes_read = read(fd, buf, count);
+ if (bytes_read < 0) {
+ if (errno == EINTR) {
+ return Read(fd, buf, count); // Just try again.
+ }
+ std::clog << "skiphead: Read failed: " << strerror(errno) << '\n';
+ exit(1);
+ }
+ return bytes_read;
+}
+
+void CopyLines(int lines_to_skip) {
+ std::array<char, 16> buffer;
+ while (lines_to_skip > 0) {
+ int bytes_read =
+ Read(STDIN_FILENO, buffer.data(),
+ std::min(static_cast<int>(buffer.size()), lines_to_skip));
+ if (bytes_read == 0) {
+ // Standard input got closed. Just move on.
+ break;
+ }
+
+ for (int i = 0; i < bytes_read; ++i) {
+ if (buffer[i] == '\n') {
+ --lines_to_skip;
+ }
+ }
+
+ std::cout.write(buffer.data(), bytes_read);
+ }
+}
+
+} // namespace
+
+int main(int argc, char* argv[]) {
+ goldfishlocale::SetLocaleFromEnvironment();
+
+ // We're not using the C stdio functions in this program, so enable extra
+ // userspace buffering to reduce syscall overhead.
+ std::ios_base::sync_with_stdio(false);
+
+ if (argc < 2) {
+ std::clog << kShortUsage << goldfishlocale::ToSystem(kAskForHelp);
+ return 1;
+ }
+
+ int lines_to_skip = ParseOptionsAndAdvanceArgv(argv);
+ assert(lines_to_skip >= 0);
+ if (argv[0] == nullptr) {
+ std::clog << kShortUsage << goldfishlocale::ToSystem(kAskForHelp);
+ return 1;
+ }
+
+ CopyLines(lines_to_skip);
+ std::cout.flush();
+
+ execvp(argv[0], argv);
+
+ int r = errno;
+ std::clog << goldfishlocale::ToSystem(
+ u8"skiphead: Failed to execute command “")
+ << argv[0] << goldfishlocale::ToSystem(u8"”: ") << strerror(r)
+ << '\n';
+ return r;
+}