Let's say we have a string and its delimiter is ?:

Leslie Cheung April 1 ? Elvis August 16 ? Leonard Nimoy February 27

I know how to grep the first substring between delimiters:

echo $above_string | grep -oP "^[^?]*"
Leslie Cheung April 1

How should I change the Regex in order to grep the second or third substring?

  • 3
    I'd use cut instead of grep: echo ... | cut '-d?' -f2 – pmg Mar 31 '15 at 17:56
echo $above_string | grep -oP "^([^?]*\?){2}\K[^?]*"

Change 2 to the n - 1 value in order to obtain the nth string.

This assumes that you want the nth string in that line. You have n - 1 strings with no ? ending with a literal '?' (\? since it's a special character in perl regex). Then with \K you state you are not interested in the previous contents, thus extracting just the following text until the next separator.

  • thanks, very good, I do not understand the part after \K , good you explain it ? – Abdul Al Hazred Mar 31 '15 at 21:53
  • 1
    The [^?]* is the same regex you used in the question: anything not containing a ? (the separator) in a greedy way (thus running until a ? or an end of line is found) – Ángel Mar 31 '15 at 21:58
  • so, ^([^?]*\?){2} recognizes the first two substrings that ends in a ? , \k kills them and then the third occurence is finally greped ? – Abdul Al Hazred Mar 31 '15 at 22:19

How about using cut? If you'd like to print the 2nd pattern

echo "$above_string" | cut -f2 -d "?"

Second column onward

echo "$above_string" | cut -f2- -d "?"


Using Awk to print the second and third records separated by newlines:

awk -F"?" '{printf "%s\n%s\n", $2,$3}'
Elvis August 16 
Leonard Nimoy February 27

If you want to swap out the record, you can set it as a variable:

awk -v record=2 -F"?" '{print $record}'
Elvis August 16 

With sed you can do:

sed '/\n/P;//d;s/[^?]*/\n&\n/[num];D'

...where you would replace the [num] above with some number representing the desired occurrence.

If the numbered occurrence you specify does not exist, as is demonstrated in the following example, sed will simply print nothing at all.

echo ,2,3 | sed '/\n/P;//d;s/[^,]*/\n&\n/4;D'

Above the first match for a sequence of zero-or-more not-comma chars are the zero chars occurring before the first comma. The second is 2 and the third is 3 - there is no fourth occurrence of that pattern and so the substitution is not successful.

Note also that not every sed will support the \n newline escape in the right-hand-substitution field and you may have to replace the n characters in the escape string with literal newlines.

sed '/\n/P;//d;s/[^?]*/\

With your string it does:

str='> Leslie Cheung April 1 ? Elvis August 16 ? Leonard Nimoy February 27'
for o in 1 2 3
do  printf %s\\n "$str" |
    sed "/\n/P;//d;s/[^?]*/\n:$o:&\n/$o;D"

...which is just a little for loop which runs sed 3 times trying for all 3 [^?]* matches and prints...

:1:> Leslie Cheung April 1
:2: Elvis August 16
:3: Leonard Nimoy February 27

...or one for each value of $o.

You can expand on that a little to skip over [num] not-null occurrences like:

until [ "$((i+=1))" -gt 10 ] &&
      printf %s\\n "$str"
do    printf %s ":$i:$str?"; done |
sed '/..*\n?*/P;s///;s/[^?]*/\n&\n/7;D'

...which prints...

:3:> Leslie Cheung April 1
 Elvis August 16
 Leonard Nimoy February 27
:10:> Leslie Cheung April 1

It can also be inclusive. For example:

printf %s\\n "$str?$str" |
sed '/.*\n[^_[:alnum:]]*/P;s///

...which prints each on a separate line every third not-null sequence of alphanumeric and _ characters in a concatenation of two of your strings...



You can use sed for this, but it is not advisable, e.g. here is a zero-based solution that uses a quantifier to select the desired field:

sed 's/\([^?]*? *\)\{'$n'\}//; s/?.*//' <<<"$above_string"


Elvis August 16 

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.