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
1

11 Answers 11

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.

1
  • Are there ways to tell apart an internal SATA drive versus an external SATA drive connected over esatap? Commented Nov 1, 2015 at 9:07
18

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.

3
  • I was looking for this in the answers Commented Apr 4, 2018 at 20:44
  • this is by far the best solution, and can be easily used inside script. Commented Jul 28, 2020 at 5:02
  • with -n to remove headers and it's perfect :)
    – Nayfe
    Commented Nov 18, 2020 at 9:47
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
[...]
0
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.)

3
  • Thanks to @StephaneChazelas for making the first part of my answer more readable. Commented Jan 5, 2013 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 )). Commented May 10, 2017 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. Commented May 11, 2017 at 6: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).

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

2
  • 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
    Commented Jun 6, 2012 at 13:47
  • Well, I said should and it works here with virtual disks. Commented Jun 6, 2012 at 13:52
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)
1
  • dmesg reads the kernel's circular message buffer so this solution will only work relatively recently after a reboot Commented May 10, 2017 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'`"
2
  • 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) Commented May 10, 2017 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. Commented May 10, 2017 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.

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

2
  • 2
    Please provide more information/clarification to make your answer useful.
    – George M
    Commented Jun 6, 2012 at 13:20
  • 2
    How about not being insulting when answering a question? Commented Jun 8, 2012 at 11:30

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .