Como executar um shellscript ao conectar um dispositivo USB

28

Quero executar um script quando conectar um dispositivo na minha máquina Linux. Por exemplo, execute xinputno mouse ou em um backupscript em uma determinada unidade.

Eu já vi muitos artigos sobre isso, mais recentemente aqui e aqui . Mas eu simplesmente não consigo fazê-lo funcionar.

Aqui estão alguns exemplos simples tentando obter pelo menos algum tipo de resposta.

/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

A pasta de regras é monitorada inotifye deve estar ativa imediatamente. Eu continuo recarregando meu teclado, mouse, tablet, cartão de memória e drive USB, mas nada. Nenhum arquivo de log foi tocado.

Agora, qual seria a maneira mais simples de pelo menos saber que algo está funcionando? É mais fácil trabalhar com algo que está funcionando do que com algo que não está.

Redsandro
fonte
11
Você não quis publicar no Unix e Linux ? Qual é a sua versão do kernel? Você executou udevadm triggerou conectou um dispositivo para aplicar a nova regra?
Gilles 'SO- stop be evil'
Sim, faço isso após cada edição das regras para testá-las. Eu editei a pergunta de acordo. É assim que o udev funciona por um tempo agora, mas estou executando 3.5.0-23-generic.
Redsandro 22/02

Respostas:

24

Se você deseja executar o script em um dispositivo específico, pode usar os IDs do fornecedor e do produto

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

    ATTRS{idVendor}=="152d", ATTRS{idProduct}=="2329", RUN+="/tmp/test.sh"
  • em 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

Com env, você pode ver qual ambiente está definido no udev e com file, descobrirá o tipo de arquivo.

Os atributos concretos do seu dispositivo podem ser descobertos com lsusb

lsusb

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

Olaf Dietsche
fonte
11
Isto é interessante! Parece que não tem permissão para gravar em / log /. Ele faz gravação para / tmp /. Eu acho que também não tinha permissão para ler meus scripts de teste anteriores.
Redsandro 22/02
@Redsandro Isso não foi intencional, apenas para, bem, fins de teste. De qualquer forma, estou feliz que tenha ajudado. ;-)
Olaf Dietsche 22/02
Gostaria de encorajá-lo a também verificar esta questão e ver se seu conhecimento pode ser valioso lá. :)
Redsandro
3
Você também pode adicionar ACTION=="add",diretamente à definição de regra.
Avindra Goolcharan 30/07/16
4

Não se trata diretamente da sua pergunta, mas do que você está fazendo. Se você iniciar um script de backup no udev, enfrentará dois problemas principais:

  1. Seu scrpit pode ser iniciado antes que o dispositivo esteja pronto e possa ser montado. Você deve manter a condição KERNEL == "sd *" se desejar usar o nó / dev para montá-lo
  2. Mais importante, se o seu scirpt demorar algum tempo para ser executado (o que pode ser facilmente o caso de um script de backup), ele será eliminado logo após ser iniciado (cerca de 5s)
  3. Você enfrentará muitos problemas complicados de permissão do usuário

Meu conselho é criar um script em sua página inicial do usuário que ouça um pipe nomeado e que seja iniciado de forma assíncrona como:

#!/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"

Nota: Eu uso a montagem automática com o kde, para verificar se a pasta aparece. Você pode passar o parâmetro / dev / sd * no fifo da regra udev e montá-lo você mesmo no script. Para escrever no fifo, não esqueça que o udev não é um shell e que o redirecionamento não funciona. Seu RUN deve ser como:

RUN + = "/ bin / sh -c '/ bin / eco conectado >> / tmp / IomegaUsbPipe'"

Olivier Laporte
fonte
Grande uso de pipes nomeados aqui. Eu queria saber que você também pode apenas criar um arquivo arbitrário em tmp e procurá-lo também em vez de um pipe nomeado, correto?
jamescampbell
1

Publiquei uma solução em /ubuntu//a/516336 e também estou copiando e colando a solução aqui.

Eu escrevi um script Python usando pyudev que deixo sendo executado em segundo plano. Esse script ouve eventos do udev (portanto, é muito eficiente) e executa o código que eu quiser. No meu caso, ele executa xinputcomandos para configurar meus dispositivos ( link para a versão mais recente ).

Aqui está uma versão curta do mesmo 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()
Denilson Sá Maia
fonte
11
Parece um bom roteiro, +1. Uma coisa que eu sugiro é usar a lista em vez de apenas uma string call(). Dessa forma, se for necessário fornecer argumentos para o foobar.shscript, você poderá fazer isso dinamicamente.
Sergiy Kolodyazhnyy
11
Ponto justo. Meu script "real" (vinculado à resposta) usa uma lista. Nesta versão minimalista que colei aqui, errei e acidentalmente usei uma string. Obrigado! Eu atualizei a resposta.
Denilson Sá Maia
-1

Para executar o script na inicialização quando o dispositivo USB é inserido, eu uso a solução abaixo:

Formate o pendrive ou qualquer outro armazenamento USB e atribua um nome ao fazê-lo. Em seguida, /etc/rc.local adicione linhals -q /dev/disk/by-label > /home/pi/label.txt

ele criará um arquivo txt chamado label.txt (pode ser qualquer outro nome)

depois, em /etc/rc.local, adicione outras 2 linhas:

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

Agora, sempre que o pendrive com o nome USB_drive_name for inserido, ele executará o script.

Com algumas pequenas modificações acima, a solução pode ser usada quando o sistema estiver em funcionamento.

Aleksander Celewicz
fonte
Não responde à pergunta: Isso cobre apenas o tempo de inicialização (e o uso udevpara outros momentos não são "algumas pequenas modificações") e o Raspberry Pi. Há um desnecessário sudo- rc.localé executado como root, é um problema de escalonamento de privilégios - um arquivo que é editável por um usuário normal é executado como root.
Gert van den Berg