make getpeername() return the original socket address which before it was intercepted
[hband-tools.git] / user-tools / foreach
blobd00556a0c8ee6bc09c9781c6795832ff3b55d9cd
1 #!/bin/bash
3 true <<'EOF'
4 =pod
6 =head1 NAME
8 foreach - Run an OS or shell command on each input line, similar to xargs(1)
10 =head1 SYNOPSIS
12 foreach [I<OPTIONS>] I<COMMAND> [I<ARGS> ...]
14 =head1 DESCRIPTION
16 Take each input line from stdin as B<DATA>, and run I<COMMAND> with B<DATA> appended to the end of I<ARGS> as a single argument.
18 If C<{}> is there in I<ARGS> then it's substituted with B<DATA> rather than append to the end,
19 unless I<--no-placeholder> is given, because then C<{}> is read literally.
20 Additionally, parse B<DATA> into fields and add each of them to the end of I<ARGS> if I<--fields> is given.
21 Numbered placeholders, like C<{0}>, C<{1}>, ... are substituted with the respective field's value.
23 So if you have not specified any I<ARGS> in the command line and type both I<--data> and I<--fields>,
24 then B<DATA> goes into argv[1], and the first field goes into argv[2], second to argv[3] and so on.
25 If have not given I<--data> nor I<--fields>, then I<--data> is implied.
27 If called with I<--sh> option, I<COMMAND> is run within a shell context;
28 input line goes to C<$DATA>, individual fields go to C<${FIELD[@]}> (0-indexed).
30 Both in command and shell (--sh) modes, individual fields are available in
31 C<$F0>, C<$F1>, ... environment variables.
33 Set I<-d> B<DELIM> if you want to split B<DATA> not by C<$IFS> but by other delimiter chars,
34 eg. C<-d ",:"> for command and colon.
35 There is also C<-t>/C<--tab> option to set delimiter to TAB for your convenience.
37 =head1 OPTIONS
39 =over 4
41 =item -e, --sh
43 I<COMMAND> is a shell script and for each B<DATA>, it runs in the same shell context,
44 so variables are preserved across invocations.
46 =item -l, --data
48 Pass B<DATA> in the arguments after the user-specified I<ARGS>.
50 =item -f, --fields
52 Pass individual fields of B<DATA> in the arguments after B<DATA> if I<--data> is given,
53 or after the user-specified I<ARGS> if I<--data> is not given.
55 =item -i, --input I<DATA>
57 Don't read any B<DATA> from stdin, but take I<DATA> given at B<--input> option(s).
58 This option is repeatable.
60 =item -d, --delimiter I<DELIM>
62 Cut up B<DATA> into fields at I<DELIM> chars.
63 Default is C<$IFS>.
65 =item -t, --tab
67 Cut up B<DATA> into fields at TAB chars..
69 =item -P, --no-placeholder
71 Do not substitute C<{}> with B<DATA>.
73 =item -p, --prefix I<TEMPLATE>
75 Print something before each command execution.
76 I<TEMPLATE> is a bash-interpolated string,
77 may contain C<$DATA> and C<${FIELD[n]}>.
78 Probably need to put in single quotes when passing to foreach(1) by the invoking shell.
79 It's designed to be B<eval>uated, so backtick, command substitution, semicolon, and other shell expressions are B<eval>'ed by bash.
81 =item --prefix-add I<TEMPLATE>
83 Append I<TEMPLATE> to the prefix template.
84 See B<--prefix> option.
86 =item --prefix-add-data
88 Add B<DATA> to the prefix which is printed before each command execution.
89 See B<--prefix> option.
91 =item --prefix-add-tab
93 Add a B<TAB> char to the prefix which is printed before each command execution.
94 See B<--prefix> option.
96 =item -v, --verbose
98 =item -n, --dry-run
100 =item -E, --errexit
102 Stop executing if a I<COMMAND> returns non-zero.
103 Rather exit with the said command's exit status code.
105 =back
107 =head1 EXAMPLES
109 ls -l --time-style +%FT%T%z | foreach --data --fields sh -c 'echo size: $5, file: $7'
110 ls -l --time-style +%FT%T%z | foreach --sh 'echo size: ${FIELD[4]}, file: ${FIELD[6]}'
112 =head1 LIMITS
114 Placeholders for field values (C<{0}>, C<{1}>, ...) are considered from 0 up to 99.
115 There must be a limit somewhere, otherwise I had to write a more complex replace routine.
117 =head1 CAVEATS
119 Placeholder C<{}> is substituted in all I<ARGS> anywhere, not just stand-alone C<{}> arguments,
120 but IS NOT ESCAPED!
121 So be careful using it in shell command arguments like C<sh -e 'echo "data is: {}"'>.
123 =head1 SEE ALSO
125 xargs(1), xe(1) L<https://github.com/leahneukirchen/xe>, apply(1), xapply(1) L<https://www.databits.net/~ksb/msrc/local/bin/xapply/xapply.html>
127 =cut
134 FOREACH_MODE=command
135 FOREACH_VERBOSE=no
136 unset FOREACH_DELIM
137 FOREACH_PASS_DATA=maybe
138 FOREACH_PASS_FIELDS=no
139 FOREACH_REPLACE_PLACEHOLDER=yes
140 FOREACH_DRYRUN=no
141 FOREACH_ERREXIT=no
142 FOREACH_PREFIX_TEMPLATE=''
143 FOREACH_INPUT_DATA=()
144 FOREACH_INPUT_DATA_INDEX=0
147 while [ $# != 0 ]
149 case "$1" in
150 (--help)
151 pod2text "$0"
152 exit
154 (-e|--sh)
155 FOREACH_MODE=sh
157 (-l|--data)
158 FOREACH_PASS_DATA=yes
160 (-f|--fields)
161 FOREACH_PASS_FIELDS=yes
163 (-i|--input)
164 shift
165 FOREACH_INPUT_DATA+=("$1")
167 (-d|--delimiter)
168 shift
169 FOREACH_DELIM=$1
171 (-t|--tab)
172 FOREACH_DELIM=$'\t'
174 (-P|--no-placeholder)
175 FOREACH_REPLACE_PLACEHOLDER=no
177 (-p|--prefix)
178 shift
179 FOREACH_PREFIX_TEMPLATE=$1
181 (--prefix-add)
182 shift
183 FOREACH_PREFIX_TEMPLATE=${FOREACH_PREFIX_TEMPLATE}"$1"
185 (--prefix-add-data)
186 FOREACH_PREFIX_TEMPLATE=${FOREACH_PREFIX_TEMPLATE}'$DATA'
188 (--prefix-add-tab)
189 FOREACH_PREFIX_TEMPLATE=${FOREACH_PREFIX_TEMPLATE}$'\t'
191 (-v|--verbose)
192 FOREACH_VERBOSE=yes
194 (-n|--dry-run)
195 FOREACH_DRYRUN=yes
197 (-E|--errexit)
198 FOREACH_ERREXIT=yes
200 (--)
201 shift
202 break;;
203 (-*)
204 echo "foreach: unknown option: $1" >&2
205 exit -1
207 (*) break;;
208 esac
209 shift
210 done
212 if [ $# = 0 ]
213 then
214 pod2text "$0" >&2
215 exit -1
218 if [ $FOREACH_PASS_FIELDS = yes -a $FOREACH_PASS_DATA != yes ]
219 then
220 FOREACH_PASS_DATA=no
222 if [ $FOREACH_PASS_FIELDS != yes ]
223 then
224 FOREACH_PASS_DATA=yes
227 declare -a FOREACH_COMMAND=("$@")
228 declare -g DATA
230 next_data()
232 if [ ${#FOREACH_INPUT_DATA[@]} -gt 0 ]
233 then
234 if [ $FOREACH_INPUT_DATA_INDEX -ge ${#FOREACH_INPUT_DATA[@]} ]
235 then
236 return 1
237 else
238 DATA=${FOREACH_INPUT_DATA[$FOREACH_INPUT_DATA_INDEX]}
239 FOREACH_INPUT_DATA_INDEX=$[FOREACH_INPUT_DATA_INDEX + 1]
240 return 0
242 else
243 local rc
244 read -r DATA
245 rc=$?
246 if [ $rc != 0 -a -z "$DATA" ]
247 then
248 return $rc
250 return 0
254 while next_data
256 export DATA
257 declare -a FIELD=()
258 IFS=${FOREACH_DELIM+$FOREACH_DELIM}${FOREACH_DELIM-$IFS} read -r -a FIELD <<< "$DATA"
260 for ((idx = 0; idx < ${#FIELD[@]}; idx++))
262 export F$idx="${FIELD[$idx]}"
263 done
264 # unset old F$idx variables
265 while declare -p F$idx >/dev/null 2>/dev/null
267 unset F$idx
268 let idx++
269 done
271 FOREACH_COMMAND_STATUS=0
273 declare -a cmd_and_args=()
274 placeholder_found=no
275 if [ "$FOREACH_REPLACE_PLACEHOLDER" = yes ]
276 then
277 for arg in "${FOREACH_COMMAND[@]}"
279 if [[ $arg =~ \{\} ]]
280 then
281 placeholder='{}'
282 arg=${arg//$placeholder/$DATA}
283 placeholder_found=yes
285 for ((idx = 0; idx < 100; idx++))
287 if [[ $arg =~ \{$idx\} ]]
288 then
289 placeholder="{$idx}"
290 arg=${arg//$placeholder/${FIELD[$idx]}}
291 placeholder_found=yes
293 done
294 cmd_and_args+=("$arg")
295 done
296 else
297 cmd_and_args=("${FOREACH_COMMAND[@]}")
300 if [ "$FOREACH_MODE" = command ]
301 then
302 if [ "$FOREACH_REPLACE_PLACEHOLDER" != yes -o $placeholder_found = no ]
303 then
304 if [ "$FOREACH_PASS_DATA" = yes ]; then cmd_and_args+=("$DATA"); fi
305 if [ "$FOREACH_PASS_FIELDS" = yes ]; then cmd_and_args+=("${FIELD[@]}"); fi
309 if [ "$FOREACH_VERBOSE" = yes ]
310 then
311 echo "foreach: ${cmd_and_args[*]}" >&2
314 eval "FOREACH_PREFIX=\"$FOREACH_PREFIX_TEMPLATE\""
315 echo -n "$FOREACH_PREFIX"
317 if [ "$FOREACH_DRYRUN" = no ]
318 then
319 if [ "$FOREACH_MODE" = command ]
320 then
321 command "${cmd_and_args[@]}"
322 FOREACH_COMMAND_STATUS=$?
323 else
324 eval "${cmd_and_args[@]}"
325 FOREACH_COMMAND_STATUS=$?
329 if [ $FOREACH_ERREXIT = yes -a $FOREACH_COMMAND_STATUS != 0 ]
330 then
331 exit $FOREACH_COMMAND_STATUS
333 done