Two real shell scripts.
variables, conditionals, exit codes, logging.
ops1 wants a disk usage checker that fails gracefully and an idempotent user-creation tool that logs everything. Real scripts with argument handling and proper exit codes.
The ticket: ops1 needs two scripts in /usr/local/bin/, both runnable by the automation group. A diskcheck that reports the usage of any mountpoint passed as an argument and fails cleanly with a non-zero exit code if the mountpoint does not exist. And an adduser_safe that creates a user if they do not already exist, skips them if they do, and appends every action to an audit log.
The real problem: this is where scripting stops being one-liners. Argument handling, conditionals, exit codes, and logging, done the way a real tool would.
What we are doing: writing two genuine shell scripts, testing every failure path, not just the happy one.
The ticket
id ops1 # UID 7003 getent group automation # GID 6500
Concept review. The bash you actually need.
The bash man page is enormous. The sections that matter here are SPECIAL PARAMETERS, CONDITIONAL EXPRESSIONS, and Compound Commands.
man bash # search /SPECIAL PARAMETERS and /CONDITIONAL
#!/bin/bash shebang, tells the kernel which interpreter to use NAME="value" a variable. read it back with $NAME $1 $2 $@ $# positional parameters, all args, and the arg count $? exit code of the last command if [ cond ]; then ... fi conditional for i in list; do ... done loop over a list while [ cond ]; do ... done loop while true -f file -d dir -z empty-string -eq numeric-equal common tests
Script one. diskcheck.
It takes a mountpoint as $1. If none is given, or the path is not actually a mountpoint, it prints a clear error and exits non-zero. Different failures get different exit codes so a caller can tell them apart.
sudo bash -c 'cat > /usr/local/bin/diskcheck.sh << "EOF"
#!/bin/bash
# diskcheck.sh -- report disk usage for a given mountpoint
# usage: diskcheck.sh /mountpoint
MOUNT="$1"
if [ -z "$MOUNT" ]; then
echo "ERROR: usage: $0 <mountpoint>"
exit 1
fi
if ! mountpoint -q "$MOUNT"; then
echo "ERROR: $MOUNT is not a mounted filesystem"
exit 2
fi
USAGE=$(df -h "$MOUNT" | awk "NR==2 {print \$5}")
echo "Disk usage at $MOUNT: $USAGE"
exit 0
EOF'
sudo chmod 750 /usr/local/bin/diskcheck.sh
sudo chgrp automation /usr/local/bin/diskcheck.sh
# test every path: valid, invalid, missing arg
/usr/local/bin/diskcheck.sh / ; echo "exit: $?"
/usr/local/bin/diskcheck.sh /fakepath ; echo "exit: $?"
/usr/local/bin/diskcheck.sh ; echo "exit: $?"-z tests for an empty string, mountpoint -q checks the path is a real mount, and each exit code is distinct so failures are diagnosable.
Script two. adduser_safe.
Idempotent means running it twice is safe. If the user exists it logs a skip and exits 0. If a named group does not exist it errors. Every outcome is appended to the audit log with a timestamp.
sudo bash -c 'cat > /usr/local/bin/adduser_safe.sh << "EOF"
#!/bin/bash
# adduser_safe.sh -- idempotent user creation with an audit trail
# usage: adduser_safe.sh <username> [group]
LOGFILE="/var/log/user_provisioning.log"
USERNAME="$1"
GROUP="$2"
TS=$(date "+%Y-%m-%d %H:%M:%S")
if [ -z "$USERNAME" ]; then
echo "ERROR: usage: $0 <username> [group]"
exit 1
fi
if id "$USERNAME" &>/dev/null; then
echo "$TS SKIPPED: user $USERNAME already exists" >> "$LOGFILE"
echo "User $USERNAME already exists. Skipping."
exit 0
fi
if [ -n "$GROUP" ]; then
if ! getent group "$GROUP" &>/dev/null; then
echo "$TS ERROR: group $GROUP does not exist" >> "$LOGFILE"
echo "ERROR: group $GROUP does not exist"
exit 2
fi
useradd -m -G "$GROUP" "$USERNAME"
echo "$TS CREATED: $USERNAME (added to $GROUP)" >> "$LOGFILE"
else
useradd -m "$USERNAME"
echo "$TS CREATED: $USERNAME" >> "$LOGFILE"
fi
exit 0
EOF'
sudo chmod +x /usr/local/bin/adduser_safe.sh
sudo touch /var/log/user_provisioning.log
sudo chgrp automation /var/log/user_provisioning.log
sudo chmod 660 /var/log/user_provisioning.log
# test: create, then re-run (skip), then missing arg, then bad group
sudo /usr/local/bin/adduser_safe.sh testuser_a developers
sudo /usr/local/bin/adduser_safe.sh testuser_a developers
sudo /usr/local/bin/adduser_safe.sh
sudo /usr/local/bin/adduser_safe.sh testuser_b nonexistent_group
sudo cat /var/log/user_provisioning.log
sudo userdel -r testuser_a 2>/dev/nullid “$USERNAME” &>/dev/null is the existence check, its output thrown away because you only care about the exit status. -n tests for a non-empty string, the mirror of -z.
For loops with real data
Reading /etc/passwd line by line and acting on a field is a pattern you will reuse constantly.
cat > ~/list_real_users.sh << 'EOF'
#!/bin/bash
printf "%-20s %-6s %s\n" "USERNAME" "UID" "HOME"
while IFS=: read -r user _ uid _ _ home _; do
if [ "$uid" -ge 1000 ] && [ "$uid" -lt 65000 ]; then
printf "%-20s %-6s %s\n" "$user" "$uid" "$home"
fi
done < /etc/passwd
EOF
chmod +x ~/list_real_users.sh
~/list_real_users.shWhile loop with a threshold
A default value with ${2:-80} means the second argument is optional and falls back to 80.
cat > ~/disk_watch.sh << 'EOF'
#!/bin/bash
MOUNT="$1"
THRESHOLD="${2:-80}"
for i in 1 2 3; do
USAGE=$(df --output=pcent "$MOUNT" 2>/dev/null | tail -1 | tr -d ' %')
if [ "$USAGE" -ge "$THRESHOLD" ]; then
echo "WARN: $MOUNT at ${USAGE}% (threshold $THRESHOLD%)"
else
echo "OK: $MOUNT at ${USAGE}%"
fi
sleep 1
done
EOF
chmod +x ~/disk_watch.sh
~/disk_watch.sh / 50Exam questions
Write the script before you run it.
Q1. Write a script that takes a filename as $1 and prints whether it is a regular file, a directory, or does not exist.
Q2. Write a script that takes a username as $1, and if they do not exist, creates them, adds them to group staff, and logs the action with a timestamp to /tmp/audit.log.
Q3. Loop through all users in /etc/passwd with UID 1000 and up and print username, UID, and home in a formatted table.
Answers.
# A1
#!/bin/bash
if [ -f "$1" ]; then echo "$1 is a regular file"
elif [ -d "$1" ]; then echo "$1 is a directory"
else echo "$1 does not exist"; fi
# A2
#!/bin/bash
USER="$1"
getent group staff || sudo groupadd staff
if ! id "$USER" &>/dev/null; then
sudo useradd -G staff "$USER"
echo "$(date): created $USER, added to staff" >> /tmp/audit.log
else echo "user $USER already exists"; fi
# A3
awk -F: '$3 >= 1000 {printf "%-20s %-6s %s\n", $1, $3, $6}' /etc/passwdFinal checklist: confirm everything works
If every check passes, the ticket is done.
# 1. both scripts exist and are executable ls -l /usr/local/bin/diskcheck.sh /usr/local/bin/adduser_safe.sh # 2. diskcheck returns 0 on a real mount, non-zero on a fake one /usr/local/bin/diskcheck.sh / ; echo $? /usr/local/bin/diskcheck.sh /nope ; echo $? # 3. the audit log has entries sudo cat /var/log/user_provisioning.log # 4. all three exam scripts written from scratch # 5. tracker entry checked off
Reply to ops1: both scripts are in /usr/local/bin/, group-owned by automation. The audit log is at /var/log/user_provisioning.log, group-writable by automation. Every failure path returns a distinct non-zero exit code.