5

I found many string manipulation tutorials, but can not figure out how to apply them to my particular situation. I need to insert (not substitute) a string variable word into a text variable text using either method (can not depend on line numbering, variable manipulation preferred over read/write to a file):

  1. Before the matched string, or
  2. At a specific index (byte position)

    text="mytextMATCHmytext"
    word="WORD"
    match="MATCH"
    
    # method1 - not working, because text is not a file
    sed '/$word/ i $match' text
    
    # method2
    indx="${text%%$match*}"
    indx=${indx%:*} # leave only the byte index where match starts
    text="$text{0-$index-1}$word$text{$index-end}"
    
    # expected value of text:
    "mytextWORDMATCHmytext"
    

Please, help to figure out the syntax. It would be nice to fix both methods. Any other ways of doing it? The text contains >1MB of text, so, the efficient way is preferred.

  • 1
    You have 1 Mb of text in a variable? Why? Wouldn't it be easier to work on a file with sed? – Kusalananda Jun 7 '17 at 18:55
  • 1
    @Kusalananda wouldn't the program read the file into a variable anyway? – Nazar Jun 7 '17 at 18:57
  • Are there multiple of these files? Because 1Mb of text would only take a second or two to parse, if that – cutrightjm Jun 7 '17 at 19:03
  • 1
    @Naz That totally depends on what you want to do with the text in the end, but most Unix tools does line-by-line processing. – Kusalananda Jun 7 '17 at 19:33
10

To insert the text j into the variable text at position p (counting from zero):

p=5
text="$(seq 10)"               ## arbitrary text
text="${text:0:p}j${text:p}"

To insert the text j before the matching portion in $match:

text="${text%%${match}*}j${match}${text##*${match}}"

This pulls off the leading portion of $text until it finds $match, then adds the j, then the $match, then the trailing portion of $text until it finds $match. Hopefully there's only one match of $match in $text!

  • Thank you. I think the first option is better for me, as it will only perform insertion once and at a specified place. Just need to make sure the p value is correct. – Nazar Jun 7 '17 at 19:19
3

You need the following string manipulation routines to reliably perform the operation:

text_new=${text%%"${match}"*}${word}${text#*"${text%%"${match}"*}"}
#         |------- A -------| |-B--| |------------ C -------------|
  • A is the pre-match portion, that is, the string before the match begin.
    • It is generated by, standing at the end of the string, looking towards the beginning of string, and ripping off up to and including the last sighting of the match text.
  • B is the data to be inserted.
  • C is the post-match portion, that is, the string after the match end.
    • It is generated by, standing at the beginning of the string, looking towards the end of string, and ripping of upto and including the first sighting of what was the result of the Step-A.
  • Note that match string is quoted to avoid any metacharacters in it being understood by the bash parameter expansion routines.
  • Due consideration is given to the possibility of the match to be present more than once in the string. The first match from the left is substituted.
  • The case of a string with newlines is also taken care of.

Sed

match_esc=$(printf '%s\n' "$match" | sed -e 's|[][^\/.*$]|\\&|g' | sed -e 'H;1h;$!d;g;s/\n/\\n/g')
 word_esc=$(printf '%s\n' "$word"  | sed -e 's|[\&/]|\\&|g;$!s/$/\\/')
printf '%s\n' "$text" | sed -e 'H;1h;$!d;g;'"s/$match_esc/$word_esc&/"''
  • The sed method is more circuitous as we've to make sure that all chars that have a meaning to sed and are appearing in the input text should be made silent. Another level of complication is that these characters are different for LHS and RHS of the sed's s/// command.
  • Identifying those chars, then silencing them forms the next step.
  • After this it's a simple matter of running the text data thru sed with the usual sed s/// stuff.
1

As you say, if you have the data in a variable and not a file, you cannot sed it directly. However, it's still the simple way to do what you ask; you just need to pipeline it:

#!/usr/local/bin/bash
text="loremipsumNEEDLEdolorsitamet"
word="HAYSTALK"
match="NEEDLE"

echo "$text" | sed "s/$match/&$word/g"

Demonstrative output:

$ ./369818.sh
loremipsumNEEDLEHAYSTALKdolorsitamet

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.