Three Approaches to “Catching” Peripheral Events
If you’re a Linux system administrator, you’ve likely faced a challenging scenario: How do you ensure that when a 2TB hard drive full of log files is plugged in, the system automatically recognizes it, mounts it to the correct location, and runs a processing script immediately?
Here are three common approaches that admins typically consider:
- Shell Script + Cronjob: Running
lsblkevery 60 seconds. This method is both slow and increases unnecessary system load. - Pure Udev Rules: Configuring directly in
/etc/udev/rules.d/. This method is very lightweight but extremely difficult to debug. Writing complex logic, such as calling an API or parsing JSON files using Shell scripts inside udev rules, is a nightmare. - Python + pyudev library: This is the optimal choice. It combines the power of the Linux udev system with the flexible processing capabilities of Python.
Why is pyudev Superior to Shell Scripts?
My first project only required copying a few files from a USB to a server. At that time, the Python script was only about 50 lines long. However, as requirements grew—such as verifying MD5 checksums, categorizing 500GB of data by date, and sending notifications via Slack—things started to get complicated.
If using Shell scripts, maintenance would become a nightmare. pyudev solves this by managing devices as objects. You can filter devices by Serial Number or Vendor ID very cleanly. Since it listens directly from the Kernel via Netlink, Python receives the signal as soon as the USB connector touches the port, with latency usually under 10ms.
Implementing a Monitoring Script in Practice
Environment Setup
First, you need to install the library. Most distributions like Ubuntu or CentOS already have libudev available, so installation is quick and easy.
pip install pyudev
Sample Code: Monitoring Plug & Play Events
The code below acts as a background “sentry”. It prints detailed information as soon as any block device connects to the system.
import pyudev
def monitor_devices():
context = pyudev.Context()
monitor = pyudev.Monitor.from_netlink(context)
monitor.filter_by(subsystem='block')
print("--- Waiting for devices... ---")
for device in iter(monitor.poll, None):
if device.action == 'add':
print(f"Detected: {device.device_node}")
print(f"File System: {device.get('ID_FS_TYPE')}")
print(f"UUID: {device.get('ID_FS_UUID')}")
elif device.action == 'remove':
print(f"Device removed: {device.device_node}")
if __name__ == "__main__":
monitor_devices()
Automating Mounting and Data Processing
For production environments, I usually use the subprocess module to execute mount commands. A small tip: always check the ID_FS_USAGE attribute. This helps you avoid accidentally mounting swap partitions or unwanted system partitions.
import pyudev
import subprocess
import os
def handle_device_event(device):
if device.action == 'add' and device.get('ID_FS_USAGE') == 'filesystem':
node = device.device_node
label = device.get('ID_FS_LABEL') or "usb_disk"
mount_path = f"/mnt/external/{label}"
os.makedirs(mount_path, exist_ok=True)
try:
subprocess.run(["mount", node, mount_path], check=True)
print(f"Successfully mounted {node} to {mount_path}")
# Add file processing logic (e.g., rsync data) here
except subprocess.CalledProcessError as e:
print(f"Mount error: {e}")
def main():
context = pyudev.Context()
monitor = pyudev.Monitor.from_netlink(context)
monitor.filter_by(subsystem='block')
observer = pyudev.MonitorObserver(monitor, handle_device_event)
observer.start()
try:
while True:
import time
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
if __name__ == "__main__":
main()
Real-World Experience Operating Large Systems
When a script grows from dozens to thousands of lines of code, there are critical issues you need to keep in mind to avoid hanging the server:
- Never run heavy tasks directly: Never copy 100GB of data directly inside the udev callback function. This will block the entire listening process. Instead, push the task to
threadingor task queues likeCelery/Redis. - Handle “hot unplug” errors: Users often pull out the USB without unmounting. You need to wrap file read/write operations in very tight
try...exceptblocks. - Permission Management: Scripts running with standard user permissions usually cannot mount. You should configure
sudoersto allow the user to executemount/umountcommands without a password. - Identify by UUID: Never rely on names like
/dev/sdb1because they can change after every reboot. UseID_FS_UUIDto accurately identify specific devices for each client.
This technique has helped me fully automate the process of offloading offline data from monitoring stations to a central server. I hope this solution makes your DevOps toolkit even more powerful.

