sed out string middle of string that may contain one or more numbers

Viewed 103

My strings are:

  • "TESTING_ABC_1-JAN-2022.BCK-gz;1"
  • "TESTING_ABC_30-JAN-2022.BCK-gz;1"

In bash when I run: echo "TESTING_ABC_1-JAN-2022.BCK-gz;1" | sed 's/.*\([0-9]\{1,2\}-[A-Z][A-Z][A-Z]-[0-9][0-9][0-9][0-9]\).*/\1/' it returns 1-JAN-2022 which is good.

But when I run: echo "TESTING_ABC_30-JAN-2022.BCK-gz;1" | sed 's/.*\([0-9]\{1,2\}-[A-Z][A-Z][A-Z]-[0-9][0-9][0-9][0-9]\).*/\1/' I get 0-JAN-2022 but I want 30-JAN-2022.

From me passing in my string. How can I do it so that I can get single or double digit dates in one line like "30-JAN-2022" or "1-JAN-2022"

5 Answers

1st solution: With your shown samples please try following awk code. Simply using gsub function of awk to globally substitute from starting of value to till 2nd occurrence of - AND from dot to till last of value with NULL and printing remaining of the value of current line.

awk '{gsub(/^[^_]*_[^_]*_|\..*/,"")} 1' Input_file


2nd solution: Using GNU grep please try following code. Using -oP options to print matched value and enabling PCRE regex with these options respectively. In main program using .*? concept for non-greedy match and using it 2 times from starting of value to match till 2nd occurrence of _ and then using \K option to forget all matched values. After that matching values just before . occurrence to get required output.

grep -oP '^(.*?_){2}\K[^.]*' Input_file


3rd solution: Using GNU awk with its match function which is using array concept of creating its indexes(keys) from matched regex's capturing groups.

awk 'match($0,/^[^_]*_[^_]*_([^.]*)/,arr){print arr[1]}' Input_file


4th solution: Using sed with -E option to enable ERE(extended regular expressions) and using regex ^[^_]*_[^_]*_([^.]*).* to create 1 and only capturing group which contains required value in it and using substitute function to substitute whole value with only captured value.

sed -E 's/^[^_]*_[^_]*_([^.]*).*/\1/' Input_file


5th solution: Using GNU grep please try following, thanks to "the fourth bird" for this one.

grep -oP '^(?:[^_]*_){2}\K[^.]*' Input_file

Using sed

$ echo "TESTING_ABC_1-JAN-2022.BCK-gz;1
> TESTING_ABC_30-JAN-2022.BCK-gz;1" | sed -E 's/[^0-9]*([^.]*).*/\1/'
1-JAN-2022
30-JAN-2022

It is much easier to use awk and avoid any regex:

cat file

TESTING_ABC_1-JAN-2022.BCK-gz;1
TESTING_ABC_30-JAN-2022.BCK-gz;1

awk -F '[_.]' '{print $3}' file

1-JAN-2022
30-JAN-2022

Another option is to use grep -Eo with a valid regex for date in DD-MON-YYYY format:

grep -Eo '[0-9]{1,2}-[A-Z]{3}-[0-9]{4}' file

1-JAN-2022
30-JAN-2022

The problem with your regex is that greedy * quantifier: .* will match as many characters as possible while still being able to match the rest of your input. In many regex implementations you can change the greedyness of * by adding ?. So /.*?a/ would match as few characters as possible until it finds an a. Unfortunately, sed doesn't support changing greedyness. Here are two options:

If your string always has an _ before the date, you can simply add _ to that .* part:

$ sed -r 's/.*_([0-9]{1,2}-[A-Z]{3}-[0-9]{4}).*/\1/' <<< "TESTING_ABC_30-JAN-2022.BCK-gz;1"
30-JAN-2022

Or just grep the relevant part:

$ grep -Po '[0-9]{1,2}-[A-Z]{3}-[0-9]{4}' <<< "TESTING_ABC_30-JAN-2022.BCK-gz;1"
30-JAN-2022

if u wanna do it in the most arbitrarily hard-coded-in fashion, try :

echo 'TESTING_ABC_1-JAN-2022.BCK-gz;1
      TESTING_ABC_30-JAN-2022.BCK-gz;1' | 

{m,g}awk ++NF OFS= FS='.+_|[.].+$' 

gawk -F'.+_|[.].+$' '$_=$--NF' # only if u know for certain 
                               # there aren't empty lines in btwn
1-JAN-2022
30-JAN-2022
Related