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.

migrated from stackoverflow.com Feb 23 '13 at 20:45

This question came from our site for professional and enthusiast programmers.

  • 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? – Gilles Feb 22 '13 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 '13 at 16:12
up vote 18 down vote accepted

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
...

  • 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 '13 at 16:20
  • @Redsandro This was not intentional, just for, well, testing purposes. Anyway, I'm glad it helped. ;-) – Olaf Dietsche Feb 22 '13 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 '13 at 9:56
  • 2
    You can also add ACTION=="add", directly to the rule definition. – Avindra Goolcharan Jul 30 '16 at 15:19

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 scrpit 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 scirpt takes some time to execute (which can easely 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 listen to a named pipe and that 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 exists, 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 red 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 frome fifo : [$line]"
        fi
    fi
done
echo "Reader exiting"

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

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

  • 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? – jamescampbell Oct 27 at 1:52

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.

  • 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. – Gert van den Berg Aug 24 at 13:51

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.