48

I want to execute a script when I plug in a device in my Linux machine. For example, run xinput on mouse or a backupscript on a certain drive.

I have seen a lot of articles on this, most recently here and here. But I just can't get it to work.

Here's some simple examples trying to get at least some kind of response.

/etc/udev/rules.d/test.rules

#KERNEL=="sd*", ATTRS{vendor}=="*", ATTRS{model}=="*", ATTRS{serial}=="*", RUN+="/usr/local/bin/test.sh"
#KERNEL=="sd*", ACTION=="add", "SUBSYSTEM=="usb", ATTRS{model}=="My Book 1140    ", ATTRS{serial}=="0841752394756103457194857249", RUN+="/usr/local/bin/test.sh"
#ACTION=="add", "SUBSYSTEM=="usb", RUN+="/usr/local/bin/test.sh"
#KERNEL=="sd*", ACTION=={add}, RUN+="/usr/local/bin/test.sh"
KERNEL=="sd*", RUN+="/usr/local/bin/test.sh"
KERNEL=="*", RUN+="/usr/local/bin/test.sh"

/usr/local/bin/test.sh

#!/usr/bin/env bash
echo touched >> /var/log/test.log

if [ "${ACTION}" = "add" ] && [ -f "${DEVICE}" ]
then
    echo ${DEVICE} >> /var/log/test.log
fi

The rules folder is watched by inotify and should be active immediately. I keep replugging my keyboard, mouse, tablet, memorystick and usb-drive, but nothing. No log file touched.

Now, what would be the most simple way to at least know something is working? It's easier to work from something that's working than from something that's not.

2
  • 1
    Didn't you mean to post on Unix & Linux? What's your kernel version? Did you run udevadm trigger or plug a device in to apply the new rule? Feb 22, 2013 at 15:41
  • Yes, I do that after every edit of the rules to try them out. I edited the question accordingly. This is the way udev works for a while now, but I am running 3.5.0-23-generic.
    – Redsandro
    Feb 22, 2013 at 16:12

4 Answers 4

41

If you want to run the script on a specific device, you can use the vendor and product ids

  • In /etc/udev/rules.d/test.rules:

    ATTRS{idVendor}=="152d", ATTRS{idProduct}=="2329", RUN+="/tmp/test.sh"
    
  • in test.sh:

    #! /bin/sh
    
    env >>/tmp/test.log
    file "/sys${DEVPATH}" >>/tmp/test.log
    
    if [ "${ACTION}" = add -a -d "/sys${DEVPATH}" ]; then
    echo "add ${DEVPATH}" >>/tmp/test.log
    fi
    

With env, you can see what environment is set from udev and with file, you will discover the file type.

The concrete attributes for your device can be discovered with lsusb

lsusb

gives

...
Bus 001 Device 016: ID 152d:2329 JMicron Technology Corp. / JMicron USA Technology Corp. JM20329 SATA Bridge
...

6
  • 1
    This is interesting! It seems that it has no permission to write to /log/. It does write to /tmp/. I guess it had no permission to read my previous testscripts either.
    – Redsandro
    Feb 22, 2013 at 16:20
  • @Redsandro This was not intentional, just for, well, testing purposes. Anyway, I'm glad it helped. ;-) Feb 22, 2013 at 16:24
  • I would like to encourage you to also check out this question and see if your knowledge can be valuable there. :)
    – Redsandro
    Apr 16, 2013 at 9:56
  • 4
    You can also add ACTION=="add", directly to the rule definition. Jul 30, 2016 at 15:19
  • Huh. So lsusb provides idVendor:idProduct in its output (per example rules file), but that same information is referred to as ID_VENDOR_ID and ID_MODEL_ID in the output of udevadm info --query=all /device/path. The naming inconsistency is confusing. Are model and product considered different info?
    – sherrellbc
    Aug 4, 2021 at 18:11
11

This isn't directly about your question but about what you're doing. If you start a backup script from udev you will face two main issues :

  1. Your script might be started before the device is ready and can be mounted, you have to keep the KERNEL=="sd*" condition if you want to use the /dev node to mount it
  2. More important, if your script takes some time to execute (which can easily be the case with a backup script) it will be killed shortly after it is started (about 5s)
  3. You will face many complicated user permission issues

