Wood's Words

More Bash Patterns

Let’s look at a few more patterns that a bash shell script can use. I’ll show how a script can drop its own privileges, run with a lower priority, obtain a lock file1, and create a temp file.

  1. Drop Privileges
  2. Create a Temp File
  3. Run With a Lower Priority
  4. Generate a Seeded Random Number
  5. Footnotes

Drop Privileges

Occasionally you might write a script that needs to run as a particular user. Rather than having to remember to use su to switch to that user or sudo -u $user to run the script as that user, you can do the following:

# global vars
prog="${0##*/}"

# prints an error message to stderr
err() {
	local message="$1"
	shift
	printf "$message\n" "$@"
}

# drop privileges
user=nobody
if (( EUID == 0 )); then
	exec runuser -u "$user" "$0" "$@"
	# or if runuser isn't available on your system:
	#exec su "$user" -c "$(printf %q "$0") $(printf %q "$*")"
elif [[ $(id -un) != "$user" ]]; then
	err "This script must be run as %s or %s" 'root' "$user"
	exit 1
fi

Now you can run the script from a root shell or with sudo, safe in the knowledge that it will only run as the nobody user.

Create a Temp File

The pitfalls involved in creating a temporary file are well-known. To safely create a temp file, use mktemp.

# create a temporary file
trap "rm -f '$tmpfile'" EXIT
tmpfile="$(mktemp --tmpdir "$prog.XXXXXXXXXX")" || exit 1

This will atomically create a new temporary file in a directory defined by the $TMPDIR environment variable if it’s defined, or /tmp if it’s not.

Run With a Lower Priority

If you have a script that uses a lot of CPU, you can set its niceness with renice. Higher niceness values give a process lower scheduling priority.

# change niceness of this script
renice 10 -p $$

Generate a Seeded Random Number

Suppose you have a script that runs daily from cron, and it needs to perform some cleanup task once per week. If you wanted the task to happen on a day that varies from system to system, you could do

# clean up once per week
RANDOM=$(( 16#$(hostname -f | md5sum | cut -c1-4) ))
cleanup_day=$(RANDOM % 7)
if (( "$(date +%u)" == "$cleanup_day" )); then
	# do cleanup task
fi

Note that this is only a pathological example meant to demonstrate the technique. It would be simpler to schedule the cleanup task as a separate cron job in this scenario. But if, for example, you needed to schedule such a cron job from a configuration management system, you could use this technique to generate the day of the week (or the day of the month, the hour, the minute, etc.) to schedule it on.

Footnotes

  1. I’ve removed the section about lock files after reddit user /u/vogelke raised some concerns about it.