Select a word before and after a pattern (Hyphen is the pattern ) on shell script

Viewed 402

Select a word before and after a pattern (Hyphen is the pattern) using shell script.

Out is a text file which has hundreds of lines and i have selected the ones which has Required ID's, however i need to select ALPHABETS-NUMBERS. Count of Alphabet and Number varies.

I have tried various utilities including cut, sed, awk, however it s trimming the required fields.

Input

cat out | grep "[A-Z][-][0-9]"
BUG-KEYWORD-BUG-101
ABC-10
DEF-10327
Output is referred in ABC-1043
Please refer DEF-11234

Output Should be

BUG-101
ABC-10
DEF-10327
ABC-1043
DEF-11234
6 Answers

Could you please try following. Written and tested with shown samples in GNU awk.

awk 'match($0,/[a-zA-Z]+-[0-9]+$/){print substr($0,RSTART,RLENGTH)}' Input_file

Explanation: Adding detailed explanation for above.

awk '                               ##Starting awk program from here.
match($0,/[a-zA-Z]+-[0-9]+$/){      ##using match function to match alphabets dash and digits till last of line.
  print substr($0,RSTART,RLENGTH)   ##Printing matches sub string of matched regex.
}
' Input_file                        ##Mentioning Input_file name here.

I like the approach with grep, and awk, but since you tagged sed as well, to round out your answer selection, you can use:

sed -E 's/^.*(\b[[:alpha:]]+-[[:digit:]]+).*$/\1/' file

Where -E extended regex is selected and then the normal substitution s/find/replace/ is used where:

  • find is ^.*(\b[[:alpha:]]+-[[:digit:]]+).*$ with ^.* anchoring to the beginning of line, discarding zero or more characters of any type and then capturing the text within the capture group (...) and discarding zero or more character to the end of line .*$,
  • the capture group selects a \b word-boundary followed by one or more A-Za-z [:alpha:] characters, then a '-', followed by one or more 0-9 [:digits:],
  • the replace simply reinserts what was captured using the first backreference \1.

Example Use/Output

With your data in file, you would have:

$ sed -E 's/^.*(\b[[:alpha:]]+-[[:digit:]]+).*$/\1/' file
BUG-101
ABC-10
DEF-10327
ABC-1043
DEF-11234

You can use

grep -oE '[[:alpha:]]+-[0-9]+' file

The o option makes grep output matches only, E enables the POSIX ERE syntax (no need to escape +), and [[:alpha:]]+-[0-9]+ matches all substrings that start with one or more letters, then have a - char and then have one or more digits.

Alternatives

The above solution extracts all, multiple matches even if they occur on the same line in the file. If you do not want this behavior, and only want to match the <letters>-<digits> at the end of lines, you should simply add $ at the end of the regex pattern and use

grep -oE '[[:alpha:]]+-[0-9]+$' file`

If you need to extract the last occurrence on each line that is not necessarily at the end of a line, use a PCRE based regex with GNU grep or pcregrep (in case you installed it, a common workaround tool with non-GNU grep versions):

grep -oP '^(?:.*\P{L})?\K\p{L}+-\d+' file
pcregrep -o '^(?:.*\P{L})?\K\p{L}+-\d+' file

See this regex demo. In a PCRE pattern, \p{L} matches any letter (you may still use [[:alpha:]], too) and \d matches any ASCII digit, ^(?:.*\P{L})?\K matches start of string (^), and then an optional sequence of any zero or more chars other than line break chars, as many as possible (.*) and then any char other than a letter (\P{L}), this part makes the engine go to the last match occurrence of \p{L}+-\d+.

See the main solution online demo:

s='BUG-KEYWORD-BUG-101
ABC-10
DEF-10327
Output is referred in ABC-1043
Please refer DEF-11234'
grep -oE '[[:alpha:]]+-[0-9]+' <<< "$s"

Output:

BUG-101
ABC-10
DEF-10327
ABC-1043
DEF-11234

I would use AWK following way, let file.txt content be

BUG-KEYWORD-BUG-101
ABC-10
DEF-10327
Output is referred in ABC-1043
Please refer DEF-11234

then

awk 'BEGIN{FPAT="[A-Z]+-[0-9]+"}{print $1}' file.txt

output

BUG-101
ABC-10
DEF-10327
ABC-1043
DEF-11234

Explanation: I inform AWK too look for fields matching pattern, then print first field. Disclaimer: I assume in every line uppercase-digits appears exactly once.

And with GNU awk for multi-char RS, RT:

awk -v RS='[[:alpha:]]+-[[:digit:]]+' 'RT {print RT}' file
BUG-101
ABC-10
DEF-10327
ABC-1043
DEF-11234

This might work for you (GNU sed):

sed -nE ':a;/[[:digit:]]+-[[:alpha:]]+/{s//\n&\n/;s/^[^\n]*\n//;P;s///;ba}' file

This extracts one or more of the desired strings and prints them each on a separate line.

It surrounds the desired string by newlines then removes up to and including the first newline, prints up to and including the newline, removes what it just printed and repeats.

An exotic alternative using the same method:

sed -E '/\n/!s/[A-Z]+-[0-9]+/\n&\n/g;/^[A-Z]+-[0-9]+/P;D' file

N.B. These sed solutions are included for completeness, but as grep offers the most efficient/terse option, I would use that.

Related