How to execute a shellscript when I plug-in a USB-device

1,155

Solution 1

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

Solution 2

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'"

Solution 3

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()
Share:
1,155
Peter Anthony
Author by

Peter Anthony

Updated on September 18, 2022

Comments

  • Peter Anthony
    Peter Anthony almost 2 years

    I'm looking for a very simple form processing API for Java. Assuming the form input fields correspond to bean properties, and all beans have javax.Validation annotations, ideally the API would:

    • Display a bean as an html form
    • Populate the bean, including nested objects where applicable, using the request parameters
    • Validate the input using Validation annotation
    • If there is an error, display errors at top of form, and highlight error fields.

    Additionally:

    • It would be nice if i didn't have to buy into a whole application framework, since I am working with a legacy app.
    • Allow configuration for more complicated use cases, but by default just use convention.

    Bonus:

    • generates javascript client side validation too.

    Note: If this requires several different libraries, that is fine too.

    Update:

    Since I never found what I was looking for, and migrating to Spring was not an option, I went ahead and rolled my own solution. It is, affectionately, called java in jails (loosely modeled on rails form processing). It gives you dead simple (and pretty) form creation, client and server side validation, and request parameter to object mapping. No configuration required.

    Example Bean:

    public class AccountForm {
        @NotBlank(groups = RequiredChecks.class)
        @Size(min = 2, max = 25)
        private String name;
    //...
    }
    

    Example Form:

    <%@ taglib uri="http://org.jails.org/form/taglib" prefix="s" %>
    <s:form name="accountForm" action="/jails-demo/jails" label="Your Account Details" style="side">
        <s:text name="name" label="Name" size="25" />
        <s:text name="accountName" label="Account Name" size="15" />
        ...
    </s:form>
    

    Example Validation and Mapping:

    SimpleValidator validator = new SimpleValidator();
    
    if ("submit".equals(request.getParameter("submit"))) {
        Map<String, List<String>> errors = validator.validate(AccountForm.class, request.getParameterMap());
    
        if (errors != null) {
            AccountForm account = validator.getMapper().toObject(AccountForm.class, request.getParameterMap());
            //do something with valid account
        } else {
            SimpleForm.validateAs(AccountForm.class).inRequest(request).setErrors(errors);
            //handle error
        }
    } else {
        SimpleForm.validateAs(AccountForm.class).inRequest(request);
        //forward to formPage
    }
    

    This is what the form looks like, with client side validation using jQuery (provided by Position Absolute):

    enter image description here

    • CoolBeans
      CoolBeans almost 13 years
      we usually do not provide complete solutions like you have asked above. Why don't you start developing your "form" and then when you have more specific coding questions we can help you more?
    • Peter Anthony
      Peter Anthony over 12 years
      Thanks for the offer... I actually wasn't looking for coding help, I was looking for a n existing solution that I could just plug into.
  • Peter Anthony
    Peter Anthony almost 13 years
    BeanUtils.populate(bean, request.getParameterMap())... that's half the battle. does it come with stock converters for String->Primitive->String or do yu have to write your own? unfortunately, writing the HTML was what the other thing I was most interested in trying to get around not having to do myself!
  • Peter Anthony
    Peter Anthony almost 13 years
    scatch that. i see that there are stock converters for all the standard use cases, and you can create your own as needed. thanks again!
  • Redsandro
    Redsandro over 11 years
    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.
  • Olaf Dietsche
    Olaf Dietsche over 11 years
    @Redsandro This was not intentional, just for, well, testing purposes. Anyway, I'm glad it helped. ;-)
  • Redsandro
    Redsandro about 11 years
    I would like to encourage you to also check out this question and see if your knowledge can be valuable there. :)
  • Avindra Goolcharan
    Avindra Goolcharan almost 8 years
    You can also add ACTION=="add", directly to the rule definition.
  • Gert van den Berg
    Gert van den Berg almost 6 years
    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.
  • jamescampbell
    jamescampbell over 5 years
    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?
  • Alexis Wilke
    Alexis Wilke over 3 years
    @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.
  • jamescampbell
    jamescampbell over 3 years
    @AlexisWilke thank you that is super useful to know why that pipe is better
  • Louis Kröger
    Louis Kröger almost 3 years
    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?
  • Olaf Dietsche
    Olaf Dietsche almost 3 years
    @sherrellbc I don't know. But consider, that lsusb is specific to USB, while udevadm is more general and covers a wider range of device types.
  • Al F
    Al F about 2 years
    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?
  • Admin
    Admin about 2 years
    thanks to your answer I was able to create solution that detects when I connect my USB keyboard and it changes the layout automatically - askubuntu.com/a/1414424/94200