1

I have a variable such as:

disk=/dev/sda1

I want to extract:

  • only the non numeric part (i.e. /dev/sda)
  • only the numeric part (i.e. 1)

I'm gonna use it in a script where I need the disk and the partition number. How can I do that in shell (bash and zsh mostly)?

I was thinking about using Shell parameters expansions, but couldn't find working patterns in the documentation.

Basically, I tried:

echo ${disk##[:alpha:]}

and

echo ${disk##[:digit:]}

But none worked. Both returned /dev/sda1

Cyrus
  • 84,225
  • 14
  • 89
  • 153
Ben
  • 6,321
  • 9
  • 40
  • 76

3 Answers3

4

With bash and zsh and Parameter Expansion:

disk="/dev/sda12"
echo "${disk//[0-9]/} ${disk//[^0-9]/}"

Output:

/dev/sda 12
Cyrus
  • 84,225
  • 14
  • 89
  • 153
  • Notice that this assumes that the digits appear only at the end of the string; if you have something like `/dev/foo1bar12` (is that a valid device name?), you'll end up with `/dev/foobar 112`. – Benjamin W. Jun 16 '19 at 18:33
  • 1
    @BenjaminW.: yes, this won't work in this case or with devices from Solaris (/dev/dsk/c0t2d0s0, e.g.). – Cyrus Jun 16 '19 at 18:36
1

The expansions kind-of work the other way round. With [:digit:] you will match only a single digit. You need to match everything up until, or from a digit, so you need to use *.

The following looks ok:

$ echo ${disk%%[0-9]*} ${disk##*[^0-9]}
/dev/sda 1

To use [:digit:] you need double braces, cause the character class is [:class:] and it itself has to be inside [ ]. That's why I prefer 0-9, less typing*. The following is the same as above:

echo ${disk%%[[:digit:]]*} ${disk##*[^[:digit:]]}

* - Theoretically they may be not equal, as [0-9] can be affected by the current locale, so it may be not equal to [0123456789], but to something different.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
1

You have to be careful when using patterns in parameter substitution. These patterns are not regular expressions but pathname expansion patterns, or glob patterns.

The idea is to remove the last number, so you want to make use of Remove matching suffix pattern (${parameter%%word}). Here we remove the longest instance of the matched pattern described by word. Representing single digit numbers is easily done by using the pattern [0-9], however, multi-digit numbers is harder. For this you need to use extended glob expressions:

*(pattern-list): Matches zero or more occurrences of the given patterns

So if you want to remove the last number, you use:

$ shopt -s extglob
$ disk="/dev/sda1"
$ echo "${disk#${disk%%*([0-9])}} "${disk%%*([0-9])}"
1 dev/sda
$ disk="/dev/dsk/c0t2d0s0"
$ echo "${disk#${disk%%*([0-9])}} "${disk%%*([0-9])}"
0 /dev/dsk/c0t2d0s

We have to use ${disk#${disk%%*([0-9])}} to remove the prefix. It essentially searches the last number, removes it, uses the remainder and remove that part again.

You can also make use of pattern substitution (${parameter/pattern/string}) with the anchors % and # to anchor the pattern to the begin or end of the parameter. (see man bash for more information). This is completely equivalent to the previous solution:

$ shopt -s extglob
$ disk="/dev/sda1"
$ echo "${disk/${disk/%*([0-9])}/}" "${disk/%*([0-9])}"
1 dev/sda
$ disk="/dev/dsk/c0t2d0s0"
$ echo "${disk/${disk/%*([0-9])}/}" "${disk/%*([0-9])}"
0 /dev/dsk/c0t2d0s
kvantour
  • 25,269
  • 4
  • 47
  • 72