commit 2636b3f2de18f146b676f74bdc1269ef7e2fa1c1
parent a3deb1b53a2cc67b0c53bb3391f32e166546782c
Author: Friedel Schön <[email protected]>
Date:   Thu,  5 Sep 2024 23:06:00 +0200
add pipeto utility for simple piping
Diffstat:
| M | Makefile | 14 | +++++++++----- | 
| A | pipeto.c | 130 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
2 files changed, 139 insertions(+), 5 deletions(-)
diff --git a/Makefile b/Makefile
@@ -2,19 +2,23 @@ CFLAGS += -g -std=c99 -O2 -Wall -Wextra -Wpedantic
 LDFLAGS += -lncurses
 PREFIX ?= /usr
 
+BINS := textselect pipeto
+HEADERS := arg.h config.h
+
 .PHONY: all install clean
 
-all: textselect
+all: $(BINS)
 
-textselect.o: textselect.c arg.h config.h
+%.o: %.c $(HEADERS)
 	$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
 
-textselect: textselect.o
+%: %.o
 	$(CC) $< -o $@ $(LDFLAGS)
 
-install: textselect textselect.1
+install: all
 	cp textselect $(PREFIX)/bin/
+	cp pipeto $(PREFIX)/bin/
 	cp textselect.1 $(PREFIX)/share/man/man1/
 
 clean:
-	rm -f textselect textselect.o
+	rm -f textselect textselect.o pipeto pipeto.o
diff --git a/pipeto.c b/pipeto.c
@@ -0,0 +1,130 @@
+#include "arg.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#define USAGE "Usage: %s [-h] [-d delimiter] <inputcmd> {delimiter} <outputcmd>\n"
+
+#define NORETURN  __attribute__((noreturn))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+
+static char       *argv0     = NULL;
+static const char *delimiter = "+";
+
+static void die(const char *message) {
+	fprintf(stderr, "error: %s: %s\n", message, strerror(errno));
+	exit(EXIT_FAILURE);
+}
+
+static void help(void) {
+	fprintf(stderr,
+	        USAGE "Pipe output of command to another without a shell.\n"
+	              "\n"
+	              "Options:\n"
+	              "  -h              Display this help message and exit\n"
+	              "  -d delimeter    Split commands by demiliter (default: %s)\n"
+	              "\n"
+	              "Examples:\n"
+	              "  pipeto xbps-query -l + wc -l\n"
+	              "  pipeto find -name 'myfile' + xargs rm\n",
+	        argv0, delimiter);
+}
+
+static void usage(int exitcode) {
+	fprintf(stderr, USAGE, argv0);
+	exit(exitcode);
+}
+
+static void runcommand(char **inputcmd, char **outputcmd) {
+	int   pipefd[2];    // pipefd[0] is for reading, pipefd[1] is for writing
+	pid_t pid1, pid2;
+
+	if (pipe(pipefd) == -1) {
+		perror("pipe");
+		exit(EXIT_FAILURE);
+	}
+
+	// First child process to run inputcmd
+	if ((pid1 = fork()) < 0) {
+		perror("fork");
+		exit(EXIT_FAILURE);
+	}
+
+	if (pid1 == 0) {
+		// Child process 1: will exec inputcmd
+		close(pipefd[0]);                  // Close unused read end
+		dup2(pipefd[1], STDOUT_FILENO);    // Redirect stdout to pipe write end
+		close(pipefd[1]);                  // Close write end after dup
+
+		execvp(inputcmd[0], inputcmd);    // Replace child process with inputcmd
+		die("unable to execute input command");
+	}
+
+	// Second child process to run outputcmd
+	if ((pid2 = fork()) < 0) {
+		perror("fork");
+		exit(EXIT_FAILURE);
+	}
+
+	if (pid2 == 0) {
+		// Child process 2: will exec outputcmd
+		close(pipefd[1]);                 // Close unused write end
+		dup2(pipefd[0], STDIN_FILENO);    // Redirect stdin to pipe read end
+		close(pipefd[0]);                 // Close read end after dup
+
+		execvp(outputcmd[0], outputcmd);    // Replace child process with outputcmd
+		die("unable to execute output command");
+	}
+
+	// Parent process
+	close(pipefd[0]);    // Close both ends of the pipe in the parent
+	close(pipefd[1]);
+
+	// Wait for both children to finish
+	waitpid(pid1, NULL, 0);
+	waitpid(pid2, NULL, 0);
+}
+
+int main(int argc, char *argv[]) {
+	argv0 = argv[0];
+	ARGBEGIN
+	switch (OPT) {
+		case 'h':
+			help();
+			exit(0);
+		case 'd':
+			delimiter = EARGF(usage(1));
+			break;
+		default:
+			fprintf(stderr, "error: unknown option '-%c'\n", OPT);
+			usage(1);
+	}
+	ARGEND;
+
+	if (argc == 0) {
+		fprintf(stderr, "error: missing command\n");
+		usage(1);
+	}
+
+	char **outputcmd = NULL;
+
+	for (int i = 0; i < argc; i++) {
+		if (!strcmp(argv[i], delimiter)) {
+			if (outputcmd) {
+				fprintf(stderr, "error: delimiter occured more than once\n");
+				exit(EXIT_FAILURE);
+			}
+			argv[i]   = NULL;
+			outputcmd = &argv[i + 1];
+		}
+	}
+
+	runcommand(argv, outputcmd);
+
+	return 0;
+}