My advice is to create a script in your user home which listens to a named pipe and which will be started asynchronously like :

#!/bin/bash

PIPE="/tmp/IomegaUsbPipe"
REMOTE_PATH="/path/to/mount/point"
LOCAL_PATH="/local/path/"


doSynchronization()
{
  #your backup here
}

trap "rm -f $PIPE" EXIT

#If the pipe doesn't exist, create it
if [[ ! -p $PIPE ]]; then
    mkfifo $PIPE
fi

#If the disk is already plugged on startup, do a syn
if [[ -e "$REMOTE_PATH" ]]
then
    doSynchronization
fi

#Make the permanent loop to watch the usb connection
while true
do
    if read line <$PIPE; then
        #Test the message read from the fifo
        if [[ "$line" == "connected" ]]
        then
            #The usb has been plugged, wait for disk to be mounted by KDE
            while [[ ! -e "$REMOTE_PATH" ]]
            do
                sleep 1
            done
            doSynchronization
        else
            echo "Unhandled message from fifo : [$line]"
        fi
    fi
done
echo "Reader exiting"

Note : I use auto-mount with kde so I check for the folder to appear. You can pass the /dev/sd* parameter in the fifo from the udev rule and mount it yourself in the script. To write to the fifo don't forget that udev is not a shell and that redirection doesn't work. Your RUN should be like :

RUN+="/bin/sh -c '/bin/echo connected >> /tmp/IomegaUsbPipe'"

5
  • Great use of named pipes here. I was wondering you could also just create an arbitrary file in tmp and look for it as well instead of a named pipe, correct?
    – james-see
    Oct 27, 2018 at 1:52
  • 2
    @jamescampbell The pipe allows you to do a read which blocks until something is written to the pipe. You use 0 CPU cycles during that read which is way better than checking for the existance of a file which would require a sleep (i.e. a polling solution) and the detection could happen seconds later if you use a long sleep. Feb 26, 2021 at 19:01
  • @AlexisWilke thank you that is super useful to know why that pipe is better
    – james-see
    Feb 26, 2021 at 19:31
  • With disks automounting, code like while [[ ! -e "$REMOTE_PATH" ]]; do sleep 1 seems to use 0 CPU as well. Since just waiting for the path works. are there still good reasons to use a pipe?
    – Al F
    Apr 12, 2022 at 8:32
  • @AlF How is that zerp cycles? "If REMOTE_PATH does not exist execute command sleep 1. and then try again" that is a loop with a relatively expensive check for existing file. If sleep isn't internal to shell, it will even start a new process.
    – NiKiZe
    Nov 5, 2022 at 8:26
5

I've posted a solution on https://askubuntu.com/a/516336 and I'm also copy-pasting the solution over here.

I wrote a Python script using pyudev that I leave running in the background. That script listens to udev events (thus, it's very efficient) and runs whatever code I want. In my case, it runs xinput commands to setup my devices (link to the most recent version).

Here's a short version of the same script:

#!/usr/bin/env python3

import pyudev
import subprocess

def main():
    context = pyudev.Context()
    monitor = pyudev.Monitor.from_netlink(context)
    monitor.filter_by(subsystem='usb')
    monitor.start()

    for device in iter(monitor.poll, None):
        # I can add more logic here, to run different scripts for different devices.
        subprocess.call(['/home/foo/foobar.sh', '--foo', '--bar'])

if __name__ == '__main__':
    main()
1
-1

To run the script on the boot when usb device is inserted I use solution below:

Format the pendrive or any other usb storage and give it a name when doing so. Then in /etc/rc.local add line ls -q /dev/disk/by-label > /home/pi/label.txt

it will create a txt file called label.txt ( can be any other name )

then again in /etc/rc.local add another 2 lines:

if  grep -q USB_drive_name /home/pi/label.txt; then
sudo /home/pi/script.sh

Now everytime pendrive with name USB_drive_name is inserted it will run the script.

With a few little modifications above solution can be used when system is up and running.

1
  • 2
    Does not answer the question: This only covers boot-time (and using udev for other times are not a "few little modifications") and the Raspberry Pi. There is an unnecessary sudo - rc.local runs as root, it is a privilege escalation issue - a file that is editable by a normal user is run as root. Aug 24, 2018 at 13:51

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .