27

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
  • 53,527
  • 78
  • 236
  • 383

11 Answers11

18

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
  • 18,757
  • 3
  • 57
  • 74
16

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
  • 79,330
  • 30
  • 216
  • 245
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
  • 8,985
  • 3
  • 31
  • 36
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).

  • This duplicates information in [F. Hauri’s answer](https://unix.stackexchange.com/q/40143/80216#60339) and adds no new content.  Please don’t post an answer unless you can add new aspects not found in any existing answers. – G-Man Says 'Reinstate Monica' May 10 '17 at 20:42
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
  • 4
  • `dmesg` reads the kernel's circular message buffer so this solution will only work relatively recently after a reboot – roaima 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
  • 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) – 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](//unix.stackexchange.com/q/5778/80216), [this](//unix.stackexchange.com/q/147838/80216), and [this](//unix.stackexchange.com/q/104119/80216). – 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
  • 2,990
  • 16
  • 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
  • 20,963
  • 4
  • 61
  • 62
  • 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,113
  • 1
  • 18
  • 19