I ended up using a systemd service. The udev script remains like this
And the actual scripts look like this
As you can see, the udev scripts are performing extremely minimal work, just starting and stopping a service. The service file looks like this
Because of Type=oneshot and RemainAfterExit=yes, this service will simply run its ExecStart script when started and run its ExecStopPost script when stopped. While it's running, it's doing nothing, but that's ok.
Finally, the actual scripts connect-service-port-drive.sh and disconnect-service-port-drive.sh can contain the actual code that I want to run when the USB device is attached or removed.
Here is part of my connect script that mounts the drive and reads from it:
Code:
ACTION=="add", SUBSYSTEM=="usb", RUN="/opt/udev-connect.sh"ACTION=="remove", SUBSYSTEM=="usb", RUN="/opt/udev-disconnect.sh"Code:
#!/bin/bash# udev-connect.shecho "Device connected. Starting service-port.service"systemctl start service-port.serviceCode:
#!/bin/bash# udev-disconnect.shecho "Device disconnected. Stopping service-port.service"systemctl stop service-port.serviceCode:
[Unit]Description=Service port flash drive handler[Service]Type=oneshotExecStart=/opt/connect-service-port-drive.shExecStopPost=/opt/disconnect-service-port-drive.shRemainAfterExit=yesRestart=no[Install]WantedBy=multi-user.targetBecause of Type=oneshot and RemainAfterExit=yes, this service will simply run its ExecStart script when started and run its ExecStopPost script when stopped. While it's running, it's doing nothing, but that's ok.
Finally, the actual scripts connect-service-port-drive.sh and disconnect-service-port-drive.sh can contain the actual code that I want to run when the USB device is attached or removed.
Here is part of my connect script that mounts the drive and reads from it:
Code:
#!/bin/bashecho "USB drive connected"echo "Mounting device..."# the /dev/sd# device will not be available right away, so we need to keep# trying to find it until it's actually found. It usually takes about 4 or 5# tries, but here we use a maximum of 100 triesMOUNTED=0TRIES=0while [ $TRIES -le 100 ] && [ $MOUNTED -ne 1 ]; do echo " ...attempt number $TRIES" TRIES=$(($TRIES + 1)) # list block devices # * --output specifies which data to print # * --paths means show the full paths of the devices # * --json puts the output into JSON format # * | jq passes the output to the jq JSON parser # * -r causes jq to output a raw string (without quotes) # * '.blockdevices[] | select(.rm == true) | .children[0] | .name') # finds all the block devices that are removable and gets the first # child of each and selects the name attribute from the output. # The final result should be either an empty string or the name of the # device. However, I found that sometimes it was null DEVICE_NAME=$(lsblk --output NAME,MOUNTPOINTS,RM --paths --json | jq -r '.blockdevices[] | select(.rm == true) | .children[0] | .name') # if the device name is empty or null then just sleep a little and try # again if [ -z $DEVICE_NAME ] || [ $DEVICE_NAME == "null" ]; then sleep 0.1s else # if we found the device, then we need to unmount any previously # mounted devices. If there are multiple devices mounted at the # location, the umount command will unmount one at a time. If there are # no devices mounted, then the umount command will return a non-zero # exit status. So here we just keep calling umount until we get an # error (capped at 100 tries to prevent an infinite loop) NEED_TO_UNMOUNT=1 UMOUNT_TRIES=0 while [ $NEED_TO_UNMOUNT == 1 ] && [ $UMOUNT_TRIES -le 100 ]; do UMOUNT_TRIES=$(($UMOUNT_TRIES + 1)) umount /media/service_port if [ $? != 0 ]; then NEED_TO_UNMOUNT=0 fi done # mount the new device mount $DEVICE_NAME /media/service_port MOUNTED=1 fidoneif [ $MOUNTED == 1 ]; then echo "Device mounted"else echo "Failed to mount device" exit 1fi# do actual work here and be able to read from /media/service_portecho "Done"Statistics: Posted by nullromo — Wed Jun 11, 2025 6:24 pm