mirror of
				git://git.code.sf.net/p/zsh/code
				synced 2025-10-31 18:10:56 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			259 lines
		
	
	
	
		
			7 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			259 lines
		
	
	
	
		
			7 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
| # 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.
 | |
| #
 | |
| # 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
 | |
| #
 | |
| # BUGS:
 | |
| #
 | |
| # "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.
 | |
| #
 | |
| # With the --max-procs option, zargs may not correctly capture the exit
 | |
| # status of the backgrounded jobs, because of limitations of the "wait"
 | |
| # builtin.  If the zsh/parameter module is not available, the status is
 | |
| # NEVER correctly returned, otherwise the status of the longest-running
 | |
| # job in each batch is captured.
 | |
| #
 | |
| # Also because of "wait" limitations, --max-procs spawns max-procs jobs,
 | |
| # then waits for all of those, then spawns another batch, etc.
 | |
| #
 | |
| 
 | |
| emulate -L zsh || return 1
 | |
| local -a opts eof n s l P i
 | |
| 
 | |
| local ZARGS_VERSION="1.3"
 | |
| 
 | |
| 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
 | |
|     if (( $#P ))
 | |
|     then
 | |
| 	P=${${P##-(P|-max-procs(=|))}:-1}
 | |
| 	if [[ x${P} != x$[P] ]]
 | |
| 	then
 | |
| 	    print -u2 zargs: invalid number for -P option
 | |
| 	    return 1
 | |
| 	fi
 | |
|     else P=1
 | |
|     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 wait bg
 | |
| local execute='
 | |
|     if (( $opts[(I)-(-interactive|p)] ))
 | |
|     then read -q "?$call?..." || continue
 | |
|     elif (( $opts[(I)-(-verbose|t)] ))
 | |
|     then print -u2 -r -- "$call"
 | |
|     fi
 | |
|     eval "{
 | |
| 	\$call
 | |
|     } $bg"'
 | |
| local ret=0 analyze='
 | |
|     case $? in
 | |
|     (0) ;;
 | |
|     (<1-125>|128)  ret=123;;
 | |
|     (255)       return 124;;
 | |
|     (<129-254>) return 125;;
 | |
|     (126)       return 126;;
 | |
|     (127)       return 127;;
 | |
|     (*)         return 1;;
 | |
|     esac'
 | |
| 
 | |
| if (( ARGC == 0 ))
 | |
| then
 | |
|     if (( $opts[(I)-(-no-run-if-empty|r)] ))
 | |
|     then return 0
 | |
|     else
 | |
| 	call=($command)
 | |
| 	# Use "repeat" here so "continue" won't complain.
 | |
| 	repeat 1 eval "$execute ; $analyze"
 | |
| 	return $ret
 | |
|     fi
 | |
| fi
 | |
| 
 | |
| n=${${n##-(n|-max-args(=|))}:-$[ARGC+c]}
 | |
| 
 | |
| if (( n > c ))
 | |
| then (( n -= c ))
 | |
| else
 | |
|     print -u2 zargs: argument list too long
 | |
|     return 1
 | |
| fi
 | |
| 
 | |
| P=${${P##-(P|-max-procs(=|))}:-1}
 | |
| 
 | |
| if (( P != 1 && ARGC > 1 ))
 | |
| then
 | |
|     # These setopts are necessary for "wait" on multiple jobs to work.
 | |
|     setopt nonotify nomonitor
 | |
|     bg='&'
 | |
|     if zmodload -i zsh/parameter 2>/dev/null
 | |
|     then
 | |
| 	wait='wait %${(k)^jobstates[(R)running:*]}'
 | |
|     else
 | |
| 	wait='wait'
 | |
|     fi
 | |
| fi
 | |
| 
 | |
| s=${${s##-(s|-max-chars(=|))}:-20480}
 | |
| l=${${l##-(l|-max-lines(=|))}:-${${l[1]:+1}:-$ARGC}}
 | |
| 
 | |
| # Everything has to be in a subshell just in case of backgrounding jobs,
 | |
| # so that we don't unintentionally "wait" for jobs of the parent shell.
 | |
| (
 | |
| 
 | |
| while ((ARGC))
 | |
| do
 | |
|     if (( P == 0 || P > ARGC ))
 | |
|     then (( P = ARGC ))
 | |
|     fi
 | |
| 
 | |
|     repeat $P
 | |
|     do
 | |
| 	((ARGC)) || break
 | |
| 	for (( end=l; end && ${(c)#argv[1,end]} > s; end/=2 )) :
 | |
| 	(( end > n && ( end = n ) ))
 | |
| 	args=( $argv[1,end] )
 | |
| 	shift $((end > ARGC ? ARGC : 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
 | |
| 	    continue
 | |
| 	else
 | |
| 	    eval "$execute"
 | |
| 	fi
 | |
|     done
 | |
| 
 | |
|     eval "$wait
 | |
| 	$analyze"
 | |
| done
 | |
| return $ret
 | |
| 
 | |
| )
 | |
| 
 | |
| # }
 |