about summary refs log tree commit diff
diff options
context:
space:
mode:
authorChristian Neukirchen <chneukirchen@gmail.com>2016-02-11 13:23:18 +0100
committerChristian Neukirchen <chneukirchen@gmail.com>2016-02-11 13:28:32 +0100
commitb3bc8cac0063a51ed44a118162d2df327d998576 (patch)
tree9994d90414413b3d5f8d7cfeb3af1d8cc063d408
parentdfb94adb2574b60e7eb403f00789ab6576960435 (diff)
downloadxe-b3bc8cac0063a51ed44a118162d2df327d998576.tar.gz
xe-b3bc8cac0063a51ed44a118162d2df327d998576.tar.xz
xe-b3bc8cac0063a51ed44a118162d2df327d998576.zip
support reading arguments from file with -f
-rw-r--r--README.md2
-rwxr-xr-xtests16
-rw-r--r--xe.110
-rw-r--r--xe.c26
4 files changed, 47 insertions, 7 deletions
diff --git a/README.md b/README.md
index 1f5c352..12761b6 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,7 @@ Over apply:
 ## Usage:
 
 	xe [-0FRnv] [-I arg] [-N maxargs] [-j maxjobs] COMMAND...
+	   | -f ARGFILE COMMAND...
 	   | -s SHELLSCRIPT
 	   | -a COMMAND... -- ARGS...
 	   | -A ARGSEP COMMAND... ARGSEP ARGS...
@@ -40,6 +41,7 @@ Over apply:
   `-j0` will run as many processes as there are CPU cores running.
 * `COMMAND...`: default operation: each command line argument is
   passed as-is, `{}` is replaced by the argument (not with `-N` > 1).
+* `-f ARGFILE`: Read arguments from ARGFILE, do not close standard input.
 * `-s SHELLSCRIPT`: The argument `SHELLSCRIPT` is evaluated using `/bin/sh`
   with the arguments (up to `-N`) passed as `$1`, `$2`, `$3`...
   (this behaves as if `/bin/sh -c SHELLSCRIPT -` is passed as plain COMMAND).
diff --git a/tests b/tests
index 5a5168a..e810c0d 100755
--- a/tests
+++ b/tests
@@ -1,5 +1,5 @@
 #!/bin/sh
-printf '1..40\n'
+printf '1..42\n'
 
 set -e
 
@@ -127,15 +127,19 @@ check_output 'using -A%' '$XE -A% echo -- % 1 2 3' <<EOF
 -- 3
 EOF
 
-check_output 'using -a with no arguments' '$XE -A% echo' <<EOF
+check_output 'using -A% with no arguments' '$XE -A% echo' <<EOF
 EOF
 
-check_output 'using -a with no command' '$XE -N2 -A% % 1 2 3' <<EOF
+check_output 'using -A% with no command' '$XE -N2 -A% % 1 2 3' <<EOF
 1
 2
 3
 EOF
 
+check_output 'using -f' 'necho notme | $XE -f tests echo | grep ^notme || echo success' <<EOF
+success
+EOF
+
 check_output 'using -s' 'necho 1 2 3 | $XE -s "echo x\$1"' <<EOF
 x1
 x2
@@ -224,6 +228,12 @@ y
 y
 EOF
 
+check_output 'should not close stdin when arguments were read from file' 'yes | $XE -f tests -s "sed q" | sed 3q' <<EOF
+y
+y
+y
+EOF
+
 printf '# limit checks, expecting maximal POSIX limits available\n'
 
 check_output 'argscap check' 'head -c 17711 /dev/zero | tr "\0" "\012" | $XE -N0 -s "echo \$#"' <<EOF
diff --git a/xe.1 b/xe.1
index 81e3698..3e7ca50 100644
--- a/xe.1
+++ b/xe.1
@@ -13,6 +13,9 @@
 .Ar command\ ...
 .Nm
 .Op Ar flags\ ...
+.Fl f Ar argfile Ar command\ ...
+.Nm
+.Op Ar flags\ ...
 .Fl s Ar shellscript
 .Nm
 .Op Ar flags\ ...
