0

I have a file with multiple columns, separated by an underscore (_).

Most of the columns started with an uppercase for the first character, some with lower case.

I am intended to extract the string for each line, which begins with an upper case and the next column started with a lower case. There will be at least one such case happened for every line. (Update: It would be great to have only the first match). The tricky part is that the case doesn't happen at the same column for every line.

For example:

Today_is_a_Good_Day
It_Doesnt_rain
i_dont_Like_rainy_day

Desired output:

Today_is
Doesnt_rain
Like_rainy

Is there any way to do such text extraction using grep/sed/awk or other command?

I was trying to look for some similar solution to my problem but failed to find one.

Update: There will be at least

4 Answers 4

2

With a grep implementation with PCRE support and -o:

$ grep -P -o '(?<![^_])\p{Lu}[^_]*_\p{Ll}[^_]*' < your-file
Today_is
Doesnt_rain
Like_rainy

(you can replace grep -P with pcregrep if your grep doesn't support -P).

That's an uppercase Letter followed by 0 or more non-_s, followed by _, a lowercase Letter and another sequence of 0 or more non-_s, the whole thing only matching provided it doesn't follow a non-_ (that is, follows a _ or the start of the line).

That will print every matching occurrence on its own line. To limit it to the first match on each line, you could take a different approach:

grep -P -o '^(.*?_)??\K\p{Lu}[^_]*_\p{Ll}[^_]*' < your-file

For the last match on each line, the same but using the greedy version of the operators for the part that eats away the leading part:

grep -P -o '^(.*_)?\K\p{Lu}[^_]*_\p{Ll}[^_]*' < your-file
1
  • Thanks for the answer!!! -P is working fine here. Btw is there any way to limit the search only for the first match in every line?
    – web
    Feb 5, 2020 at 15:41
1
$ grep -o '[[:upper:]][[:alpha:]]*_[[:lower:]][[:alpha:]]*' file
Today_is
Doesnt_rain
Like_rainy

This pulls out any string that starts with an uppercase letter followed by any number of alphabetical characters, then an underscore followed by a lowercase letter and (possibly) more alphabetical characters.

The above would however pull out multiple matches per line, if there were multiple matches.

The following sed command does not have that issue (it would instead pull out the last such string on each line):

$ sed -n 's/.*\([[:upper:]][[:alpha:]]*_[[:lower:]][[:alpha:]]*\).*/\1/p' file
Today_is
Doesnt_rain
Like_rainy
0

An awk approach

awk -F'_' -v OFS='_' '{
    for (i=1; i<NF; i++) {
        if ($i ~ /^[[:upper:]]/ && $(i+1) ~ /^[[:lower:]]/) {
            print $i, $(i+1)
            break
        }
    }
}' file

The break ensures that only the first match is printed.

0
sed 's/.*\([A-Z][^_]*_[a-z][^_]*\).*/\1/' <your-file

EDIT: greedy sed give the last match. awk solution for first match:

awk '{match($0,/([A-Z][^_]*_[a-z][^_]*)/,a); print a[1]}' <your-file
6
  • Thanks for the help @gabor! May I know what is the "1" at the end of the command means? The sed command worked great for my case, just that it happened to extracted the second match of the line. Is it possible to limit the search only for the first match in every line?
    – web
    Feb 5, 2020 at 16:00
  • 1
    The \1 is a "back reference" and will be replaced by the content of the first "capture group", i.e. sub-expression enclosed by \( ... \). The same syntax allows for multiple such sub-expressions to be referenced by \2, \3, ...
    – AdminBee
    Feb 5, 2020 at 16:09
  • A few problems: it would print in full the lines that have no match; depending on the locale, [A-Z] can match all sorts of things, letter or not, uppercase or not, that sort inbetween A and Z. [[:upper:]] would probably be better to match on uppercase letters (regardless of the alphabet). On an input like FOO_bar, or X-O_bar, it would output O_bar Feb 5, 2020 at 17:07
  • @StéphaneChazelas: 1. according to description all lines have match; 2. can you give some reference how A-Z differs from :upper:?; 3. you have right, but again, acording to description, there is no such case.
    – gabor.zed
    Feb 7, 2020 at 6:02
  • On 2.: This section in the GNU Awk User's Guide e.g. explains that the sorting order of characters is locale dependent, so whereas in the C locale you have the same order as in the ASCII standard, and [a-z] is equivalent to [:lower:] safe for non-ASCII characters, in almost any other locale, the letters are ordered as in aAbBcC ... zZ, meaning that [a-z] would also include upper-case letters (and vice versa).
    – AdminBee
    Feb 17, 2020 at 14:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.