Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/std-usage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ printf 'example\n' > "$example_file"
update_file_section "$example_file" "# BEGIN base-bash-libs" "# END base-bash-libs" "managed=true"

log_info "Validated standalone Base Bash library usage."
run --no-exit --quiet test -f "$example_file"
std_run --no-exit --quiet test -f "$example_file"
print_message "example_file=$example_file"
30 changes: 17 additions & 13 deletions lib/bash/std/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ The library improves Bash-based scripting in a few practical ways:
another command may pipe or capture.
- **Readable failures**: fatal errors include a message and Bash stack trace
instead of a mysterious non-zero exit.
- **Safe command execution**: `run` preserves argument boundaries, supports
- **Safe command execution**: `std_run` preserves argument boundaries, supports
dry-run mode, and can either exit or return a status.
- **Shared dry-run behavior**: scripts do not need to reimplement "print what
would happen" logic.
Expand Down Expand Up @@ -140,11 +140,11 @@ itself is fine and the user simply gave invalid arguments.

## Running Commands Safely

`run` is the preferred helper for simple external command execution:
`std_run` is the preferred helper for simple external command execution:

```bash
run git status --short
run touch "file with spaces.txt"
std_run git status --short
std_run touch "file with spaces.txt"
```

It improves on ad hoc command strings because it:
Expand All @@ -158,17 +158,17 @@ Dry-run mode:

```bash
DRY_RUN=true
run brew install jq
std_run brew install jq
```

`DRY_RUN` and `dry_run` both accept `true`, `1`, `yes`, and `on`. Use
`is_dry_run` when a script needs to branch on the same normalized dry-run state
without executing a command through `run`.
without executing a command through `std_run`.

Handle a failing command yourself with `--no-exit`:

```bash
if ! run --no-exit grep "needle" "$file"; then
if ! std_run --no-exit grep "needle" "$file"; then
log_info "needle was not present; continuing"
fi
```
Expand All @@ -177,14 +177,18 @@ For expected probe failures where the caller handles the status, add `--quiet`
to suppress the warning:

```bash
if ! run --no-exit --quiet test -f "$optional_file"; then
if ! std_run --no-exit --quiet test -f "$optional_file"; then
log_debug "Optional file is absent."
fi
```

Use `run` for commands plus arguments. Keep shell features such as pipelines,
redirection, process substitution, and complex conditionals explicit in the
calling script so the code remains clear.
Use `std_run` for commands plus arguments. Keep shell features such as
pipelines, redirection, process substitution, and complex conditionals explicit
in the calling script so the code remains clear.

`run` remains available as a compatibility wrapper for existing callers, but new
code should use `std_run` to avoid collisions with test frameworks and other
Bash libraries that define their own `run` helper.

## Importing Other Bash Libraries

Expand Down Expand Up @@ -308,7 +312,7 @@ main() {

assert_command_exists git
log_info "Checking project '$project'."
run git status --short
std_run git status --short
}

main "$@"
Expand All @@ -325,7 +329,7 @@ source "/path/to/base/lib/bash/std/lib_std.sh"

main() {
set_log_level DEBUG
run echo "hello"
std_run echo "hello"
}

main "$@"
Expand Down
30 changes: 20 additions & 10 deletions lib/bash/std/lib_std.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
# __SCRIPT_DIR__ Absolute path to the script that sourced the library.
#
# Core helpers:
# run [--no-exit] [--quiet] cmd ...
# std_run [--no-exit] [--quiet] cmd ...
# # Safe command runner with dry-run & failure handling.
# exit_if_error rc msg... # Log + exit when rc != 0 (preserves original status).
# fatal_error msg... # Convenience wrapper: exit with last status or 1.
Expand All @@ -35,7 +35,7 @@
# assert_* utilities # Validation helpers (assert_not_null / assert_integer / ...).
#
# Patterns:
# run some_cmd # exits on failure; DRY_RUN=true/1/yes/on prints instead.
# std_run some_cmd # exits on failure; DRY_RUN=true/1/yes/on prints instead.
# some_cmd || fatal_error ... # preserves failing exit code before terminating.
# add_to_path -p "/opt/tools" # inject directories without duplicates.
#
Expand Down Expand Up @@ -632,7 +632,7 @@ is_dry_run() {
}

