-
Notifications
You must be signed in to change notification settings - Fork 3
/
parallel-bash.bash
executable file
·146 lines (119 loc) · 4.24 KB
/
parallel-bash.bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#!/usr/bin/env bash
# Parallel processing of commands in pure bash
# Also supports functions
_usage::parallel-bash() {
printf "%b\n" \
"Parallel processing commands in pure bash ( like xargs )
Usage: something | ${0##*/} -p 5 echo
${0##*/} -p 5 echo < some_file
${0##*/} -p 5 echo <<< 'some string'
The keyword '{}' can be used to for command inputs.
Either a command or a function can be used. For functions, need to export it first.
e.g: something | ${0##*/} -p 5 echo {}
Optional flags:
-k | -kc | --kill-children-processes => Kill children processes created when command is manually interrupted.
-p | --parallel-jobs => Number of parallel processes. Default value is 1.
-D | --debug => Show debug trace.
-h | --help => Show this help."
exit 0
}
_setup_arguments::parallel-bash() {
while [[ $# -gt 0 ]]; do
case "${1}" in
-h | --help) _usage::parallel-bash ;;
-D | --debug) set -x ;;
-p | --parallel-jobs)
if [[ ${2} -gt 0 ]]; then
NO_OF_JOBS="${2}" && shift
else
printf "\nError: -P only takes as positive integer as argument.\n"
return 1
fi
;;
-k | -kc | --kill-children-processes)
KILL_CHILD_PROCESSES=true
;;
*)
CMD_ARRAY=""
# all the args given after -c is taken as command input
for cmd in "${@}"; do CMD_ARRAY+="\"${cmd}\" "; done
break
;;
esac
shift
done
return 0
}
_process_arguments::parallel-bash() {
declare job=0 cmds=""
# a wrapper function
# takes 1 argument
_execute::_process_arguments::parallel-bash() {
# job, no_of_jobs_final ans cmds is from parent function
((job += 1))
# not using arrays because it is slow
# `;` is added to last to prevent stopping the execution because of a failed process
export "cmds+=${1:-:} ; "
# when job == no_of_jobs_final, then reset it and then again start appending from job 1
[[ ${job} -eq "${NO_OF_JOBS}" ]] && {
job=0
# all hail the eval lord
eval "${cmds}" &
cmds=""
}
}
# iterate over both input arrays
# then pass formed string for _execute::_process_arguments::parallel-bash
case "${CMD_ARRAY}" in
*'{}'*)
while IFS= read -r line; do
# If CMD_ARRAY array contains {}, then replace it with the input
_execute::_process_arguments::parallel-bash "${CMD_ARRAY//\{\}/\"${line}\"}"
done
;;
*)
while IFS= read -r line; do
_execute::_process_arguments::parallel-bash "${CMD_ARRAY} \"${line}\""
done
;;
esac
# this is probably pointless as the processes might be already completed before even reaching this point
# todo: fix this
declare status
wait || status=1
return "${status:-0}"
}
_cleanup::parallel-bash() {
{
# print messages if exited manually
export abnormal_exit && if [[ -n ${abnormal_exit} ]]; then
p_print() { printf "%b\n" "${1}"; }
else
p_print() { :; }
fi
p_printf "\n\nparallel-bash exited manually."
if [[ ${KILL_CHILD_PROCESSES} = "true" ]]; then
p_printf "Killing child processes."
# this kills the script including all the child processes
kill -- -$$ &
else
p_printf "Not killing child processes."
fi
} 2>| /dev/null || :
return 0
}
parallel-bash() {
[[ $# = 0 ]] && _usage::parallel-bash
! [[ ${BASH_VERSINFO:-0} -ge 3 ]] &&
printf "Bash version lower than 3.x not supported.\n" && return 1
set -o errexit -o noclobber -o pipefail
trap 'abnormal_exit="1"; exit' INT TERM
trap '_cleanup::parallel-bash' EXIT
trap '' TSTP # ignore ctrl + z
declare NO_OF_JOBS=1 CMD_ARRAY MAIN_PID KILL_CHILD_PROCESSES
_setup_arguments::parallel-bash "${@}" || return 1
export MAIN_PID="$$"
_process_arguments::parallel-bash || return 1
return 0
}
parallel-bash "${@}" || exit 1