summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Functions/Misc/zargs199
1 files changed, 199 insertions, 0 deletions
diff --git a/Functions/Misc/zargs b/Functions/Misc/zargs
new file mode 100644
index 000000000..b52c80af5
--- /dev/null
+++ b/Functions/Misc/zargs
@@ -0,0 +1,199 @@
+# function zargs {
+#
+# This function works like GNU xargs, except that instead of reading lines
+# of arguments from the standard input, it takes them from the command
+# line. This is possible/useful because, especially with recursive glob
+# operators, zsh often can construct a command line for a shell function
+# that is longer than can be accepted by an external command.
+#
+# Like xargs, zargs exits with the following status:
+# 0 if it succeeds
+# 123 if any invocation of the command exited with status 1-125
+# 124 if the command exited with status 255
+# 125 if the command is killed by a signal
+# 126 if the command cannot be run
+# 127 if the command is not found
+# 1 if some other error occurred.
+#
+# However, "killed by a signal" is determined by the usual shell rule
+# that $? is the signal number plus 128, so zargs can be fooled by a
+# command that explicitly exits with 129+. Also, zsh prior to 4.1.x
+# returns 1 rather than 127 for "command not found" so this function
+# incorrectly returns 123 in that case if used with zsh 4.0.x.
+#
+# The full set of GNU xargs options is supported (see help text below);
+# although --eof and --max-lines therefore have odd names, they have
+# analogous meanings to their xargs counterparts. Also zargs --help is
+# a lot more helpful than xargs --help, at least as of xargs 4.1.
+#
+# Note that "--" is used both to end the options and to begin the command,
+# so to specify some options along with an empty set of input-args, one
+# must repeat the "--" as TWO consecutive arguments, e.g.:
+# zargs --verbose -- -- print There are no input-args
+# If there is at least one input-arg, the first "--" may be omitted:
+# zargs -p -i one -- print There is just {} input-arg
+# Obviously, if there is no command, the second "--" may be omitted:
+# zargs -n2 These words will be echoed in five lines of two
+#
+
+emulate -L zsh || return 1
+local -a opts eof n s l P i
+
+local ZARGS_VERSION="1.0"
+
+if zparseopts -a opts -D -- \
+ -eof::=eof e::=eof \
+ -exit x \
+ -help \
+ -interactive p \
+ -max-args:=n n:=n \
+ -max-chars:=s s:=s \
+ -max-lines::=l l::=l \
+ -max-procs:=P P:=P \
+ -no-run-if-empty r \
+ -null 0 \
+ -replace::=i i::=i \
+ -verbose t \
+ -version
+then
+ if (( $opts[(I)--version] ))
+ then
+ print -u2 zargs version $ZARGS_VERSION zsh $ZSH_VERSION
+ fi
+ if (( $opts[(I)--help] ))
+ then
+ >&2 <<-\HELP
+ Usage: zargs [options --] [input-args] [-- command [initial-args]]
+
+ If command and initial-args are omitted, "print -r --" is used.
+
+ Options:
+ --eof[=eof-str], -e[eof-str]
+ Change the end-of-input-args string from "--" to eof-str. If
+ given as --eof=, an empty argument is the end; as --eof or -e,
+ with no (or an empty) eof-str, all arguments are input-args.
+ --exit, -x
+ Exit if the size (see --max-chars) is exceeded.
+ --help
+ Print this summary and exit.
+ --interactive, -p
+ Prompt before executing each command line.
+ --max-args=max-args, -n max-args
+ Use at most max-args arguments per command line.
+ --max-chars=max-chars, -s max-chars
+ Use at most max-chars characters per command line.
+ --max-lines[=max-lines], -l[max-lines]
+ Use at most max-lines of the input-args per command line.
+ This option is misnamed for xargs compatibility.
+ --max-procs=max-procs, -P max-procs
+ Run up to max-procs command lines in the background at once.
+ --no-run-if-empty, -r
+ Do nothing if there are no input arguments before the eof-str.
+ --null, -0
+ Split each input-arg at null bytes, for xargs compatibility.
+ --replace[=replace-str], -i[replace-str]
+ Substitute replace-str in the initial-args by each initial-arg.
+ Implies --exit --max-lines=1.
+ --verbose, -t
+ Print each command line to stderr before executing it.
+ --version
+ Print the version number of zargs and exit.
+HELP
+ return 0
+ fi
+ if (( $opts[(I)--version] ))
+ then
+ return 0
+ fi
+ if (( $#i ))
+ then
+ l=1
+ i=${${i##-(i|-replace(=|))}:-\{\}}
+ opts[(r)-x]=-x
+ # The following is not how xargs is documented,
+ # but GNU xargs does behave as if -i implies -r.
+ opts[(r)-r]=-r
+ fi
+else
+ return 1
+fi
+
+local -i end c=0
+if [[ $eof == -(e|-eof) ]]; then ((end=ARGC+1))
+elif (( $#eof )); then end=$argv[(i)${eof##-(e|-eof=)}]
+else end=$argv[(i)--]
+fi
+local -a args call command; command=( ${argv[end+1,-1]} )
+
+if (( $opts[(I)-(null|0)] ))
+then set -- ${(ps:\000:)argv[1,end-1]}
+else set -- $argv[1,end-1]
+fi
+
+if [[ -n $command ]]
+then (( c = $#command - 1 ))
+else command=( print -r -- )
+fi
+
+local last='return $ret' execute='
+ if (( $opts[(I)-(-interactive|p)] ))
+ then read -q "?$call?..." || eval "$last"
+ elif (( $opts[(I)-(-verbose|t)] ))
+ then print -u2 -r -- "$call"
+ fi
+ $call
+ case $? in
+ (0) ;;
+ (<1-125>|128) ret=123;;
+ (255) return 124;;
+ (<129-254>) return 125;;
+ (126) return 126;;
+ (127) return 127;;
+ (*) return 1;;
+ esac
+ eval "$last"'
+
+if (( ARGC == 0 ))
+then
+ if (( $opts[(I)-(-no-run-if-empty|r)] ))
+ then return 0
+ else call=($command); eval "$execute"
+ fi
+fi
+
+n=${${n##-(n|-max-args(=|))}:-$[ARGC+c]}
+s=${${s##-(s|-max-chars(=|))}:-20480}
+l=${${l##-(l|-max-lines(=|))}:-${${l[1]:+1}:-$ARGC}}
+P=${${P##-(P|-max-procs(=|))}:-1}
+
+if (( n > c ))
+then (( n -= c ))
+else
+ print -u2 zargs: argument list too long
+ return 1
+fi
+
+last='shift $((end > ARGC ? ARGC : end)); continue'
+while ((ARGC))
+do
+ for (( end=l; end && ${(c)#argv[1,end]} > s; end/=2 )) :
+ (( end > n && ( end = n ) ))
+ args=( $argv[1,end] )
+ if (( $#i ))
+ then call=( ${command/$i/$args} )
+ else call=( $command $args )
+ fi
+ if (( ${(c)#call} > s ))
+ then
+ print -u2 zargs: cannot fit single argument within size limit
+ # GNU xargs exits here whether or not -x,
+ # but that just makes the option useless.
+ (( $opts[(I)-(-exit|x)] )) && return 1
+ eval "$last"
+ else
+ eval "$execute"
+ fi
+done
+return $ret
+
+# }