2

I'd like to use reasoning similar to the approach below, which in this case uses a number (starting at 1) to replace the nth occurrence, but we want something in reverse order, that is, last, last 2nd, last 3rd, last 4th and so on.

QUESTION: Is it possible to use sed to substitute last, last 2nd, last 3rd, last 4th (and so on) match (occurrence)? If yes, how?

NOTE: Any approach that converts these numeric values to something specific to sed (a bash function, for example) is also acceptable. Using sed in conjunction with some command commonly found on Linux is also an acceptable answer. However, it is necessary that we can also use sed with files by adding -i.

EXAMPLE

sed -z 's/<TARGET>/<REPLACE>/<NTH_MATCH>'

EXAMPLE USE I

read -r -d '' FILE_CONTENT << 'HEREDOC'
BEGIN
askdjisoadjsadmsadjnasjndnsakjn

jkjkljkljklkljlkjkljl
jkljkljkljkklj
hjkhjkhkjh

jkjkljkljklkljlkjkljl
jkljkljkljkklj
hjkhjkhkjh

jdsjhfbjdhs
jdsjhfbjdhs
jdsjhfbjdhs
jdsjhfbjdhs
jdsjhfbjdhs
jdsjhfbjdhs
END
HEREDOC
SOURCE="${FILE_CONTENT:6:-3}"
echo "${SOURCE}" | sed -z 's/jkjkljkljklkljlkjkljl\njkljkljkljkklj\nhjkhjkhkjh/hbeuhyewuhdfjh\nfdsjisfdjiusdfijuhsfiuhsfdihusfdjhiusfdjkh\nkjhsfdjkfjkhsfdjknh/2'

EXAMPLE USE II

read -r -d '' FILE_CONTENT << 'HEREDOC'
BEGIN
askdjisoadjsadmsadjnasjndnsakjn

jkjkljkljklkljlkjkljl
jkljkljkljkklj
hjkhjkhkjh

jkjkljkljklkljlkjkljl
jkljkljkljkklj
hjkhjkhkjh

jdsjhfbjdhs
jdsjhfbjdhs
jdsjhfbjdhs
jdsjhfbjdhs
jdsjhfbjdhs
jdsjhfbjdhs
END
HEREDOC
SOURCE="${FILE_CONTENT:6:-3}"
echo "${SOURCE}" | sed -z 's/jdsjhfbjdhs/IReplacedTheLast4th/4'

**TIP:** This approach can be used to replace the last match (occurrence) -> `sed -z 's/\(.*\)<TARGET>/\1<REPLACE>/'`.

**Thanks!** 
Eduardo Lucio
  • 1,771
  • 2
  • 25
  • 43
  • 3
    Thanks for sharing your efforts, could you please do edit your question more clearly with samples of input and expected output to make it more clear(not my downvote btw), thank you. – RavinderSingh13 Aug 17 '22 at 03:49
  • 5
    It may be preferable if you can simplify the input string to clarify the logic and make it easy to validate the result. – tshiono Aug 17 '22 at 04:17
  • 1
    I do not understand the requirements. Could you give a _simple_ example? Why include BEGIN and END in the here document if you remove them? Should the number of occurrence be a parameter? Why do you want to use sed, just use something else? `Is it possible` It is possible, sed is turing complete. But why not just reverse the file, replace 3rd occurrence, and reverse the file again? – KamilCuk Aug 17 '22 at 06:56
  • @RavinderSingh13 I made some modifications and provided an extra example. See if it's improved. – Eduardo Lucio Aug 17 '22 at 14:24
  • @KamilCuk "I do not understand the requirements. Could you give a simple example?" -> Done; "Why include BEGIN and END in the here document if you remove them?" -> It's a move to avoid losing whitespace at the end and beginning of the string; "Should the number of occurrence be a parameter?" -> Yes; "Why do you want to use sed, just use something else?" -> Sed is a performant and safe resource when used well; "But why not just reverse the file, replace 3rd occurrence, and reverse the file again?" -> And I thought of that hypothesis, but I would like a more direct approach. – Eduardo Lucio Aug 17 '22 at 14:30
  • `-> Done;` Do you want to replace the last N-th occurence _in a line_ or _in a file_? Are line boundaries relevant? `It's a move to avoid losing whitespace at the end and beginning of the string;` Is it relevant to the problem at hand? `Sed is a performant` Sure, but overall, developers are not performant with sed, I am paid for doing my stuff fast. You already spent time on writing this question. I do not think "sed is performant" is enough argument to not use python or perl here. `I would like a more direct approach` Still, `tac | sed -z ... | tac` or similar is just waiting for you. – KamilCuk Aug 17 '22 at 17:10
  • And `Is it possible` - sed has no arithmetic. To do arithmetic, you have to _list_ all possible combinations of possible values and encode all the states. It _will not_ be performant and there will be an upper limit. And sed has no Perl regex, in particular negative lookarounds, which makes it just harder. And with sed and regex and `-z` you will hit https://www.regular-expressions.info/catastrophic.html and it will be very very not performant. You better of using python or perl. – KamilCuk Aug 17 '22 at 17:17
  • "N-th occurence in a line or in a file" -> In a string or in a file. Thanks! – Eduardo Lucio Aug 17 '22 at 18:12

