0

Trying to match some Gentoo package naming components from strings in forms category/name-version, due to its complexity, I have come to this:

if [[ "$1" =~ ^([<>]?=?)(([^\/]+)\/)?([^[:space:]]+)-(([[:digit:]]+)?(\.([[:digit:]]+))*([a-z])?(_(alpha|beta|pre|rc|p)([[:digit:]]*))*(-(r([[:digit:]]+))?)?)?$ ]]; then
    # use "${BASH_REMATCH[n]}" here to capture groups contents
fi

It splits strings like <category/package-name-12345.25b_rc10-r7 as expected:

Version specifier: <
Category: category
Package name: package-name
Version: 12345.25b_rc10-r7
Version, major: 12345
Version, minor: 25
Version, letter: b
Version, patch type: rc
Version, patch level: 10
Version, revision number: 7

Now, I need to match and split stings that may have version missing, like category/package-name.

So, is there a way to make the version part above optional?

Above, this part is the following:

-(([[:digit:]]+)?(\.([[:digit:]]+))*([a-z])?(_(alpha|beta|pre|rc|p)([[:digit:]]*))*(-(r([[:digit:]]+))?)?)?

I've tried to change it as:

(-(([[:digit:]]+)?(\.([[:digit:]]+))*([a-z])?(_(alpha|beta|pre|rc|p)([[:digit:]]*))*(-(r([[:digit:]]+)))?))?

It works for a string without a version, but there seem that optional groups prevent it to match a slightly complete string like above and it gets:

Version specifier: 
Category: category
Package name: package-name-12345.25b_rc10-r7
Version: 
Version, major: 
Version, minor: 
Version, letter: 
Version, patch type: 
Version, patch level: 
Version, revision number:

EDIT: Slots

What about two optional parts?

Additionally to the above, must match slots. Slots would be matched like this:

:(([[:digit:]]+)(\.([[:digit:]]+))*)?

Now, there are a category/name part:

([<>]?=?)(([^\/]+)\/)?([^[:space:]]+)

followed by either a version: -(([[:digit:]]+)(\.([[:digit:]]+))*)([a-z])?(_(alpha|beta|pre|rc|p)([[:digit:]]*))*(-(r([[:digit:]]+))?)?

a slot :(([[:digit:]]+)(\.([[:digit:]]+))*)?

or both, version and slot, in this order.

Note, a version starts with the -, as separator, and a slot starts with the :, as separator.

All I can think of is:

if [[ "$1" =~ ^${CATEGORY_PACKAGE}-${VERSION}:${SLOT}$ ]] ; then
    # use "${BASH_REMATCH[n]}" here to capture groups contents
else
    if [[ "$1" =~ ^${CATEGORY_PACKAGE}:${SLOT}$ ]] ; then
        # use "${BASH_REMATCH[n]}" here to capture groups contents
    else
        if [[ "$1" =~ ^${CATEGORY_PACKAGE}-${VERSION}$ ]] || [[ "$1" =~ ^${CATEGORY_PACKAGE}$ ]] ; then
        # use "${BASH_REMATCH[n]}" here to capture groups contents
        fi
    fi
fi

Is this a full solution? Is there a better version? Is there a POSIX solution based on options, like <category-name>(<slot option>|<version option>|<version:slot> option)?

EDIT:

I think bash can handle optional groups in one expression, but I fail to change ([^[:space:]:]+) to handle a hyphen. How can hyphen be included as does not seem to work neither as the first, nor the last in the range?

4
  • Say the input was foo-bar-2022, is 2022 a version or part of the name of the package?
    – muru
    Commented Apr 12, 2023 at 10:00
  • I'd consider it a version number, there are packages like this. Generally, the package string has also a category, so would be like foo-category/foo-bar-2022, but to work without categories would be a plus. As a rule, versions are separated by the name by a -.
    – mike
    Commented Apr 12, 2023 at 10:04
  • 1
    re. that edit, if you have another question, post another question.
    – ilkkachu
    Commented Apr 17, 2023 at 6:25
  • [^-[:space:]:] or [^[:space:]:-] work to match any character except whitespace, colon and hyphen but since your package names can apparently contain -s, it's probably not an option. Commented Apr 17, 2023 at 6:42