#
# run - Safely executes a simple command with its arguments.
# std_run - Safely executes a simple command with its arguments.
#
# This function is designed to be a secure and robust replacement for using
# `eval` or simple command execution. It correctly handles arguments with
Expand All @@ -652,7 +652,7 @@ is_dry_run() {
# failures and is most useful with `--no-exit`.
#
# Usage:
# run [options] command [arg1] [arg2] ...
# std_run [options] command [arg1] [arg2] ...
#
# Options:
# --no-exit If provided as an initial argument, the script will not
Expand All @@ -663,22 +663,24 @@ is_dry_run() {
#
# Examples:
# # Run a simple command. Exits if `ls` fails.
# run ls -l /tmp
# std_run ls -l /tmp
#
# # Run a command with spaces in an argument.
# run touch "a file with spaces.txt"
# std_run touch "a file with spaces.txt"
#
# # Run a command but don't exit the script on failure.
# if ! run --no-exit grep "not_found" /etc/hosts; then
# if ! std_run --no-exit grep "not_found" /etc/hosts; then
# log "INFO" "The text was not found, but we are continuing."
# fi
#
# # In a script where DRY_RUN=true, this will only print the command.
# DRY_RUN=true
# run rm -rf /some/important/path
# std_run rm -rf /some/important/path
#
################################################################################
run() {
__std_run_impl__() {
local helper_name="$1"
shift
local exit_on_failure=1 quiet=0

# Parse optional run flags before the command.
Expand All @@ -704,7 +706,7 @@ run() {

# Check if the command is empty.
if [[ $# -eq 0 ]]; then
log_error "run: No command provided."
log_error "$helper_name: No command provided."
return 1
fi

Expand Down Expand Up @@ -741,6 +743,14 @@ run() {
return 0
}

std_run() {
__std_run_impl__ std_run "$@"
}

run() {
__std_run_impl__ run "$@"
}

############################################## FILE AND DIRECTORY HANDLING ############################################

#
Expand Down
45 changes: 32 additions & 13 deletions lib/bash/std/tests/lib_std.bats
Original file line number Diff line number Diff line change
Expand Up @@ -745,31 +745,36 @@ EOF
[[ "$output" == *"fatal boom"* ]]
}

@test "run returns an error when no command is provided" {
@test "std_run is the preferred command runner and run remains compatible" {
[ "$(type -t std_run)" = "function" ]
[ "$(type -t run)" = "function" ]
}

@test "std_run returns an error when no command is provided" {
local stderr_file="$TEST_TMPDIR/run-empty.err"
local rc

if run 2>"$stderr_file"; then
if std_run 2>"$stderr_file"; then
rc=0
else
rc=$?
fi

[ "$rc" -eq 1 ]
[[ "$(cat "$stderr_file")" == *"run: No command provided."* ]]
[[ "$(cat "$stderr_file")" == *"std_run: No command provided."* ]]
}

@test "run honors dry-run mode without executing the command" {
@test "std_run honors dry-run mode without executing the command" {
local target="$TEST_TMPDIR/dry-run.txt"
DRY_RUN=true

run touch "$target"
std_run touch "$target"

[ "$?" -eq 0 ]
[ ! -e "$target" ]
}

@test "run treats common truthy dry-run values as dry-run mode" {
@test "std_run treats common truthy dry-run values as dry-run mode" {
local case_name target value var_name

for case_name in \
Expand All @@ -787,18 +792,18 @@ EOF
export "$var_name"
target="$TEST_TMPDIR/dry-run-${var_name}-${value}.txt"

run touch "$target"
std_run touch "$target"

[ "$?" -eq 0 ]
[ ! -e "$target" ]
done
}

@test "run --no-exit returns the underlying failure status" {
@test "std_run --no-exit returns the underlying failure status" {
local stderr_file="$TEST_TMPDIR/run-no-exit.err"
local rc

if run --no-exit bash -c 'exit 7' 2>"$stderr_file"; then
if std_run --no-exit bash -c 'exit 7' 2>"$stderr_file"; then
rc=0
else
rc=$?
Expand All @@ -808,11 +813,11 @@ EOF
[[ "$(cat "$stderr_file")" == *"continuing"* ]]
}

@test "run --no-exit --quiet suppresses failure warning" {
@test "std_run --no-exit --quiet suppresses failure warning" {
local stderr_file="$TEST_TMPDIR/run-no-exit-quiet.err"
local rc

if run --no-exit --quiet bash -c 'exit 7' 2>"$stderr_file"; then
if std_run --no-exit --quiet bash -c 'exit 7' 2>"$stderr_file"; then
rc=0
else
rc=$?
Expand All @@ -822,13 +827,13 @@ EOF
[ ! -s "$stderr_file" ]
}

@test "run exits the script on failure by default" {
@test "std_run exits the script on failure by default" {
local script="$TEST_TMPDIR/run-fail.sh"

create_script "$script" <<EOF
#!/usr/bin/env bash
source "$STDLIB_PATH"
run bash -c 'exit 9'
std_run bash -c 'exit 9'
echo "after"
EOF

Expand All @@ -839,6 +844,20 @@ EOF
[[ "$output" != *"after"* ]]
}

@test "run compatibility wrapper delegates to std_run behavior" {
local stderr_file="$TEST_TMPDIR/run-compat.err"
local rc

if run --no-exit --quiet bash -c 'exit 7' 2>"$stderr_file"; then
rc=0
else
rc=$?
fi

[ "$rc" -eq 7 ]
[ ! -s "$stderr_file" ]
}

@test "safe_mkdir creates directories and tolerates existing paths with -p" {
local first="$TEST_TMPDIR/a"
local second="$TEST_TMPDIR/b/c"
Expand Down
Loading