Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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.

share|improve this question
    
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. –  rainbowgoblin Mar 12 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. –  MountainX Mar 12 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? –  Patrick Mar 13 at 3:50
    
@Patrick - The script is not being run from the USB drive. The script will reside on the main internal drive. Thanks –  MountainX Mar 14 at 4:57

4 Answers 4

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

share|improve this answer
    
Why don't you just use while read line; do ...;done < /etc/mtab? That way you don't need to fiddle with $IFS. –  terdon Mar 12 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/1‌​1: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 –  MountainX Mar 12 at 13:23
    
@terdon thanks for the tip. –  rainbowgoblin Mar 13 at 22:46
    
@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. –  rainbowgoblin Mar 13 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. –  MountainX Mar 14 at 2:47

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
share|improve this answer
    
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 –  MountainX Mar 12 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. –  MountainX Mar 13 at 1:46
    
@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. –  slm Mar 13 at 2:18
    
I guess my bash skills are too poor to get this sorted out on Ubuntu... no luck yet. –  MountainX Mar 14 at 3:02

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
share|improve this answer
    
You might want to add further details or an example to this, so that's it more obvious. –  slm Mar 12 at 15:38
    
Yeah, I guess I figured lsblk would be obvious enough, but if you insist. –  mikeserv Mar 12 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-) –  slm Mar 12 at 16:35
1  
Me too. Just got lazy. –  mikeserv Mar 12 at 16:55
    
I get lsblk: unknown column: tran,name,mountpoint on Ubuntu 12.04 –  MountainX Mar 13 at 0:56

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

http://unix.stackexchange.com/a/119782/15010
http://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 http://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
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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