Neat bash script
Neat bash script example.
We'll learn about >&2
and pushd
and popd
in this script.
Details
#!/usr/bin/env bash
set -euo pipefail # -e: exit on error, -u: treat unset vars as error, -o pipefail: fail if any command in a pipeline fails
exit_failure=1
project_root=$(git rev-parse --show-toplevel)
yeet_name=${1:-}
location=$project_root
render_file_template() {
if [ "$(uname)" == "Darwin" ]; then
sed -i '' "s/$1/$2/g" $3
else
sed -i "s/$1/$2/g" $3
fi
}
render_yeet_template() {
render_file_template "__TARGET__" "$yeet_name" Makefile
render_file_template "__TARGET__" "$yeet_name" YEET
}
prompt_user() {
prompt=$1
default=${2:-}
while :; do
if [ -n "$default" ]; then
read -p "$prompt [default: $default]: " response
else
read -p "$prompt: " response
fi
if [ -n "$response" ]; then
echo "$response"
return
fi
if [ -z "$response" ] && [ -n "$default" ]; then
response=$default
echo "$response"
return
fi
>&2 echo "Invalid input." # Print error message to stderr
done
}
process_variables() {
if [ -z "$yeet_name" ]; then
yeet_name=$(prompt_user "What would you like to name the new yeet?")
fi
}
main() {
process_variables
>&2 echo "Creating directory for $yeet_name"
if [ -d "$location/$yeet_name" ]; then
>&2 echo "Directory for $yeet_name already exists!"
exit $exit_failure
fi
mkdir -p "$location/$yeet_name"
cp -r .templates/yeet/* "$location/$yeet_name"
cp -r .templates/yeet/.clang-format "$location/$yeet_name"
pushd "$location/$yeet_name" > /dev/null 2>&1 # Save current dir, go to new dir (suppress output)
render_yeet_template
git init
popd > /dev/null 2>&1 # Return to previous dir (suppress output)
}
pushd "$project_root" > /dev/null 2>&1
main
popd > /dev/null 2>&1
This is a bash script I found on internet. It uses some very good patterns.
Here’s a progression of examples—from the simplest to more advanced—showing how and why you’d redirect output to stderr with constructs like >&2
.
1. Basic: Send a message to stderr
By default, echo
writes to stdout (file descriptor 1). To send it to stderr (fd 2) instead:
echo "Oops, something went wrong!" >&2
This is equivalent to:
echo "Oops, something went wrong!" 1>&2
Here:
1>
means “take stdout (fd 1)”2
is the target (stderr, fd 2)
Use this when you want your message to count as an error (so it won’t be mixed with normal output if someone is piping or capturing only stdout).
2. Intermediate: Functions and logging
Wrap stderr-writes in a helper function for cleaner code:
log_error() {
echo "ERROR: $*" >&2
}
# usage
if ! cp "$src" "$dst"; then
log_error "Failed to copy $src to $dst"
exit 1
fi
Here, any call to log_error "…”
goes to stderr, keeping it separate from “happy-path” output.
3. Redirecting entire blocks
You can redirect all output in a block to stderr:
{
echo "Step 1 passed"
echo "Step 2 passed"
echo "Final check failed!"
} >&2
Everything inside { … }
now writes to fd 2.
4. Advanced: Using exec
for global redirection
If you want your entire script (or a large section) to send stdout to stderr, you can exec
:
# At top of script, before any output:
exec 1>&2
echo "This goes to stderr"
# any subsequent echo also goes to stderr
You can even swap streams:
# Swap stdout and stderr for the remainder of the script:
exec 3>&1 1>&2 2>&3 3>&-
After this:
- What was stderr goes to stdout
- What was stdout goes to stderr
5. Super-advanced: Custom descriptors & logging to files
Unix supports more fds than just 0,1,2. You can open your own:
# Open fd 3 for appending to a logfile:
exec 3>>/var/log/my_script.log
# Send normal info to fd 3:
echo "All OK so far" >&3
# Send errors to stderr as usual:
echo "Fatal error" >&2
# Restore/close when done:
exec 3>&-
This lets you:
- Keep “info” messages in a log file via fd 3
- Keep “errors” distinct on stderr
Why stderr vs. stdout?
-
Separation of concerns
- stdout → data/results
- stderr → diagnostics/errors
-
Safe piping
myscript.sh | grep Success
only sees stdout successes, not debug or error chatter.
-
Better automation
Tools can capture or redirect each stream independently.
Rundown of pushd
/popd
, from simple to advanced:
-
Basic directory stack
pushd /path/to/dir # cd into DIR, and push old cwd onto a stack
popd # pop the top stack entry and cd back to itUse when you need to temporarily work in another directory and then return.
-
Inspecting & nesting
pushd /one
pushd /two
dirs # shows stack order: /two /one /original
popd # returns to /one (next entry on stack)Stack lets you nest multiple switches without manually tracking each path.
-
Rotating through entries
pushd A B C # push A, then B, then C; cwd ends at C
dirs # C B A
pushd +1 # rotate: bring second entry (B) to top; cwd=B
pushd +2 # now rotates third (A) to top; cwd=AQuickly cycle among saved locations by index instead of full paths.
-
Silent switching
pushd /tmp > /dev/null 2>&1
popd > /dev/null 2>&1Suppresses the usual “stack” output when you just want the directory change.
-
Robust scripting
pushd "$dir" || { echo "Cannot enter $dir" >&2; exit 1; }
# perform actions in $dir…
popd || exit 1Combine with error checks to ensure you always return (or bail) on failure.
Why use it?
- Convenience: No need to save and restore
$OLDPWD
manually. - Clarity:
dirs
shows your navigation history. - Flexibility: Rotate and jump by index for quick context switches.