22

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.

  • 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? – 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
22

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

  • 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 '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
  • 3
    You can also add ACTION=="add", directly to the rule definition. – Avindra Goolcharan Jul 30 '16 at 15:19
3

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 '18 at 1:52
1

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
    Looks like a nice script, +1. One thing I'd suggest is using list instead of just one string in call(). That way if it's necessary to provide arguments to the foobar.sh script, you can do that dynamically. – Sergiy Kolodyazhnyy Dec 23 '18 at 9:41
  • 1
    Fair point. My "real" script (linked from the answer) uses a list. In this minimalist version I pasted here, I messed up and accidentally used a string. Thanks! I've updated the answer. – Denilson Sá Maia Dec 24 '18 at 11:30
-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.

  • 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 '18 at 13:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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