@@ -48,6 +51,13 @@ must appears as its own word.
 .Pp
 If no argument is passed, default is
 .Sq Ic printf %s\en .
+.It Fl f Ar argfile
+Like previous,
+but read arguments from
+.Ar argfile
+instead of standard input.
+.Pp
+This will not close standard input for execution.
 .It Fl s Ar shellscript
 In this mode, the single argument
 .Ar shellscript
diff --git a/xe.c b/xe.c
index c4b6292..5c0b22e 100644
--- a/xe.c
+++ b/xe.c
@@ -23,6 +23,7 @@ static char delim = '\n';
 static char default_replace[] = "{}";
 static char *replace = default_replace;
 static char *argsep;
+static char *fflag;
 static char *sflag;
 
 static int maxatonce = 1;
@@ -32,6 +33,7 @@ static int failed = 0;
 static int Aflag, Fflag, Rflag, aflag, nflag, vflag;
 static long iterations = 0;
 static FILE *traceout;
+static FILE *input;
 
 static size_t argmax;
 
@@ -59,9 +61,9 @@ getarg()
 			return 0;
 	}
 
-	int read = getdelim(&line, &linelen, delim, stdin);
+	int read = getdelim(&line, &linelen, delim, input);
 	if (read == -1) {
-		if (feof(stdin))
+		if (feof(input))
 			return 0;
 		else
 			exit(1);
@@ -206,7 +208,7 @@ run()
 		snprintf(iter, sizeof iter, "%ld", iterations);
 		setenv("ITER", iter, 1);
 		// redirect stdin to /dev/null when we read arguments from it
-		if (!(aflag || Aflag)) {
+		if (input == stdin) {
 			int fd = open("/dev/null", O_RDONLY);
 			if (fd >= 0) {
 				if (dup2(fd, 0) != 0)
@@ -282,7 +284,7 @@ main(int argc, char *argv[], char *envp[])
 
 	traceout = stdout;
 
-	while ((c = getopt(argc, argv, "+0A:FI:N:Raj:ns:v")) != -1)
+	while ((c = getopt(argc, argv, "+0A:FI:N:Raf:j:ns:v")) != -1)
 		switch(c) {
 		case '0': delim = '\0'; break;
 		case 'A': argsep = optarg; Aflag++; break;
@@ -291,6 +293,7 @@ main(int argc, char *argv[], char *envp[])
 		case 'F': Fflag++; break;
 		case 'R': Rflag++; break;
 		case 'a': aflag++; break;
+		case 'f': fflag = optarg; break;
 		case 'j': maxjobs = parse_jobs(optarg); break;
 		case 'n': nflag++; break;
 		case 's': sflag = optarg; break;
@@ -298,6 +301,7 @@ main(int argc, char *argv[], char *envp[])
 		default:
 			fprintf(stderr, 
 			    "Usage: %s [-0FRnv] [-I arg] [-N maxargs] [-j maxjobs] COMMAND...\n"
+			    "     | -f ARGFILE COMMAND...\n"
 			    "     | -s SHELLSCRIPT\n"
 			    "     | -a COMMAND... -- ARGS...\n"
 			    "     | -A ARGSEP COMMAND... ARGSEP ARGS...\n",
@@ -305,6 +309,20 @@ main(int argc, char *argv[], char *envp[])
 			exit(1);
 		}
 
+	if (aflag || Aflag) {
+		input = 0;
+	} else if (!fflag || strcmp("-", fflag) == 0) {
+		input = stdin;
+	} else {
+		input = fopen(fflag, "rb");
+		if (!input) {
+			fprintf(stderr, "xe: opening %s: %s\n",
+			    fflag, strerror(errno));
+			exit(1);
+		}
+		fcntl(fileno(input), F_SETFD, FD_CLOEXEC);
+	}
+
 	cmdend = argc;
 	if (aflag) {  // find first -- in argv
 		for (i = 1; i < argc; i++)