Extracting part of path containing a number in bash

Viewed 326

In bash, given a path such as:

mypath='my/path/to/version/5e/is/7/here'

I would like to extract the first part that contains a number. For the example I would want to extract: 5e

Is there a better way than looping over the parts using while and checking each part for a number?

while IFS=/ read part
do
   if [[ $part =~ *[0-9]* ]]; then
      echo "$part"
   fi
done <<< "$mypath"
7 Answers

Using Bash's regex:

[[ "$mypath" =~ [^/]*[0-9]+[^/]* ]] && echo "${BASH_REMATCH[0]}" 
5e

Method using 'grep -o'.

echo $mypath | grep -o -E '\b[^/]*[0-9][^/]*\b' | head -1
  1. Replace / by a newline
  2. Filter the first match with a number

mypath='my/path/to/version/5e/is/7/here'
<<<"${mypath//\//$'\n'}" grep -m1 '[0-9]'

and a safer alternative that uses zero separated stream with GNU tools in case there are newlines in the path:

<<<"${mypath}" tr '/' '\0' | grep -z -m1 '[0-9]'

Is there a better way than looping over the parts using while and checking each part for a number?

No, either way or another you have to loop through all the parts until the first part with numbers is discovered. The loop may be hidden behind other tools, but it's still going to loop through the parts. You solution seems to be pretty good by itself, just break after you've found the first part if you want only the first.

Could you please try following, written and tested with shown samples. This should print if we have more than 1 values in the lines too. If you talk about better way, awk could be fast compare to pure bash loop + regex solutions IMHO, so adding it here.

awk -F'/' '
{
  val=""
  for(i=1;i<=NF;i++){
    if($i~/[0-9][a-zA-Z]/ || $i~/[a-zA-Z][0-9]/){
      val=(val?val OFS:"")$i
    }
  }
  print val
}' Input_file

Explanation: Adding detailed explanation for above.

awk -F'/' '                                              ##Starting awk program from here and setting field separator as / here.
{
  val=""                                                 ##Nullifying val here.
  for(i=1;i<=NF;i++){                                    ##Running for loop till value of NF.
    if($i~/[0-9][a-zA-Z]/ || $i~/[a-zA-Z][0-9]/){        ##Checking condition if field value is matching regex of digit alphabet then do following.
      val=(val?val OFS:"")$i                             ##Creating variable val where keep on adding current field value in it.
    }
  }
  print val                                              ##Printing val here.
}' Input_file                                            ##Mentioning Input_file name here.

Using Perl:

mypath='my/path/to/version/5e/is/7/here'

# Method 1 (using for loop):
echo "${mypath}" | perl -F'/' -lane 'for my $dir ( @F ) { next unless $dir =~ /\d/; print $dir; last; }'

# Method 2 (using grep):
echo "${mypath}" | perl -F'/' -lane 'my $dir = ( grep { /\d/ } @F )[0]; print $dir if defined $dir;'

# Prints:
# 5e

The Perl one-liner uses these command line flags:
-e : Tells Perl to look for code in-line, instead of in a file.
-n : Loop over the input one line at a time, assigning it to $_ by default.
-l : Strip the input line separator ("\n" on *NIX by default) before executing the code in-line, and append it when printing.
-a : Split $_ into array @F on whitespace or on the regex specified in -F option.
-F'/' : Split into @F on /, rather than on whitespace.

next unless $dir =~ /\d/; : skip the rest of the loop if the current part of the path does not* contain a digit (\d).
last; : exit the loop (here, it also exits the script), so that it prints only the first occurrence of the matching directory.
grep { ... } LIST : for the LIST argument, returns the list of elements for which the expression ... is true, here returns the list of all path elements that have a digit.
(LIST)[0] : returns the first element of the LIST, here, the first path element with a digit.

SEE ALSO:

perldoc perlrun: how to execute the Perl interpreter: command line switches
perldoc perlre: Perl regular expressions (regexes)
perldoc perlre: Perl regular expressions (regexes): Quantifiers; Character Classes and other Special Escapes; Assertions; Capture groups

With awk, set RS to / and print the first record containing a number.

awk -v RS=/ '/[0-9]/{print;exit}' <<< "$mypath"
5e

Another bash variant

mypath='my/path/to/app version/5e/is/7/here'
until [[ ${mypath:0:1} =~ [0-9] ]]; do
    mypath=${mypath#*/}
done
echo ${mypath%%/*}
Related