2 Answers2

1

This might work for you (GNU sed and some bash):

cat <<\! > repStr
this stanza
is in
lowercase

!

cat repStr repStr repStr repStr > sourceFile

tac repStr |
sed -z 's#.*#s/&/\\U&/3#;s/\n/\\n/g' |
sed -e 'H;1h;$!d;x
        s/.*/echo "&"|tac/e' -f - -e 's/.*/echo "&"|tac/e' sourceFile

In essence, reverse the replacement string and source file and use the existing properties of sed to replace string(s) then reverse the source file back to normal. The example above replaces(uppercases) the third last occurrence of repStr.

The final sed command, slurps the sourceFile into the pattern space and is reversed in situ, the prepared (already reversed) sed substitution command is applied and then the pattern space is reversed back to normal.

An alternative:

sed -Ez ':a;s/^([^\o000]*)(this stanza\nis in\nlowercase\n.*)[\o000]?/x\1\o000\2/
         tb;:b;s/^x{2}//;Ta
         s/\o000(this stanza\nis in\nlowercase\n)/\U\1/' sourceFile

This uses the -z option to slurp the file into memory and then uses the null character (\o000) as a delimiter to point to the beginning of the string to be replaced (amended).

N.B. The string and its number of occurrence to be replaced (amended) from the end of the file must exist.


With respect to the OP, the last solution would appear:

sed -Ez ':a;s/^([^\o000]*)(<TARGET>.*)[\o000]?/x\1\o000\2/
         tb;:b;s/^x{<NTH MATCH>}//;Ta
         s/\o000(<TARGET>)/<REPLACEMENT>/' 
potong
  • 55,640
  • 6
  • 51
  • 83
  • 1
    Can you explain the code please. also how -f and -e option worked. i have read entire documentation but i cant understarnt use of -f -e and s///e – Tathastu Pandya Aug 17 '22 at 12:35
  • tac can be replaced with sed '1!G;$p;h' -n – Tathastu Pandya Aug 17 '22 at 12:45
  • @potong I didn't understand this command... ``` sed -Ez ':a;s/^([^\o000]*)(this stanza\nis in\nlowercase\n.*)[\o000]?/x\1\o000\2/ tb;:b;s/^x{2}//;Ta s/\o000(this stanza\nis in\nlowercase\n)/\U\1/' sourceFile ``` ... and I also don't understand how it can be adapted to something like this... ``` sed -z 's///' ``` . – Eduardo Lucio Aug 17 '22 at 18:47
0

Basically you will need to reverse parameter using tac and rev. See the explanation in the code...

# SET PARAMETERS

## The "BEGIN"/"END" are didactic strategies to avoid the loss of whitespace at the end and beginning of the string
read -r -d '' SOURCE << 'HEREDOC'
BEGIN
askdjisoadjsadmsadjnasjndnsakjn

jkjkljkljklkljlkjkljl
jkljkljkljkklj
hjkhjkhkjh

jkjkljkljklkljlkjkljl
jkljkljkljkklj
hjkhjkhkjh

jdsjhfbjdhs
jdsjhfbjdhs
jdsjhfbjdhs
jdsjhfbjdhs
jdsjhfbjdhs
jdsjhfbjdhs
END
HEREDOC
SOURCE="${SOURCE:6:-3}"
TARGET="jdsjhfbjdhs"
REPLACE="IReplacedTheLast4th"
NTH_MATCH=4

# REVERSE PARAMETER CONTENT

TARGET=$(echo "x${TARGET}x" | tac | rev)

## Strategy to avoid losing whitespace at the end and beginning of the string
TARGET=${TARGET%?}
TARGET=${TARGET#?}

REPLACE=$(echo "x${REPLACE}x" | tac | rev)
REPLACE=${REPLACE%?}
REPLACE=${REPLACE#?}
SOURCE=$(echo "x${SOURCE}x" | tac | rev)
SOURCE=${SOURCE%?}
SOURCE=${SOURCE#?}

# USING STRING AS INPUT

echo "${SOURCE}" | sed -z 's/$TARGET/$REPLACE/$NTH_MATCH'
SED_RESULT=$(echo "x${SOURCE}x" | eval "sed -z 's/$TARGET/$REPLACE/$NTH_MATCH'")

## Undo reverse

SED_RESULT=$(echo "x${SED_RESULT}x" | tac | rev)
SED_RESULT=${SED_RESULT%?}
SED_RESULT=${SED_RESULT#?}

# USING FILE AS INPUT

echo "${SOURCE}" > ./my_file.txt

## Reverse file content input and undo on output to the same file
tac "./my_file.txt" | rev | eval "sed -z 's/$TARGET/$REPLACE/$NTH_MATCH'" | tac | rev > "./my_file.txt"

HOT: In this answer there is this implementation in a function with several interesting facilities for using with sed.

Thanks!

Eduardo Lucio
  • 1,771
  • 2
  • 25
  • 43