Skip to content

Instantly share code, notes, and snippets.

@haggen
Last active May 19, 2025 16:08
Show Gist options
  • Save haggen/1d8a8cf3b6db8c0062769eeb59947981 to your computer and use it in GitHub Desktop.
Save haggen/1d8a8cf3b6db8c0062769eeb59947981 to your computer and use it in GitHub Desktop.
POSIX compliant simplified implementation of foreman (Procfile) in shell script.
#!/usr/bin/env sh
# Halt on error or undefined variables.
set -eu
# Prefix input with a given string.
prefix() {
while IFS= read -r output; do
printf '%s\t%s\n' "$1" "$output"
done
}
# Print an error message and exit.
fatal() {
echo "procman: $1" >&2
exit 1
}
# Polyfill for mktemp.
if ! command -v mktemp >/dev/null 2>&1; then
mktemp() {
echo "/tmp/procfile.$$"
}
fi
# Read the Procfile from the first argument or use the default.
procfile=${1=./Procfile}
# Check the Procfile exists and is a file.
if ! test -f "$procfile"; then
fatal "$procfile is not a file"
fi
# Don't leave processes hanging around.
pids="--"
trap 'test "$pids" = "--" || kill $pids 2>/dev/null' EXIT QUIT TERM INT HUP
# Create a cleaned up temporary version of the Procfile.
temp_procfile="$(mktemp)"
sed -n '/^[a-z][a-z0-9_-]*: .*/ { s/#.*//; p; }' "$procfile" >"$temp_procfile"
# Read each line of the Procfile, split the line into a
# prefix and command, and launch the command in the
# background, collecting each PID.
while IFS= read -r proc; do
prefix="${proc%%:*}"
cmd="${proc#*:}"
{ sh -c -- "$cmd" 2>&1 | prefix "$prefix"; } &
pids="$pids -$!"
done <"$temp_procfile"
# Wait for all processes to finish.
wait
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment