4

How would I return (output) the path to any USB flash memory stick(s) connected to the local computer using bash (Ubuntu and Linux Mint)?

Background:

I'm providing users with an automated backup script. (The actual backup software is already installed on their computer.)

The user's job is to plug in a USB flash memory stick and enter one command at the terminal (without any parameters, options or any other variable information).

I need a bash script that can find the path to the USB flash memory stick. If more than one such path is found, I will probably just abort and pop up a message to contact me. Rather than make a complicated script, it is easier for me to just tell them to make sure only one memory stick is plugged into the computer at the time they wish to perform a backup.

4
  • You need something that will run as soon as the USB stick is plugged in, or that the user can run? You might want to look at the udevadm tool... it can be used to monitor the system for udev events, and dumps a bunch of info that you could parse to figure out where the device was just plugged in, and what type of device it is. Commented Mar 12, 2014 at 4:14
  • @rainbowgoblin - it doesn't have to run automatically and it doesn't need to be triggered by the USB stick being plugged in. I only need to find the path when I start my script from the terminal. Commented Mar 12, 2014 at 4:19
  • @MountainX is the script being run from the USB drive? Or is it coming from elsewhere and it needs to mount the drive? Commented Mar 13, 2014 at 3:50
  • @Patrick - The script is not being run from the USB drive. The script will reside on the main internal drive. Thanks Commented Mar 14, 2014 at 4:57

4 Answers 4

5

After plugging in a USB device you can tell what was installed by simply looking at this path:

$ ls -l /dev/disk/by-id/usb*

Example

$ ls -l /dev/disk/by-id/usb*
lrwxrwxrwx. 1 root root  9 Mar 12 01:01 /dev/disk/by-id/usb-JMTek_USBDrive-0:0 -> ../../sdb
lrwxrwxrwx. 1 root root 10 Mar 12 01:01 /dev/disk/by-id/usb-JMTek_USBDrive-0:0-part1 -> ../../sdb1

With the above information your script could simply look at those entries using something like readlink:

$ readlink -f /dev/disk/by-id/usb-JMTek_USBDrive-0:0*
/dev/sdb
/dev/sdb1

And then using the mount command walk backwards to find out what directory the device was automounted under:

$ mount | grep '/dev/sdb\b'
$ mount | grep '/dev/sdb1\b'
/dev/sdb1 on /run/media/saml/HOLA type vfat (rw,nosuid,nodev,relatime,uid=1000,gid=1000,fmask=0022,dmask=0077,codepage=437,iocharset=ascii,shortname=mixed,showexec,utf8,flush,errors=remount-ro,uhelper=udisks2)

This could be expanded to a one liner like this:

$ readlink -f /dev/disk/by-id/usb-JMTek_USBDrive-0:0* | \
    while read dev;do mount | grep "$dev\b" | awk '{print $3}';done
/run/media/saml/HOLA

Getting a device's ID

You can parse this out like so from the output from /dev/disk/by-id/usb*, like so:

$ ls /dev/disk/by-id/usb* | sed 's/.*usb-\(.*\)-[0-9]:.*/\1/'
JMTek_USBDrive
JMTek_USBDrive

Incidentally this information is a concatenation of the USB's manufacturer + product descriptions.

$ usb-devices
...
T:  Bus=02 Lev=02 Prnt=02 Port=01 Cnt=02 Dev#= 10 Spd=12  MxCh= 0
D:  Ver= 1.10 Cls=00(>ifc ) Sub=00 Prot=00 MxPS= 8 #Cfgs=  1
P:  Vendor=058f ProdID=9380 Rev=01.00
S:  Manufacturer=JMTek
S:  Product=USBDrive
C:  #Ifs= 1 Cfg#= 1 Atr=80 MxPwr=100mA
I:  If#= 0 Alt= 0 #EPs= 2 Cls=08(stor.) Sub=06 Prot=50 Driver=usb-storage
...

You can also access it this way once you've established which device (/dev/sd*) the USB device is using, through UDEV:

$ udevadm info --query=all --name=sdb | grep -E "MODEL=|VENDOR=|ID_SERIAL"
E: ID_MODEL=USBDrive
E: ID_SERIAL=JMTek_USBDrive-0:0
E: ID_VENDOR=JMTek
4
  • thanks (not only for this answer, but for all your answers!) Unfortunately, I couldn't quite get this to work. (For example, I would need to automate the picking up of the device name, i.e., the JMTek_USBDrive in your example.) I'm going to try again later today when I have more time. I am also going to try this solution: unix.stackexchange.com/a/60335/15010 Commented Mar 12, 2014 at 13:25
  • Thanks for updating with the additional info. I'm still getting the error I was getting previously: readlink: extra operand `/dev/disk/by-id/usb-JMTek_USBDrive-0:0-part1' Upon that error it fails to provide this info: /dev/sdb1. Commented Mar 13, 2014 at 1:46
  • 1
    @MountainX - it looks like an issue with readlink on Ubuntu, it can only take 1 argument, so the you need to give it each path that matches the /dev/disk/by-id/usb* wildcard individually, vs. giving multiples to readlink. Try that and see if it gets rid of the extra operand msg. Commented Mar 13, 2014 at 2:18
  • I guess my bash skills are too poor to get this sorted out on Ubuntu... no luck yet. Commented Mar 14, 2014 at 3:02
2

You can write a script to go through /etc/mtab and look at mounted devices, then use udevadm to check whether they're USB devices. /etc/mtab includes both the name of the device in /dev and its mount point. So you could do something like:

IFS=$'\n'
for mtabline in `cat /etc/mtab`; do 
  device=`echo $mtabline | cut -f 1 -d ' '`
  udevline=`udevadm info -q path -n $device 2>&1 |grep usb` 
  if [ $? == 0 ] ; then
    devpath=`echo $mtabline | cut -f 2 -d ' '`
    echo "devpath: $devpath"
  fi
done

(You need to set IFS in your script so that mtab is read line by line, rather than "word" by "word").

6
  • Why don't you just use while read line; do ...;done < /etc/mtab? That way you don't need to fiddle with $IFS. Commented Mar 12, 2014 at 10:23
  • Ubuntu isn't giving me a proper filesystem path; instead, udevadm is returning something like /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/host11/target11:0:0/11:0:0:0/block/sdd/sdd1. I plan to try again later today when I have more time to test. I am also going to try this solution: unix.stackexchange.com/a/60335/15010 Commented Mar 12, 2014 at 13:23
  • @MountainX Yes... the udevadm line was just to filter for USB devices. If it's a USB device, you'll see "usb" somewhere in the output, so grep will return that line and the value of $? (the return value of the last command) will be zero. You actually get the path from the devpath=... line (i.e., it's also extracted from the mtabline variable). Not very elegant, but it works for me. Commented Mar 13, 2014 at 22:49
  • OK, it works for me too. So far yours is the only answer that has worked for me. But I'm still trying to understand and potentially tweak the other approaches before giving up on them. Commented Mar 14, 2014 at 2:47
  • Glad it works. There are lots of ways to do this, I guess. Incidentally, I think my solution would be nicer if I'd used awk instead of cut (you could get both the device's location in /dev and mount point out of mtab at once, plus awk is just a classier way to do things). But I'm lousy at awk. Commented Mar 14, 2014 at 5:32
0

On my desktop with no usb flash devices present:

% lsblk -o tran,name,mountpoint
> TRAN   NAME   MOUNTPOINT
> sata   sda
>        ├─sda1 /esp
>        ├─sda2 /
>        ├─sda3 /mnt/sda3
>        ├─sda4
>        └─sda5
> sata   sdb
> sata   sdc
> sata   sr0

On my laptop with two:

% lsblk -o tran,name,mountpoint
> TRAN   NAME   MOUNTPOINT
> usb    sda
>        ├─sda1 /esp
>        └─sda2 /
> usb    sdb
>        └─sdb1 /home
> sata   sr0

That should be all you need.

So some others also told me that some debian family systems do not yet support the transport option. In that case, try this:

    ( set -- $(lsblk -ndo name,rm)
        while [ $# -ge 2 ] ; do {
            [ $2 -eq 1 ] && \
                udevadm info --query=all --name "/dev/$1"
        shift 2 
     } ; done )   

The above command first queries lsblk for a list of current (parent-only - so only sda and not sda1) block devices by name and a column for the removable flag. The output looks like:

sda 0
sdb 1
sdc 1

Only the devices flagged with 1 are removable.

So we set our ( subshell's ) positional parameters to the split content which makes two parameters per entry. while we have at least 2 positional parameters we[ test ] $2 for the 1 removable flag, && and if present we query udevadm the system's device manager for all information it has on our first positional parameter, or /dev/$1. Next shift our first 2 positional parameters away and start over with the next two.

udevadm provides a lot of info on devices you might be interested in, but if you also really just want to get straight to the point, you could add the following |pipe to udevadm's stdout:

    udevadm info --name "/dev/$1" |\
        grep -q ID_BUS=usb && echo "/dev/$1"

This much would provide you a list of only parent /dev/$DEV removable usb block devices currently present in the system in the format:

/dev/$DEV /dev/$ALSODEV

If at this point you were interested only in mount points of mounted partitions of the above provided parent links you could roll it all together with findmnt like this:

( set -- $(lsblk -ndo name,rm)
    while [ $# -ge 2 ] ; do {
        [ $2 -eq 1 ] &&
            udevadm info --query=all --name "/dev/$1" |\
            grep -q ID_BUS=usb &&
                 printf 'findmnt %s -no TARGET ;' "/dev/$1" /dev/"$1"[0-9]
        shift 2 ; } ; done ) |\ 
    . /dev/stdin

This should provide you a list like:

/removable/usb/device/mount/point/1
/ditto/2
/also/3
11
  • You might want to add further details or an example to this, so that's it more obvious. Commented Mar 12, 2014 at 15:38
  • Yeah, I guess I figured lsblk would be obvious enough, but if you insist. Commented Mar 12, 2014 at 16:11
  • Thanks, we like the A's to be more detailed than on most of the other SE sites if you haven't noticed 8-) Commented Mar 12, 2014 at 16:35
  • 1
    Me too. Just got lazy. Commented Mar 12, 2014 at 16:55
  • I get lsblk: unknown column: tran,name,mountpoint on Ubuntu 12.04 Commented Mar 13, 2014 at 0:56
0

Here's the approach I'm using. It comes from:

https://unix.stackexchange.com/a/119782/15010
https://unix.stackexchange.com/a/60335/15010

export USBKEYS=($(
    for blk in $(lsblk -ndo name) ; do {
        udevadm info --query=all --name "/dev/$blk" |\
        grep -q ID_BUS=usb &&
            printf 'findmnt %s -no TARGET ;'\
                "/dev/$blk" /dev/"$blk"[0-9]
        } ; done 2>&- |. /dev/stdin 
))
echo "$USBKEYS"
export STICK
case ${#USBKEYS[@]} in
    0 ) echo "No USB stick found."; exit 0 ;;
    1 ) STICK=$USBKEYS; echo "STICK=$USBKEYS" ;;
    * ) NOTE: the code for this case is not included in the interest of brevity.
esac

This is also working for me. It mostly comes from https://unix.stackexchange.com/a/119260/15010

#!/bin/bash

while read mtabline
do
  device=`echo $mtabline | awk '{print $1}'`
  udevline=`udevadm info -q path -n $device 2>&1 |grep usb` 
  if [ $? == 0 ] ; then
    echo "$device"
  fi
done < /etc/mtab

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.