Reporting new USB storage devices on Linux and macOS with Python Synopsis:
I wanted something for macOS (and Linux;  maybe eventually Windows) that would simply wait for the user to connect a storage device and automatically select, or otherwise output the information to be used, read, manipulated, etc.
In its current form, it just prints to the shell, but you could assign the output to a list or variable for read/write operations and so on. It will respond to any new entries in the /dev system directory, including most USB devices, SD Cards, Webcams, and so on. You can test it by running the script in one window, and running something like sudo touch /dev/{x,y,z} in another.
I plan to use it to help people (those of us who are less technically inclined) migrate to Linux by automating the creation of bootable flash drives, but you can do what you like with it.
Open-ended feedback and suggestions are welcome. Please try to post example code pertaining to your suggestions, and don't be afraid to say something positive.
Usage:
user@macOS:~$ ./devlisten.py
/dev/disk2
/dev/rdisk2
/dev/disk2s1
/dev/rdisk2s1

Code:
#!/usr/bin/env python3

import os
import re
import time
import difflib

try:
    os.mkdir('/tmp/dev')
except FileExistsError:
    pass
except FileNotFoundError:
    print('No /tmp directory found.')
    exit()
except OSError:
    print('Read-only file system.')
    exit()

file1 = open('/tmp/dev/1', 'w')
for x in os.listdir('/dev'):
    file1.write(x + '\n')
file1.close()

try:

    diff = False
    while diff == False:
        time.sleep(0.25)

        file2 = open('/tmp/dev/2', 'w')
        for x in os.listdir('/dev'):
            file2.write(x + '\n')
        file2.close()

        text1 = open('/tmp/dev/1').readlines()
        text2 = open('/tmp/dev/2').readlines()

        for line in difflib.unified_diff(text1, text2):
            for line in re.finditer(r'(?&lt;=^\+)\w.*$', line, re.MULTILINE):
                print('/dev/' + line.group(0))
                diff = True

except KeyboardInterrupt:
    
    print()
    exit()
This script might get the job done, but it is a rather crude and inefficient hack.  Ideally, you should avoid polling every quarter second (or polling at all).  Also, I see no reason to write any files to /tmp.



The ideal way to do it in Linux is to use udev.  If you don't want to write a persistent udev rule, or you don't have root access, you can run /sbin/udevadm monitor --udev --property as a child process, and trigger your test whenever udevadm offers output.  For a smarter script, you can can look for the ACTION=add line and take advantage of the information in the SUBSYSTEM=… and DEVPATH=… lines.  DEVPATH tells you path to the device within /sys.  But, even if you simply trigger your script only when any output appears, that would be a huge improvement over polling four times a second.

import itertools
from subprocess import Popen, PIPE
import re
import sys

KEYVALUE_RE = re.compile(r'([^=]+)=(.*)')

def events(stream):
    """
    Read udev events from the stream, yielding them as dictionaries.
    """
    while True:
        event = dict(
            KEYVALUE_RE.match(line).groups()
            for line in itertools.takewhile(KEYVALUE_RE.match, stream)
        )
        if event:
            yield event

try:
    UDEVADM = ['/sbin/udevadm', 'monitor', '--udev', '--property']
    with Popen(UDEVADM, stdout=PIPE, encoding='UTF-8') as udevadm:
        for event in events(udevadm.stdout):
            if event['ACTION'] == 'add' and event.get('DRIVER') == 'usb-storage':
                print(event)
                break
except KeyboardInterrupt:
    sys.exit(1)




On macOS, you can get similar information by running and monitoring the output of /usr/sbin/diskutil activity, if you are interested in storage devices.  Look for lines starting with ***DiskAppeared.

from subprocess import Popen, PIPE
import sys

try:
    DISKUTIL = ['/usr/sbin/diskutil', 'activity']
    with Popen(DISKUTIL, stdout=PIPE, encoding='UTF-8') as diskutil:
        # Ignore events that describe the present state
        for line in diskutil.stdout:            
            if line.startswith('***DAIdle'):    
                break
        # Detect the first subsequent "Disk Appeared" event 
        for line in diskutil.stdout:
            if line.startswith('***DiskAppeared'):
                print(line)
                break
except KeyboardInterrupt:
    sys.exit(1)


If you are interested in non-storage devices as well, then you could take advantage of the File System Events API, possibly through the MacFSEvents Python package or the cross-platform fswatch program.