patternbashMinor
Raspberry Pi headless server using bash and USB automounting
Viewed 0 times
andautomountingraspberryusbheadlessusingserverbash
Problem
Revised from: Bash scripts and udev rules to handle USB auto mounting / unmounting
Tested:
Uses USB insert/remove to control a headless Raspberry Pi 3 with Raspian Jessie Lite
Changes:
Goals:
Current Code:
Uses udev rules to automount USB and create folder for device. Optionally can use an init constant to cause the automount/dismount process to auto start a process (in this case copy a config file) and shutdown pi on removal.
Work Flow:
udev rules
usb-initloader.sh
```
#!/bin/bash
#
# /home/pi/scripts/usb-initloader.sh
#
# this script uses udev rules
# is initiated when usb device is inserted or removed
#
# DEVICE INSERTED - new USB device inserted
# ---------
Tested:
Uses USB insert/remove to control a headless Raspberry Pi 3 with Raspian Jessie Lite
Changes:
- Implement more functions
- Improve comments
- Modify if-fi exit handling
- Add optional flag for auto processing on insert/remove
- Optional function for USB insert - copy file (future: read config/start streaming process)
- Optional auto shutdown for USB removal (future: close process started by insert)
Goals:
- Improve bash coding and learn by implementing suggestions
- Improve my understanding of if-fi blocks and using inline commands (I'm not confident at all when it comes to streamlining code)
Current Code:
Uses udev rules to automount USB and create folder for device. Optionally can use an init constant to cause the automount/dismount process to auto start a process (in this case copy a config file) and shutdown pi on removal.
Work Flow:
uDev rules
usb-initloader.sh
Insert -> usb-automount.sh
Remove -> usb-unloader.shudev rules
# /etc/udev/rules.d/85-usb-loader.rules
# ADD rule:
# if USB inserted,
# and flash drive loaded as sd#
# pass on dev name and device formatting type
# run short script to fork another processing script
# run script to initiate another script (first script must finish quickly)
# to mkdir and mount, process file
#
# reload rules on PI by:
# sudo udevadm control --reload-rules
#
ACTION=="add", KERNEL=="sd*[0-9]", SUBSYSTEMS=="usb", RUN+="/home/pi/scripts/usb-initloader.sh ADD %k $env{ID_FS_TYPE}"
ACTION=="remove", KERNEL=="sd*[0-9]", SUBSYSTEMS=="usb", RUN+="/home/pi/scripts/usb-initloader.sh %k"usb-initloader.sh
```
#!/bin/bash
#
# /home/pi/scripts/usb-initloader.sh
#
# this script uses udev rules
# is initiated when usb device is inserted or removed
#
# DEVICE INSERTED - new USB device inserted
# ---------
Solution
Introduce a helper function:
As I suggested in the previous review, I recommend to replace this code:
With this:
Where the implementation of
It's shorter and actually quite intuitive.
Introduce a helper function:
Another repeating pattern I see is the check-then-exit combos, like this:
You could create a helper function to make repeated usages slightly easier:
Actually, this form opens up the possibility for a more compact syntax:
With this and the earlier suggestions,
Notice that the calls to
If you're not comfortable yet with chaining commands with
you can stick to the
Explanation about exit codes,
You mentioned in comment that the
The
We can build conditions using the exit codes of commands, for example:
Notice that in case of success, the matched pattern is printed. Of course. That's why we normally use
If we don't care about the matching line, if we just want to know if there was a matching line or not, then we can suppress the output using the
Rerunning the above using the
Notice the difference from earlier: no more "hello" line,
the matched pattern was not printed.
Lastly, the same example using
Slightly more compact, but equivalent solution.
But this is by no means a preferred syntax.
It's "ok" to use this syntax when the condition is simple and easy to understand.
It's not well-suited and can get very confusing with more complex conditions,
and then it's not recommended.
is_mountedAs I suggested in the previous review, I recommend to replace this code:
device_mounted=$(grep "$DEVICE" /etc/mtab)
if [ "$device_mounted" ]; then
echo "Error: seems /dev/$DEVICE is already mounted"
exit 1
fi
# ...
device_mounted=$(grep "$DEVICE" /etc/mtab)
if [ "$device_mounted" == "" ]; then
echo "Error: Failed to Mount $MOUNT_DIR/$DEVICE"
exit 1
fiWith this:
if is_mounted "$DEVICE"; then
echo "Error: seems /dev/$DEVICE is already mounted"
exit 1
fi
# ...
if ! is_mounted "$DEVICE"; then
echo "Error: Failed to Mount $MOUNT_DIR/$DEVICE"
exit 1
fiWhere the implementation of
is_mounted:is_mounted() {
grep -q "$1" /etc/mtab
}It's shorter and actually quite intuitive.
Introduce a helper function:
fatalAnother repeating pattern I see is the check-then-exit combos, like this:
if some_requirement_fails; then
echo "Error: Failed some_requirement"
exit 1
fiYou could create a helper function to make repeated usages slightly easier:
fatal() {
echo "Error: $*"
exit 1
}
if some_requirement_fails; then
fatal "Failed some_requirement"
fiActually, this form opens up the possibility for a more compact syntax:
some_requirement || fatal "Failed some_requirement"With this and the earlier suggestions,
automount could be written like this:automount() {
dt=$(date '+%Y-%m-%d/ %H:%M:%S')
echo "--- USB Auto Mount --- $dt"
# check input parameters
[ "$MOUNT_DIR" ] || fatal "Missing Parameter: MOUNT_DIR"
[ "$DEVICE" ] || fatal "Missing Parameter: DEVICE"
[ "$FILESYSTEM" ] || fatal "Missing Parameter: FILESYSTEM"
# Allow time for device to be added
sleep 2
is_mounted "$DEVICE" && fatal "seems /dev/$DEVICE is already mounted"
# test mountpoint - it shouldn't exist
[ -e "$MOUNT_DIR/$DEVICE" ] && fatal "seems mountpoint $MOUNT_DIR/$DEVICE already exists"
# make the mountpoint
sudo mkdir "$MOUNT_DIR/$DEVICE"
# make sure the pi user owns this folder
sudo chown -R pi:pi "$MOUNT_DIR/$DEVICE"
# mount the device base on USB file system
case "$FILESYSTEM" in
# most common file system for USB sticks
vfat) sudo mount -t vfat -o utf8,uid=pi,gid=pi "/dev/$DEVICE" "$MOUNT_DIR/$DEVICE"
;;
# use locale setting for ntfs
ntfs) sudo mount -t auto -o uid=pi,gid=pi,locale=en_US.UTF-8 "/dev/$DEVICE" "$MOUNT_DIR/$DEVICE"
;;
# ext2/3/4 do not like uid option
ext*) sudo mount -t auto -o sync,noatime "/dev/$DEVICE" "$MOUNT_DIR/$DEVICE"
;;
esac
is_mounted "$DEVICE" || fatal "Failed to Mount $MOUNT_DIR/$DEVICE"
echo "SUCCESS: /dev/$DEVICE successfully mounted as $MOUNT_DIR/$DEVICE"
}Notice that the calls to
fatal can be chained using || or &&, depending on whether the requirement checked should be true or false, respectively.If you're not comfortable yet with chaining commands with
|| and &&,you can stick to the
if-fi syntax, there's nothing wrong with that.Explanation about exit codes,
grep -q, if, && and ||You mentioned in comment that the
grep -q part is not exactly easy to understand, so here's a bit more explanation, I hope it helps.grep exits with exit code 0 if there was a match, and some non-zero exit code if there was no match. For example:$ echo hello | grep e
hello
$ echo $?
0
$ echo hello | grep x
$ echo $?
1The
$? variable stores the exit code of the last command.We can build conditions using the exit codes of commands, for example:
$ if echo hello | grep e; then echo success; else echo failure; fi
hello
success
$ if echo hello | grep x; then echo success; else echo failure; fi
failureNotice that in case of success, the matched pattern is printed. Of course. That's why we normally use
grep, to find matching lines.If we don't care about the matching line, if we just want to know if there was a matching line or not, then we can suppress the output using the
-q flag.Rerunning the above using the
-q flag:$ if echo hello | grep -q e; then echo success; else echo failure; fi
success
$ if echo hello | grep -q x; then echo success; else echo failure; fi
failureNotice the difference from earlier: no more "hello" line,
the matched pattern was not printed.
Lastly, the same example using
&& and || instead of if statement:$ echo hello | grep -q e && echo success || echo failure
success
$ echo hello | grep -q x && echo success || echo failure
failureSlightly more compact, but equivalent solution.
But this is by no means a preferred syntax.
It's "ok" to use this syntax when the condition is simple and easy to understand.
It's not well-suited and can get very confusing with more complex conditions,
and then it's not recommended.
Code Snippets
device_mounted=$(grep "$DEVICE" /etc/mtab)
if [ "$device_mounted" ]; then
echo "Error: seems /dev/$DEVICE is already mounted"
exit 1
fi
# ...
device_mounted=$(grep "$DEVICE" /etc/mtab)
if [ "$device_mounted" == "" ]; then
echo "Error: Failed to Mount $MOUNT_DIR/$DEVICE"
exit 1
fiif is_mounted "$DEVICE"; then
echo "Error: seems /dev/$DEVICE is already mounted"
exit 1
fi
# ...
if ! is_mounted "$DEVICE"; then
echo "Error: Failed to Mount $MOUNT_DIR/$DEVICE"
exit 1
fiis_mounted() {
grep -q "$1" /etc/mtab
}if some_requirement_fails; then
echo "Error: Failed some_requirement"
exit 1
fifatal() {
echo "Error: $*"
exit 1
}
if some_requirement_fails; then
fatal "Failed some_requirement"
fiContext
StackExchange Code Review Q#134233, answer score: 3
Revisions (0)
No revisions yet.