@objctp/opencode-shell-routines 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +114 -0
- package/agents/shell-architect.md +88 -0
- package/agents/shell-expert.md +60 -0
- package/commands/shell-audit.md +47 -0
- package/commands/shell-batch-exec.md +48 -0
- package/commands/shell-new.md +57 -0
- package/commands/shell-routines-setup.md +66 -0
- package/commands/shell-test-run.md +46 -0
- package/opencode.json +19 -0
- package/package.json +34 -0
- package/plugins/shell-hooks.ts +150 -0
- package/scripts/lib-batch.sh +297 -0
- package/scripts/lib-common.sh +332 -0
- package/skills/shell-batch-operations/SKILL.md +97 -0
- package/skills/shell-batch-operations/assets/batch-template.sh +124 -0
- package/skills/shell-batch-operations/examples/data-pipeline.sh +157 -0
- package/skills/shell-batch-operations/examples/file-batch.sh +140 -0
- package/skills/shell-batch-operations/references/decision-tree.md +53 -0
- package/skills/shell-best-practices/SKILL.md +313 -0
- package/skills/shell-best-practices/assets/library.sh +142 -0
- package/skills/shell-best-practices/assets/minimal.sh +54 -0
- package/skills/shell-best-practices/assets/posix.sh +180 -0
- package/skills/shell-best-practices/assets/standard.sh +203 -0
- package/skills/shell-best-practices/references/patterns.md +386 -0
- package/skills/shell-best-practices/references/security.md +195 -0
- package/skills/shell-debugging/SKILL.md +115 -0
- package/skills/shell-debugging/examples/debug-session.md +165 -0
- package/skills/shell-debugging/references/debugging-guide.md +336 -0
- package/skills/shell-profiling/SKILL.md +154 -0
- package/skills/shell-profiling/examples/profile-session.md +225 -0
- package/skills/shell-profiling/references/optimisation-patterns.md +373 -0
- package/skills/shell-profiling/references/profiling-tools.md +318 -0
- package/skills/shell-profiling/scripts/bench.sh +82 -0
- package/skills/shell-profiling/scripts/trace-aggregate.sh +34 -0
- package/skills/shell-review/SKILL.md +61 -0
- package/skills/shell-review/examples/sample-review.md +42 -0
- package/skills/shell-review/references/guidelines.md +48 -0
- package/skills/shell-review/references/review-template.md +56 -0
- package/skills/shell-security/SKILL.md +128 -0
- package/skills/shell-security/examples/dangerous-command-review.md +231 -0
- package/skills/shell-security/examples/secure-script-example.sh +317 -0
- package/skills/shell-security/references/dangerous-commands.md +561 -0
- package/skills/shell-security/references/security-patterns.md +30 -0
- package/skills/shell-security/references/sensitive-files.md +525 -0
- package/skills/shell-security/scripts/security-audit.sh +208 -0
- package/skills/shell-test/SKILL.md +237 -0
- package/skills/shell-test/examples/test-example.md +74 -0
- package/skills/shell-test/references/advanced-patterns.md +52 -0
- package/skills/shell-test/references/assertions.md +184 -0
- package/skills/shell-test/references/test-template.md +60 -0
- package/skills/shell-test/scripts/public-coverage.sh +93 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { Hooks, Plugin, PluginInput } from "@opencode-ai/plugin";
|
|
2
|
+
|
|
3
|
+
const SHELL_EXTENSIONS = new Set(["sh", "bash", "zsh", "ksh"]);
|
|
4
|
+
const SHEBANG_PATTERN = /^#!.*\b(bash|sh|zsh|ksh)\b/;
|
|
5
|
+
const DASH_PATTERN = /#!.*\bdash\b/;
|
|
6
|
+
const SH_ONLY_PATTERN = /#!.*\bsh\b/;
|
|
7
|
+
const BASH_FAMILY_PATTERN = /#!.*\b(bash|zsh|ksh)\b/;
|
|
8
|
+
|
|
9
|
+
async function hasCmd($: PluginInput["$"], cmd: string): Promise<boolean> {
|
|
10
|
+
const r = await $`command -v ${cmd}`.nothrow();
|
|
11
|
+
return r.exitCode === 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const ShellHooksPlugin: Plugin = async (
|
|
15
|
+
{ $ }: PluginInput,
|
|
16
|
+
): Promise<Hooks> => {
|
|
17
|
+
const hasShellcheck = await hasCmd($, "shellcheck");
|
|
18
|
+
const hasCheckbashisms = await hasCmd($, "checkbashisms");
|
|
19
|
+
return {
|
|
20
|
+
"tool.execute.after": async (
|
|
21
|
+
input: {
|
|
22
|
+
tool: string;
|
|
23
|
+
sessionID: string;
|
|
24
|
+
callID: string;
|
|
25
|
+
args: { file_path?: string; filePath?: string; [key: string]: unknown };
|
|
26
|
+
},
|
|
27
|
+
output: { title: string; output: string; metadata: unknown },
|
|
28
|
+
) => {
|
|
29
|
+
if (input.tool !== "write" && input.tool !== "edit") return;
|
|
30
|
+
|
|
31
|
+
const filePath: string | undefined = input.args?.file_path ??
|
|
32
|
+
input.args?.filePath;
|
|
33
|
+
if (!filePath) return;
|
|
34
|
+
|
|
35
|
+
// Canonicalise and verify file exists
|
|
36
|
+
let resolved: string;
|
|
37
|
+
try {
|
|
38
|
+
resolved = await $`realpath ${filePath}`.nothrow().text();
|
|
39
|
+
resolved = resolved.trim();
|
|
40
|
+
} catch {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!resolved) return;
|
|
44
|
+
|
|
45
|
+
const exists = await $`test -f ${resolved}`.nothrow();
|
|
46
|
+
if (exists.exitCode !== 0) return;
|
|
47
|
+
|
|
48
|
+
const ext = resolved.split(".").pop()?.toLowerCase() ?? "";
|
|
49
|
+
|
|
50
|
+
// Read first line once — needed for shebang check and dialect detection
|
|
51
|
+
let firstLine: string;
|
|
52
|
+
try {
|
|
53
|
+
firstLine = await $`head -1 ${resolved}`.nothrow().text();
|
|
54
|
+
} catch {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!SHELL_EXTENSIONS.has(ext) && !SHEBANG_PATTERN.test(firstLine)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let dialect = "bash";
|
|
63
|
+
let isPosix = false;
|
|
64
|
+
if (DASH_PATTERN.test(firstLine)) {
|
|
65
|
+
dialect = "dash";
|
|
66
|
+
isPosix = true;
|
|
67
|
+
} else if (
|
|
68
|
+
SH_ONLY_PATTERN.test(firstLine) && !BASH_FAMILY_PATTERN.test(firstLine)
|
|
69
|
+
) {
|
|
70
|
+
dialect = "sh";
|
|
71
|
+
isPosix = true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const findings: string[] = [];
|
|
75
|
+
|
|
76
|
+
// ShellCheck — findings on stdout, exits non-zero on issues
|
|
77
|
+
if (hasShellcheck) {
|
|
78
|
+
try {
|
|
79
|
+
const sc = await $`shellcheck -s ${dialect} ${resolved} 2>&1`
|
|
80
|
+
.nothrow().text();
|
|
81
|
+
if (sc.trim()) {
|
|
82
|
+
findings.push(
|
|
83
|
+
`ShellCheck findings in ${resolved} (shell=${dialect}):\n${sc.trim()}`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
// deno-lint-ignore no-empty
|
|
87
|
+
} catch {}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!isPosix) {
|
|
91
|
+
try {
|
|
92
|
+
const syntax = await $`bash -n ${resolved} 2>&1`.nothrow().text();
|
|
93
|
+
if (syntax.trim()) {
|
|
94
|
+
findings.push(`Syntax error in ${resolved}: ${syntax.trim()}`);
|
|
95
|
+
}
|
|
96
|
+
// deno-lint-ignore no-empty
|
|
97
|
+
} catch {}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (isPosix && hasCheckbashisms) {
|
|
101
|
+
try {
|
|
102
|
+
const bashisms = await $`checkbashisms ${resolved} 2>&1`.nothrow()
|
|
103
|
+
.text();
|
|
104
|
+
if (bashisms.trim()) {
|
|
105
|
+
findings.push(
|
|
106
|
+
`POSIX compatibility issue in ${resolved} — bashisms detected:\n${bashisms.trim()}\n` +
|
|
107
|
+
"Note: /bin/sh is dash on Ubuntu/Debian. These will fail at runtime.",
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
// deno-lint-ignore no-empty
|
|
111
|
+
} catch {}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// TODO/FIXME/HACK/XXX/BUG markers
|
|
115
|
+
try {
|
|
116
|
+
const todos =
|
|
117
|
+
await $`grep -n -E '(^|[^[:alnum:]_])(TODO|FIXME|HACK|XXX|BUG):' ${resolved}`
|
|
118
|
+
.nothrow()
|
|
119
|
+
.text();
|
|
120
|
+
if (todos.trim()) {
|
|
121
|
+
findings.push(`Unresolved markers in ${resolved}:\n${todos.trim()}`);
|
|
122
|
+
}
|
|
123
|
+
// deno-lint-ignore no-empty
|
|
124
|
+
} catch {}
|
|
125
|
+
|
|
126
|
+
// Batch script pattern validation
|
|
127
|
+
try {
|
|
128
|
+
const content = await $`cat ${resolved}`.nothrow().text();
|
|
129
|
+
if (content.includes("lib-batch.sh")) {
|
|
130
|
+
if (!content.includes("batch_output")) {
|
|
131
|
+
findings.push(
|
|
132
|
+
`Batch script detected in ${resolved}: ensure batch_output() is called to return JSON results`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (!content.includes("declare -A RESULTS")) {
|
|
136
|
+
findings.push(
|
|
137
|
+
`Batch script detected in ${resolved}: declare RESULTS array with: declare -A RESULTS`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// deno-lint-ignore no-empty
|
|
142
|
+
} catch {}
|
|
143
|
+
|
|
144
|
+
if (findings.length > 0) {
|
|
145
|
+
output.output += "\n\n---\n**Shell quality checks:**\n" +
|
|
146
|
+
findings.join("\n\n");
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
};
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# shellcheck disable=SC2178
|
|
3
|
+
#
|
|
4
|
+
# Batch operations library for shell-routines plugin
|
|
5
|
+
# Source this file in scripts that perform multiple operations and return structured JSON
|
|
6
|
+
#
|
|
7
|
+
# Functions:
|
|
8
|
+
# batch_add_result - Add a key-value pair to results
|
|
9
|
+
# batch_add_metadata - Add metadata entry
|
|
10
|
+
# batch_add_error - Add error entry
|
|
11
|
+
# batch_output - Output JSON result to stdout
|
|
12
|
+
# batch_progress - Log progress to stderr
|
|
13
|
+
#
|
|
14
|
+
|
|
15
|
+
# Guard against direct execution
|
|
16
|
+
[[ "${BASH_SOURCE[0]}" == "${0}" ]] && {
|
|
17
|
+
echo "Error: This file should be sourced, not executed" >&2
|
|
18
|
+
exit 2
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
# Version tracking
|
|
22
|
+
# shellcheck disable=SC2034
|
|
23
|
+
readonly LIB_BATCH_VERSION="1.0.0"
|
|
24
|
+
|
|
25
|
+
###
|
|
26
|
+
### :::: Result Collection :::: #######
|
|
27
|
+
###
|
|
28
|
+
|
|
29
|
+
# Add a key-value pair to results array
|
|
30
|
+
# Usage: batch_add_result RESULTS "key" "value"
|
|
31
|
+
# Results are stored as: key=value
|
|
32
|
+
function batch_add_result() {
|
|
33
|
+
local -n results_ref="$1"
|
|
34
|
+
local key="$2"
|
|
35
|
+
local value="$3"
|
|
36
|
+
|
|
37
|
+
results_ref["${key}"]="${value}"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Add a numbered result (for arrays/lists)
|
|
41
|
+
# Usage: batch_add_result_item RESULTS "item"
|
|
42
|
+
function batch_add_result_item() {
|
|
43
|
+
local -n results_ref="$1"
|
|
44
|
+
local item="$2"
|
|
45
|
+
|
|
46
|
+
local index="${#results_ref[@]}"
|
|
47
|
+
results_ref["${index}"]="${item}"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Add metadata entry
|
|
51
|
+
# Usage: batch_add_metadata METADATA "key" "value"
|
|
52
|
+
function batch_add_metadata() {
|
|
53
|
+
local -n metadata_ref="$1"
|
|
54
|
+
local key="$2"
|
|
55
|
+
local value="$3"
|
|
56
|
+
|
|
57
|
+
metadata_ref+=("${key}=${value}")
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Add error entry
|
|
61
|
+
# Usage: batch_add_error ERRORS "error message"
|
|
62
|
+
function batch_add_error() {
|
|
63
|
+
local -n errors_ref="$1"
|
|
64
|
+
local error_msg="$2"
|
|
65
|
+
|
|
66
|
+
errors_ref+=("${error_msg}")
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
###
|
|
70
|
+
### :::: Progress Logging :::: ########
|
|
71
|
+
###
|
|
72
|
+
|
|
73
|
+
# Log progress message to stderr (doesn't pollute JSON output)
|
|
74
|
+
# Usage: batch_progress "Processing file: $file"
|
|
75
|
+
function batch_progress() {
|
|
76
|
+
printf '[%(%Y-%m-%d %H:%M:%S)T] [BATCH] %s\n' -1 "$*" >&2
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# Log step with percentage
|
|
80
|
+
# Usage: batch_step "Processing files" 5 100
|
|
81
|
+
function batch_step() {
|
|
82
|
+
local message="$1"
|
|
83
|
+
local current="$2"
|
|
84
|
+
local total="$3"
|
|
85
|
+
|
|
86
|
+
local percentage=0
|
|
87
|
+
if ((total > 0)); then
|
|
88
|
+
percentage=$((current * 100 / total))
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
batch_progress "${message} (${current}/${total} - ${percentage}%)"
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
###
|
|
95
|
+
### :::: JSON Output :::: #############
|
|
96
|
+
###
|
|
97
|
+
|
|
98
|
+
# Fallback for optional ERRORS argument
|
|
99
|
+
declare -a _EMPTY_ERRORS=()
|
|
100
|
+
|
|
101
|
+
# Build JSON from associative array (results)
|
|
102
|
+
# Usage: _build_results_json RESULTS
|
|
103
|
+
# Returns: JSON object string
|
|
104
|
+
function _build_results_json() {
|
|
105
|
+
local -n _brj_ref="$1"
|
|
106
|
+
local json="{"
|
|
107
|
+
local first=true
|
|
108
|
+
|
|
109
|
+
for key in "${!_brj_ref[@]}"; do
|
|
110
|
+
if [[ "$first" == "true" ]]; then
|
|
111
|
+
first=false
|
|
112
|
+
else
|
|
113
|
+
json+=","
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# Inline JSON escaping (no subprocess)
|
|
117
|
+
local value="${_brj_ref[$key]}"
|
|
118
|
+
value="${value//\\/\\\\}"
|
|
119
|
+
value="${value//\"/\\\"}"
|
|
120
|
+
value="${value//$'\n'/\\n}"
|
|
121
|
+
value="${value//$'\r'/\\r}"
|
|
122
|
+
value="${value//$'\t'/\\t}"
|
|
123
|
+
|
|
124
|
+
# Check if value is numeric (integer or float) or boolean
|
|
125
|
+
if [[ "$value" =~ ^-?[0-9]+$ ]] || [[ "$value" =~ ^-?[0-9]+\.[0-9]+$ ]] || [[ "$value" =~ ^(true|false)$ ]]; then
|
|
126
|
+
json+="\"${key}\":${value}"
|
|
127
|
+
else
|
|
128
|
+
json+="\"${key}\":\"${value}\""
|
|
129
|
+
fi
|
|
130
|
+
done
|
|
131
|
+
|
|
132
|
+
json+="}"
|
|
133
|
+
printf '%s' "$json"
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# Build JSON from metadata array
|
|
137
|
+
# Usage: _build_metadata_json METADATA
|
|
138
|
+
# Returns: JSON object string
|
|
139
|
+
function _build_metadata_json() {
|
|
140
|
+
local -n _bmj_ref="$1"
|
|
141
|
+
local json="{"
|
|
142
|
+
local first=true
|
|
143
|
+
|
|
144
|
+
for entry in "${_bmj_ref[@]}"; do
|
|
145
|
+
if [[ "$entry" =~ ^([^=]+)=(.*)$ ]]; then
|
|
146
|
+
local key="${BASH_REMATCH[1]}"
|
|
147
|
+
# Inline JSON escaping (no subprocess)
|
|
148
|
+
local value="${BASH_REMATCH[2]}"
|
|
149
|
+
value="${value//\\/\\\\}"
|
|
150
|
+
value="${value//\"/\\\"}"
|
|
151
|
+
value="${value//$'\n'/\\n}"
|
|
152
|
+
value="${value//$'\r'/\\r}"
|
|
153
|
+
value="${value//$'\t'/\\t}"
|
|
154
|
+
|
|
155
|
+
if [[ "$first" == "true" ]]; then
|
|
156
|
+
first=false
|
|
157
|
+
else
|
|
158
|
+
json+=","
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
json+="\"${key}\":\"${value}\""
|
|
162
|
+
fi
|
|
163
|
+
done
|
|
164
|
+
|
|
165
|
+
json+="}"
|
|
166
|
+
printf '%s' "$json"
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
# Build JSON from errors array
|
|
170
|
+
# Usage: _build_errors_json ERRORS
|
|
171
|
+
# Returns: JSON array string
|
|
172
|
+
function _build_errors_json() {
|
|
173
|
+
local -n _bej_ref="$1"
|
|
174
|
+
local json="["
|
|
175
|
+
|
|
176
|
+
for i in "${!_bej_ref[@]}"; do
|
|
177
|
+
if ((i > 0)); then
|
|
178
|
+
json+=","
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
# Inline JSON escaping (no subprocess)
|
|
182
|
+
local escaped="${_bej_ref[$i]}"
|
|
183
|
+
escaped="${escaped//\\/\\\\}"
|
|
184
|
+
escaped="${escaped//\"/\\\"}"
|
|
185
|
+
escaped="${escaped//$'\n'/\\n}"
|
|
186
|
+
escaped="${escaped//$'\r'/\\r}"
|
|
187
|
+
escaped="${escaped//$'\t'/\\t}"
|
|
188
|
+
json+="\"${escaped}\""
|
|
189
|
+
done
|
|
190
|
+
|
|
191
|
+
json+="]"
|
|
192
|
+
printf '%s' "$json"
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
# Output final JSON result to stdout
|
|
196
|
+
# Usage: batch_output RESULTS METADATA [ERRORS]
|
|
197
|
+
# Results: JSON object with results, metadata, and optional errors
|
|
198
|
+
function batch_output() {
|
|
199
|
+
local -n results_ref="$1"
|
|
200
|
+
local -n metadata_ref="$2"
|
|
201
|
+
local -n errors_ref="${3:-_EMPTY_ERRORS}"
|
|
202
|
+
|
|
203
|
+
local json="{"
|
|
204
|
+
|
|
205
|
+
# Add results
|
|
206
|
+
json+="\"results\":$(_build_results_json results_ref),"
|
|
207
|
+
|
|
208
|
+
# Add metadata
|
|
209
|
+
json+="\"metadata\":$(_build_metadata_json metadata_ref)"
|
|
210
|
+
|
|
211
|
+
# Add errors if any
|
|
212
|
+
if [[ ${#errors_ref[@]} -gt 0 ]]; then
|
|
213
|
+
json+=",\"errors\":$(_build_errors_json errors_ref)"
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
json+="}"
|
|
217
|
+
printf '%s\n' "$json"
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
###
|
|
221
|
+
### :::: Batch Processing Helpers :::: ###
|
|
222
|
+
###
|
|
223
|
+
|
|
224
|
+
# Process files with a callback function
|
|
225
|
+
# Usage: batch_process_files RESULTS METADATA ERRORS "glob_pattern" callback_function
|
|
226
|
+
# Example: batch_process_files RESULTS METADATA ERRORS "*.txt" process_txt_file
|
|
227
|
+
function batch_process_files() {
|
|
228
|
+
local -n results_ref="$1"
|
|
229
|
+
local -n metadata_ref="$2"
|
|
230
|
+
local -n errors_ref="$3"
|
|
231
|
+
local pattern="$4"
|
|
232
|
+
local callback="$5"
|
|
233
|
+
|
|
234
|
+
local files=()
|
|
235
|
+
local processed=0
|
|
236
|
+
local failed=0
|
|
237
|
+
|
|
238
|
+
# Collect files
|
|
239
|
+
while IFS= read -r -d '' file; do
|
|
240
|
+
files+=("$file")
|
|
241
|
+
done < <(find . -name "$pattern" -print0 2>/dev/null)
|
|
242
|
+
|
|
243
|
+
local total="${#files[@]}"
|
|
244
|
+
batch_progress "Found ${total} files matching '${pattern}'"
|
|
245
|
+
|
|
246
|
+
# Process each file
|
|
247
|
+
local i=0
|
|
248
|
+
for file in "${files[@]}"; do
|
|
249
|
+
i=$((i + 1))
|
|
250
|
+
batch_step "Processing" "$i" "$total"
|
|
251
|
+
|
|
252
|
+
if "$callback" "$file"; then
|
|
253
|
+
processed=$((processed + 1))
|
|
254
|
+
else
|
|
255
|
+
failed=$((failed + 1))
|
|
256
|
+
batch_add_error errors_ref "Failed to process: ${file}"
|
|
257
|
+
fi
|
|
258
|
+
done
|
|
259
|
+
|
|
260
|
+
# Add summary
|
|
261
|
+
batch_add_result results_ref "total" "$total"
|
|
262
|
+
batch_add_result results_ref "processed" "$processed"
|
|
263
|
+
batch_add_result results_ref "failed" "$failed"
|
|
264
|
+
|
|
265
|
+
return 0
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
# Run command and capture output
|
|
269
|
+
# Usage: batch_run_command RESULTS "key" command [args...]
|
|
270
|
+
# Returns: Exit code of command
|
|
271
|
+
function batch_run_command() {
|
|
272
|
+
local -n results_ref="$1"
|
|
273
|
+
local key="$2"
|
|
274
|
+
shift 2
|
|
275
|
+
|
|
276
|
+
local output
|
|
277
|
+
local exit_code
|
|
278
|
+
|
|
279
|
+
output=$("$@" 2>&1)
|
|
280
|
+
exit_code=$?
|
|
281
|
+
|
|
282
|
+
batch_add_result results_ref "${key}_exit" "$exit_code"
|
|
283
|
+
batch_add_result results_ref "${key}_output" "$output"
|
|
284
|
+
|
|
285
|
+
return "$exit_code"
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
###
|
|
289
|
+
### :::: Export Functions :::: ########
|
|
290
|
+
###
|
|
291
|
+
|
|
292
|
+
export -f batch_add_result batch_add_result_item
|
|
293
|
+
export -f batch_add_metadata batch_add_error
|
|
294
|
+
export -f batch_progress batch_step
|
|
295
|
+
export -f batch_output
|
|
296
|
+
export -f batch_process_files batch_run_command
|
|
297
|
+
# Internal functions (_build_*, _EMPTY_ERRORS) are not exported
|