2 Answers 2

6

For the sake of sanity, in situations like this, IMO it's better to just use multiple regular expressions instead of stuffing every possible situation into a single regex:

base_package_name_regex='^([<>]?=?)(([^/]+)/)?([^[:space:]]+)'
version_regex='(([[:digit:]]+)?(\.([[:digit:]]+))*([a-z])?(_(alpha|beta|pre|rc|p)([[:digit:]]*))*(-(r([[:digit:]]+))?)?)?$ '
if [[ "$1" =~ $base_package_name_regex-$version_regex ]] || # package with version number
    [[ "$1" =~ $base_package_name_regex ]]  #  package without version number

then
    # use "${BASH_REMATCH[n]}" here to capture groups contents
fi

Also note that in POSIX ERE, what \/ matches is unspecified and [^\/] matches any character other than backslash or slash. I've removed those spurious backslashes.

1
  • I agree, basically, I've just needed a way to write it to detect and handle the same situation even in working per parts, like splitting it first in category, name and version strings. There I've failed too, so I have put here the entire problem to be clear the requirement. Hopefully..
    – mike
    Commented Apr 12, 2023 at 10:13
2

If you have to use a shell, you could switch to zsh. Then you can use PCREs instead of POSIX EREs with which

  • you get non-greedy operators which you need here for [^[:space:]]+ (which we can shorten to \S+ in PCRE and which we change to the non-greedy \S+? variant) not to gobble everything up to the end leaving no chance for the version to be matched
  • you can make the regexp more legible with (?x)
  • capture only what you need to capture (using (?: ...) for non-capturing groups).
set -o rematchpcre
field_names=(
  version_specifier
  category
  package
  version
  major
  minor
  letter
  patch_type
  patch_level
  revision
)

typeset -A fields

if [[ $1 =~ '(?x)
  ^
  (?<version_specifier> [<>]? =? )
  (?: (?<category> [^/]+ ) / )?
  (?<package> \S+? )
  (?:
    -
    (?<version>
      (?<major> \d* )
      (?: \. (?<minor> \d+ ) )*
      (?<letter> [a-z] )?
      (?:
    _(?<patch_type> alpha|beta|pre|rc|p )
    (?<patch_level> \d* )
      )*
      (?: - (?: r (?<revision> \d+ ) )? )?
    )?
  )?
  \z' ]]; then
  fields=( "${(@)field_names:^match}" )
  typeset -p fields
fi

Note that we name the capture groups here with (?<name> ...) but that's only for documentation purposes, the capture groups are stored in the simple $match array, zsh doesn't support retrieving them by name like the %+{name} of perl.

Here that script gives:

$ ./that-script '<category/package-name-12345.25b_rc10-r7'
typeset -A fields=(
 [category]=category
 [letter]=b
 [major]=12345
 [minor]=25
 [package]=package-name
 [patch_level]=10
 [patch_type]=rc
 [revision]=7
 [version]=12345.25b_rc10-r7
 [version_specifier]='<'
)
$ ./that-script '<category/package-name'
typeset -A fields=(
 [category]=category
 [package]=package-name
 [version_specifier]='<'
)
$ ./that-script package-name
typeset -A fields=(
 [category]=''
 [package]=package-name
 [version_specifier]=''
)
3
  • Indeed, zsh is great, but not an option for me right now.
    – mike
    Commented Apr 12, 2023 at 12:57
  • How about perl, python or other proper programming language? Commented Apr 12, 2023 at 14:25
  • Sorry, bash only.
    – mike
    Commented Apr 12, 2023 at 18:23

You must log in to answer this question.

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