diff options
Diffstat (limited to 'Functions')
-rw-r--r-- | Functions/Misc/zargs | 199 |
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 + +# } |