28

How can I know if /dev/sdX is a local HDD or USB key?  I’d prefer a way of doing this without root privileges.

OK, udevadm helped a lot:

For local HDD:

udevadm info --query=all --name=sdb | grep ID_BUS
E: ID_BUS=ata

For USB key:

udevadm info --query=all --name=sdc | grep ID_BUS
E: ID_BUS=usb
daisy
  • 54,555

11 Answers11

19

There are a few ways to tell without root privileges, many of them tricky/hacky:

Using /dev/disk/by-id:

find /dev/disk/by-id/ -lname '*sdX'

If this responds with something like /dev/disk/by-id/usb-blah-blah-blah, then it's a USB disk. Other prefixes include ata, dm, memstick, scsi, etc.

Using /dev/disk/by-path isn't significantly different:

find /dev/disk/by-path/ -lname '*sdX'

You'll get something like /dev/disk/by-path/pci-0000:00:1d.7-usb-0:1:1.0-scsi-0:0:0:0. This shows the device path leading to the disk. In this case, a rough path is PCI → USB → disk. (note the -usb-).

Using udev (I run Debian. My udevadm is in /sbin which isn't on my $PATH — yours might be elsewhere, on or off your $PATH):

/sbin/udevadm info --query=all --name=sdX | grep ID_BUS

You'll get the bus type the device is on. Remove the | grep ID_BUS for the complete listing of information (you may need to add |less).

If you have lshw installed, Huygens' answer may also work:

lshw -class disk -class storage | less

And look through the output for your disk. In less, try / sdX and look at the preceding, bus info lines — the first one will just say scsi@…, but the one several lines before it will be more enlightening. However, you really should run this as the superuser so it may not be suitable. (symptoms: on the laptop I tried it, it listed the SATA disk but not the USB one — running with sudo listed both)

There are other ones too, more or less direct than these ones.

Alexios
  • 19,157
17

You could use lsblk to report TRAN (device transport type) :

lsblk -do name,tran

NAME TRAN
sda  sata
sdb  sata
sdd  usb

where -dor --nodeps means don't print slaves and -o name,tran or --output name,tran means list only name of device and device transport type. Add rm to the list of output columns to see which devices are removable (1 if true):

lsblk --nodeps --output NAME,TRAN,RM

NAME TRAN   RM
sda  sata    0
sdb  sata    0
sdd  usb     1

or -n to remove headers, e.g. to print only the transport type for a certain drive:

lsblk -ndo tran /dev/sdb

sata

Note that modern versions of lsblk (2.27 and newer) support JSON output so you could also do something like:

lsblk -Jdo name,tran | jq -r '.blockdevices[] | select(.tran=="usb") | .name'

to list only block devices connected on the USB bus.

don_crissti
  • 82,805
10

I know a solution, but, sadly, it requires root privilege.  Anyway, you might still find it useful:

sudo lshw -class disk -class storage

For each device it will print the logical name (e.g., /dev/sda) and bus info, which in case of a USB device would be something like 'usb@1:2'.

Sample output:

[...]
  *-storage
       description: SATA controller
       physical id: d
       bus info: pci@0000:00:0d.0
       configuration: driver=ahci latency=64
[...]
     *-disk:0
          description: ATA Disk
          physical id: 0
          bus info: scsi@2:0.0.0
          logical name: /dev/sda
[...]
  *-scsi
       physical id: 3
       bus info: usb@1:2
       configuration: driver=usb-storage
     *-disk
          description: SCSI Disk
          physical id: 0.0.0
          bus info: scsi@6:0.0.0
          logical name: /dev/sdc
[...]
Huygens
  • 9,345
4

This doesn't need root privileges (but many of these commands use and depend on bashisms, so they will not work in all POSIX-compliant shells):

There is a quick way to ask about a sdX:

grep -H . /sys/block/sda/{capability,uevent,removable,device/{model,type,vendor,uevent}}
/sys/block/sda/capability:52
/sys/block/sda/uevent:MAJOR=8
/sys/block/sda/uevent:MINOR=0
/sys/block/sda/uevent:DEVNAME=sda
/sys/block/sda/uevent:DEVTYPE=disk
/sys/block/sda/removable:0
/sys/block/sda/device/model:WDC WD360GD-00FN
/sys/block/sda/device/type:0
/sys/block/sda/device/vendor:ATA     
/sys/block/sda/device/uevent:DEVTYPE=scsi_device
/sys/block/sda/device/uevent:DRIVER=sd
/sys/block/sda/device/uevent:MODALIAS=scsi:t-0x00

The really interesting file is capability. On my Debian, I have a genhd.h file, so:

eval $(sed -ne '
   s/#define.*GENHD_FL_\([A-Z0-9_]*\)[ \t]*\([0-9]*\) \?.*$/GENHD_FLAGS[\2]="\1"/p
  ' /usr/src/linux-headers-2.6.32-5-common-openvz/include/linux/genhd.h)
diskCapa=$(</sys/block/sda/capability)
for i in ${!GENHD_FLAGS[@]};do
    (( diskCapa & i )) && echo ${GENHD_FLAGS[i]}
  done
MEDIA_CHANGE_NOTIFY
UP
SUPPRESS_PARTITION_INFO

diskCapa=$(</sys/block/sdd/capability)
    for i in ${!GENHD_FLAGS[@]};do
    (( diskCapa & i )) && echo ${GENHD_FLAGS[i]}
  done
REMOVABLE
MEDIA_CHANGE_NOTIFY
UP
SUPPRESS_PARTITION_INFO

At all, for only knowing if flag removable is set:

grep REMOVABL /usr/src/linux-headers-3.2.0-4-common/include/linux/genhd.h 
#define GENHD_FL_REMOVABLE                      1

so

for disk in sd{a,b,c,d,e,f,g,h} ; do
 (( $(< /sys/block/$disk/capability ) & 1 ))  &&  echo $disk is removable
done

works by testing whether the capability value (which is 52 in my sda example, above) has the 1 bit set (i.e., whether it is an odd number).

But Linux renders all flags in /sys, so asking for /sys/block/sdX/removable is a lot simpler! ;-)

So a USB key could be removable, but as there are lots of removable devices, I would prefer to ensure that the size of the medium is greater than 0 (like an unloaded CD-ROM tray, for sample) and that the device is not in use: In watching that sdX/trace/enable is not binded:

Nota: All this is well tested on bash v4.2+.

Under , you could use this very quick and efficient way:

for disk in /sys/block/* ; do
    [ -f "$disk/removable" ]    && [ $(<"$disk/removable") -gt 0 ]   &&
    [ -f "$disk/size" ]         && [ $(<"$disk/size") -gt 0 ]        &&
    [ -f "$disk/trace/enable" ] && [ -z "$(<"$disk/trace/enable")" ] &&
    echo "${disk##*/} $(($(<"$disk/size")/1953125))G $(<"$disk/device/model")"
  done

