0

I want to write simple linux script which will display what are the real size of installed hard drive. If there is 128GB SSD, lsblk shows less than 128. I need to check the real size of memory and print it like this: 128GB installed = script gives output

128

If there are two hard drives installed, it gives output

128/512

It should work on both sata and nvme drives

7
  • 4
    You need to understand the difference between Gigabytes and Gibibytes. A 128 GB harddrive is 119 GiB (Gibibytes), which is usually what is reported by the OS. It's not clear what you mean by "real size" - there is no such thing as "fake size" AFAIK. Jul 26, 2021 at 9:48
  • Yes, I know that, sorry. I mean Gigabytes, "full" size of harddrive Jul 26, 2021 at 9:53
  • The full size is the same, no matter what unit you report it in. There's also tools to convert between units. Jul 26, 2021 at 9:54
  • I understand it.. just need a command to display harddrives size in GB (not GiB) Jul 26, 2021 at 9:59
  • 1
    lsblk -b shows the size in bytes - which you can then extract and convert to your liking. Jul 26, 2021 at 10:40

3 Answers 3

1

It requires sudo to use but it works:

sudo lshw | grep -Pzo "\*-(disk|namespace)(\n.*)+?\s+size:.*?\(\K\d+\w+" | tr "\0" "\n" | paste -sd/

It gives the output like this:

1TB/750GB

Otherwise you wouldn't know if it was TB/GB/MB so that's why I kept those.

EDIT: I noticed an .* in the regex wasn't needed. The command above has been updated.

Explanation:

sudo lshw: well... list the hardware. We need sudo to see the drives in the system. This will be our source of info.

grep -Pzo: we're going to use regex to get the required info

-P = activate perl regex
-z = treat everything as one long line; this is required because we're going to use regex over multiple lines
-o = instead of showing the output and marking the result red, just show the result

regex (tip: remove the -o (a.k.a -Pz) and add the regexes below one by one to see what happens step by step; the red text is what the regex matched, so you can see step by step how we're getting closer to the desired result and what every step changes to the output):

\*-(disk|namespace) = Find all text that is "*-disk" or "*-namespace". We need to escape (= \) the "*" because in regex it means zero or more, but we don't want that, we want to search for a literal "*".
(\n.*)+ = Keep adding (the "+" = one or more) lines ("\n" = go to the next line; ".*" = everything on that line) to the matched text; you'll see that now everything under the first match of "\*-(disk|namespace)" is red.
?\s+size: = we keep adding lines until we come across the first ("?" = non-greedy a.k.a. the first match instead of the last match) match of one or more whitespaces (= "\s+"; "\s" is whitespace (tab, space, etc.); "+" is one or more) and then "size:"; you'll see that were coming closer to the desired number in the output.
.* = match the rest of that line,
?\( = until the first match (= ?) of a "(", which we need to escape because it is used in regex (you can see it being used in the first part of the regex here).
\K\d+\w+ = match a number (= \d) one or more times (= "+") and after that a word character (= \w) one or more times (= "+"). We now have the desired text in the match, but we don't want all the matched text before that in the output, so we put a "\K" before the desired text to remove the matched text before the "\K" from the output. It is still required to match, it just isn't included in the output. This makes all the regex before it a positive lookbehind (look it up; "positive lookbehind perl regex") with regex capabilities.

tr "\0" "\n": You'll see that we have the desired text matched and nothing more or less. When we add the -o again, you'll see that the results are displayed in a weird way (all after each other). That is because they are seperated with a null character (= \0) which you can't see, instead of a newline. This is an artifact of the "-z" option of grep. To show them in a normal list, were going to replace the null characters with a newline using tr.

paste -sd/: now that we have the results in a list, we can use the paste command to put them after each other, using a "/" as a devider.

5
  • Thanks, it's great! I used this for now: lsscsi -s | awk '/N:/ {printf $NF}' | rev | cut -c 3- | rev && lsscsi -s | awk '$3 ~ /ATA/ {printf $NF}' | rev | cut -c 3- | rev Jul 30, 2021 at 15:41
  • The regex is quite complex, but I find it fun to write so it was no problem. I find it funny that marosg was talking about not wanting to write a whole script when I did it in one pipe, so don't listen too much to people that complain like that, though I do agree that you should always know what you're doing and not just blankly copy a script someone made for you. Understanding what the writer did is a great way of learning!
    – Cas
    Jul 31, 2021 at 12:43
  • Thanks, great to hear that :) Can you describe what every part of your script is doing? Jul 31, 2021 at 15:08
  • See the edit on my answer!
    – Cas
    Aug 1, 2021 at 13:21
  • +1; But I needed the prefix LANG=C for your command to work, because I do not use English. Maybe you can edit your answer to include that.
    – sudodus
    Aug 1, 2021 at 15:58
1

It is not clear what your question is. I hope you don't want somebody to write the whole script but rather you ask how to get the numbers you want to work with.

Use lshw, this is (redacted) output from my system, one NVMe and one SSD present

$ lshw -c disk
  *-namespace               
       description: NVMe namespace
       logical name: /dev/nvme0n1
       size: 953GiB (1024GB)
...
  *-disk
       description: ATA Disk
       product: Samsung SSD 850
       logical name: /dev/sda
       size: 931GiB (1TB)
...
2
  • Yes, its good command but I want it to display only size and, if possible this size in brackets (1TB, 1024GB etc) Jul 26, 2021 at 9:50
  • 2
    It is not too complicated to parse it and display as you want. Don't expect people to write you the whole script.
    – marosg
    Jul 26, 2021 at 11:53
0

The following one-line command should do what you want.

for i in $(lsblk -bdno name,size|tr -s ' ' '_'); do j=${i##*_};j=$(((j+500000000)/1000000000));printf "${i%_*}\t$j\tGB\n";done

You can put a function with it in your ~/.bashrc file near the aliases in order to run it with a short command, for example mydrives

function mydrives { for i in $(lsblk -bdno name,size|tr -s ' ' '_'); do j=${i##*_};j=$(((j+500000000)/1000000000));printf "${i%_*}\t$j\tGB\n";done }

It works like this for me, after

source ~/.bashrc

and in all terminal windows that you open after you modified your ~/.bashrc file.

In my computer it prints like this

$ mydrives
sda     256     GB
sdb     4001    GB
sr0     1       GB
nvme0n1 250     GB

I'm used to GiB (gibibytes) so I often use the plain lsblk command

lsblk

or when I want more details and have a wide terminal window

lsblk -fm

or otherwise

lsblk -m

The command matching what I suggested for you, but with mibibytes, gibibytes, tibibytes (base 2 rather than base 10) would be

$ lsblk -dno name,size
sda     238,5G
sdb       3,7T
sr0      1024M
nvme0n1 232,9G

You must log in to answer this question.

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