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

11 Answers 11


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.

  • Are there ways to tell apart an internal SATA drive versus an external SATA drive connected over esatap? Nov 1 '15 at 9:07

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

lsblk -do 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

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


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.

  • I was looking for this in the answers Apr 4 '18 at 20:44
  • this is by far the best solution, and can be easily used inside script. Jul 28 '20 at 5:02
  • with -n to remove headers and it's perfect :)
    – Nayfe
    Nov 18 '20 at 9:47

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:

       description: SATA controller
       physical id: d
       bus info: pci@0000:00:0d.0
       configuration: driver=ahci latency=64
          description: ATA Disk
          physical id: 0
          bus info: scsi@2:0.0.0
          logical name: /dev/sda
       physical id: 3
       bus info: usb@1:2
       configuration: driver=usb-storage
          description: SCSI Disk
          physical id: 0.0.0
          bus info: scsi@6:0.0.0
          logical name: /dev/sdc

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/device/model:WDC WD360GD-00FN

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)
for i in ${!GENHD_FLAGS[@]};do
    (( diskCapa & i )) && echo ${GENHD_FLAGS[i]}

    for i in ${!GENHD_FLAGS[@]};do
    (( diskCapa & i )) && echo ${GENHD_FLAGS[i]}

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


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

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

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.


txtsize() {
    local _c=$1 _i=0 _a=(b K M G T P)
    while [ ${#_c} -gt 3 ] ; do
    _c=000$(( ( $1*1000 ) >> ( 10*_i ) ))
    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)"
    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]}")
           _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]#* }"

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

chooseFreeUsbKey stick

echo "$stick"

echo "${stick[1]}"

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
    Jan 5 '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 )). 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
    May 11 '17 at 6:21

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.

  • 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 6 '12 at 13:47
  • Well, I said should and it works here with virtual disks. Jun 6 '12 at 13:52

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

For example:

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

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


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)
  • 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

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'`"
  • 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) 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. May 10 '17 at 20:43

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.


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

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
    Please provide more information/clarification to make your answer useful.
    – George M
    Jun 6 '12 at 13:20
  • 2
    How about not being insulting when answering a question?
    – Shadur
    Jun 8 '12 at 11:30

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