On my system, there are 4 USB keys, but one of them (sde) is already mounted, so the previous command output:

sdd 8G Trans-It Drive
sdf 7G Storage Media
sdg 4G silicon-power

My script:

There is a little function I wrote to install upgraded Debian Live.

#!/bin/bash

txtsize() {
    local _c=$1 _i=0 _a=(b K M G T P)
    while [ ${#_c} -gt 3 ] ; do
        ((_i++))
        _c=$((_c>>10))
      done
    _c=000$(( ( $1*1000 ) >> ( 10*_i ) ))
    ((_i+=${3:-0}))
    printf -v ${2:-REPLY} "%.2f%s" ${_c:0:${#_c}-3}.${_c:${#_c}-3} ${_a[_i]}
}

# The first part only renders human readable size. The function begins there.

chooseFreeUsbKey() {
    local _lUdisk _lUsize _lUdialog=dialog # whiptail # gdialog
    local -A _lUdevices
    unset ${1:-REPLY}
    for _lUdisk in /sys/block/*; do
        [ -f $_lUdisk/removable ] && [ $(<$_lUdisk/removable) -gt 0 ] &&
        [ -f $_lUdisk/size ] && [ $(<$_lUdisk/size) -gt 0 ] &&
        txtsize $(<$_lUdisk/size)*512 _lUsize &&
        [ -f $_lUdisk/trace/enable ] && [ -z "$(<$_lUdisk/trace/enable)" ] &&
        _lUdevices[${_lUdisk##*/}]="$_lUsize $(<$_lUdisk/device/model)"
    done
    case ${#_lUdevices[@]} in
        0 ) ;; # echo Sorry no key found. ;;
        1 ) IFS=§ read -a ${1:-REPLY} \
            <<< "${!_lUdevices[@]}§${_lUdevices[@]%% *}§${_lUdevices[@]#* }";;
        * ) declare -a menu
           for _lUdisk in ${!_lUdevices[@]}; do
               menu+=($_lUdisk "${_lUdevices[$_lUdisk]}")
           done
           _lUdisk=$($_lUdialog --menu "Choose a USB stick" \
               $((LINES-3)) $((COLUMNS-3)) $((LINES-8)) \
               "${menu[@]}" 2>&1 >/dev/tty)
           IFS=§ read -a ${1:-REPLY} \
           <<< "$_lUdisk§${_lUdevices[$_lUdisk]%% *}§${_lUdevices[$_lUdisk]#* }"
    esac
}

This assigns the answer, as an array, to the variable given as the first argument or to variable $REPLY:

chooseFreeUsbKey stick

echo "$stick"
sdf

echo "${stick[1]}"
7.26G

echo "${stick[2]}"
Storage Media

