user4401178
user4401178

Reputation:

Replacing Nth Occurrence from End of Line via Sed

Do you know of a better or easier way of using sed or Bash to replace from the last occurrence of a regex match from the end of a line without using rev?

Here is the rev way to match the third from last occurrence of the letter 's' – still forward matching, yet utilizing rev to match from the last character.

echo "a declaration of sovereignty need no witness" | rev | sed 's/s/S/3' | rev
a declaration of Sovereignty need no witness

Update -- a generalized solution based on Cyruses answer:

SEARCH="one" 
OCCURRENCE=3 
REPLACE="FOUR" 
SED_DELIM=$'\001'  
SEARCHNEG=$(sed 's/./[^&]*/g' <<< "${SEARCH}")
sed -r "s${SED_DELIM}${SEARCH}((${SEARCHNEG}${SEARCH}){$((${OCCURRENCE}-1))}${SEARCHNEG})\$${SED_DELIM}${REPLACE}\1${SED_DELIM}"  <<< "one one two two one one three three one one"

Note: To be truly generic it should escape regex items from the LHS.

Upvotes: 1

Views: 675

Answers (4)

choroba
choroba

Reputation: 241858

In Perl, you can use a look-ahead assertion:

echo "a declaration of sovereignty need no witness" \
| perl -pe 's/s(?=(?:[^s]*s[^s]*){2}$)/S/'
  • (?=...) is the look-ahead assertion, it means "if followed by ...", but the ... part is not matched and therefore not substituted
  • [^s]*s[^s]* means there's only one s wrapped possibly by non-ses
  • {2} is the number of repetitions, i.e. we want two s'es after the one we want to replace

Upvotes: 0

Walter A
Walter A

Reputation: 20002

You first need to count the number of occurances and figure out which one to replace:

echo "importantword1 importantword2 importantword3 importantword4 importantword5 importantword6" | 
    grep -o "importantword" | wc -l

Use that for your sed string

echo "importantword1 importantword2 importantword3 importantword4 importantword5 importantword6" |
   sed 's/\(\(.*importantword.*\)\{3\}\)importantword\(\(.*importantword.*\)\{2\}\)/\1ExportAntword\3/'
importantword1 importantword2 importantword3 ExportAntword4 importantword5 importantword6

You get the same answer with setting variables for left and right:

l=3
r=2
echo "importantword1 importantword2 importantword3 importantword4 importantword5 importantword6" |
  sed 's/\(\(.*importantword.*\)\{'$l'\}\)importantword\(\(.*importantword.*\)\{'$r'\}\)/\1ExportAntword\3/'

or

str="\(.*importantword.*\)"
echo "importantword1 importantword2 importantword3 importantword4 importantword5 importantword6" | 
   sed 's/\('${str}'\{'$l'\}\)importantword\('${str}'\{'$r'\}\)/\1ExportAntword\3/'

Upvotes: 0

anubhava
anubhava

Reputation: 785146

Using awk:

str='a declaration of sovereignty need no witness'

awk -v r='S' -v n=3 'BEGIN{FS=OFS="s"} {
   for (i=1; i<=NF; i++) printf "%s%s", $i, (i<NF)?(i==NF-n?r:OFS):ORS}' <<< "$str"

Output:

a declaration of Sovereignty need no witness

Upvotes: 0

Cyrus
Cyrus

Reputation: 88601

With GNU sed:

sed -r 's/s(([^s]*s){2}[^s]*)$/S\1/'  file

Output:

a declaration of Sovereignty need no witness

See: The Stack Overflow Regular Expressions FAQ

Upvotes: 2

Related Questions