(The last field may contain spaces.)

  • Thanks to @StephaneChazelas for making the first part of my answer more readable. – F. Hauri - Give Up GitHub Jan 05 '13 at 19:34
  • 1
    (1) Why do you have nested curly braces?  Do you mean …,device/{model,type,vendor,uevent}?  (2) Can you please explain your GENHD commands and your “trace/enable” commands?  (3) Your [ $(( $(< $file ) & 1 )) -ne 0 ] test can be simplified (shortened) to (( $(< $file ) & 1 )). – G-Man Says 'Reinstate Monica' May 10 '17 at 20:40
  • @G-Man (1) Yes, +1 for this! I don't understand how this tipo was introduced. (2) The Generic hard disk header file must be present in /usr/src/*/include/linux. Try sed -ne 's/#define.*GENHD_FL_\([A-Z0-9_]*\)[ \t]*\([0-9]*\) \?.*$/GENHD_FLAGS[\2]="\1"/p;' /usr/src/*/include/linux/genhd.h . (3) Yes. – F. Hauri - Give Up GitHub May 11 '17 at 06:21
1

Just read value of /sys/block/sdX/removable.

For example:

$ cat /sys/block/sda/removable
0
$ cat /sys/block/sdc/removable
1

/dev/sdc is an USB key (it could be a SD card or any other removable media).

0

dmesg is the easiest method:

dmesg | grep sdX

(sdX being the name of your device, e.g., sda)

From the command above, you will see the following:

  • Attached SCSI disk (hard disk)
  • Attached SCSI removable disk (removable media; such as, USB flash drive)
jncc99
  • 59
  • dmesg reads the kernel's circular message buffer so this solution will only work relatively recently after a reboot – Chris Davies May 10 '17 at 22:32
0

You can use the below commands to get SD, USB, and SATA device nodes.

usb_device="/dev/`ls -lR /dev/disk/by-id/  | grep ^l | grep 'usb' | awk '{print $NF}' | cut -d '/' -f 3 | awk 'NR == 1'`"

sata_device="/dev/`ls -lR /dev/disk/by-id/ | grep ^l | grep 'ata' | awk '{print $NF}' | cut -d '/' -f 3 | awk 'NR == 1'`"

sd_device="/dev/`ls -lR /dev/disk/by-id/   | grep ^l | grep 'mmc' | awk '{print $NF}' | cut -d '/' -f 3 | awk 'NR == 1'`"
lovek
  • 1
  • 1
    (1) It’s not obvious to me how this answers the question, which is, “For any particular /dev/sdX, how can I know if it is a local HDD or a USB key?”  Please explain how the OP can use your commands to make that determination.  (2) We prefer answers that give commands and explain them over answers that provide only commands.  Please explain what you’re doing.  Please do not respond in comments; [edit] your answer to make it clearer and more complete. … (Cont’d) – G-Man Says 'Reinstate Monica' May 10 '17 at 20:43
  • (Cont’d) …  (3) awk is a very powerful tool. Many beginners post answers that do a grep and pipe its output into awk. This is rarely necessary; awk can do pattern matching and can select the desired input without help from grep. You have a pipeline of two grep commands, piped into awk, and then a second awk command. This can be greatly simplified; please try. (4) Also, for clarity, you might want to change \…`` to $(…) — see this, this, and this. – G-Man Says 'Reinstate Monica' May 10 '17 at 20:43
0

I'm lazy, inxi tells me this easily:

inxi -D
Drives:    HDD Total Size: 1220.3GB (33.2% used)
           ID-1: /dev/sda model: ST380817AS size: 80.0GB
           ID-2: /dev/sdb model: WDC_WD1003FZEX size: 1000.2GB
           ID-3: USB /dev/sdc model: USB_Flash_Drive size: 140.0GB

I believe it also tells me if it's firewire and maybe one other type, but I'd have to double check, haven't used those types in a while.

It also tells me using -p if partitions are remote, like samba or nfs mounts.

Lizardx
  • 3,058
  • 17
  • 18
0

I suggest just using hdparm or lshw (which you might need to install), and using sudo to execute it as root.

sudo hdparm -I /dev/sda
sudo lshw -short -C disk
sudo lshw -class disk -class storage

should all give you information.

EightBitTony
  • 21,373
  • hdparm on a virtual disk: `hdparm -i /dev/sda

    /dev/sda: HDIO_DRIVE_CMD(identify) failed: Invalid exchange HDIO_GET_IDENTITY failed: Invalid argument`

    – Tim Jun 06 '12 at 13:47
  • Well, I said should and it works here with virtual disks. – EightBitTony Jun 06 '12 at 13:52
0

On Linux, you can get the complete path of any device from sysfs. No privileges needed.

For each block device there is symlink of the form major:minor (in decimal) inside /sys/dev/block pointing to the complete path of the device through all the buses. Same for character devices inside /sys/dev/char. Here is an example that should also work on devices without bash, stat, util-linux, udev, lshw, hdparm, sudo, perl/python, jq, golang, etc:

syspath(){ readlink -f /sys/dev/$(ls -l "$1" | awk -F'[, ]+' '{print ($1~/^c/?"char/":"block/")$5":"$6}'); }

syspath /dev/sda /sys/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb2/2-1/2-1:1.0/host0/target0:0:0/0:0:0:0/block/sda ^^^^

-1

After you plug in the USB device, run dmesg in a console window. You will be provided with some hints.

For example it will says something along the lines of "Device plugged in, mass storage /dev/sdd".

Tim
  • 6,141