3

I want to replace some lines in a file with the content of an other file using sed (on MacOs).

Let say the file is the following and I want to replace all lines that contain <iframe src=...0></iframe>.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Paupertas si malum est, mendicus beatus esse nemo

<iframe src="https://test.com/bar/11d75d1c627c017299e4fe35e" frameborder=0></iframe>

oportunitatis esse beate vivere. Quasi vero, inquit, perpetua oratio rhetorum solum, non etiam philosophorum

<iframe src="https://test.com/bar/9e4e1b69131bf5d718452aca6" frameborder=0></iframe>

vivere. Quasi vero, inquit, perpetua oratio rhetorum solum, non etiam philosophorum

On the command line the following command works well:

$ sed -i '' -e "/$line/r $replacementFile" -e "//d" $originalFile

where

  • $line is the line to change
line="\<iframe src=\"https:\/\/test\.com\/bar\/11d75d1c627c017299e4fe35e\" frameborder=0\>\<\/iframe\>"
  • $replacementFile points to the file whose content must replace the $lines, its content is the following:
LOREM IPSUM DOLOR
AMET, CONSECTETUR
ADIPISCING ELIT.
  • $originalFile is the path to the file where $line has to be changed

I have several files to modify and each one contain several lines to change. So I wrote a script where all lines of type <iframe src=...0></iframe> are found with a regex, and then I apply the command that worked on the command line.

Below is the original script that I wrote:

function replaceTextWithFile() {
    # File to modify (in production the name is passed as an argument)
    local originalFile="./file.txt"

    # Regex to find the line to change
    local regex="<iframe src=\"(https:\/\/test.*)\" frameborder"
    # Regex on "normal" line does not work neither
    #local patternToReplace="^oportu.*"

    # The file whose content must replace the line
    local replacementFile="./new_content"

    exec 4<"$originalFile"
    while read line <&4 ; do
        if [[ $line =~ $regex ]];then
            echo "PATTERN FOUND"
            sed -i '' -e '/$line/r $replacementFile' -e '//d' $originalFile
        fi

        # Following command changes the 9th line successfully
        sed -i '' -e "12s/.*/TEST TEST/" $originalFile
    done
    exec  4<&-
}

replaceTextWithFile

But the file is not changed.

I thought that nothing happened because the lines that I want to change have some particular characters (<, /, ..), but the script does not work also on "normal" line.

I also thought that the problem is because the file is opened in read mode with a descriptor file, but I can change the content of the file using sed, for example with the following command:

sed -i '' -e "9s/.*/TEST TEST/" $originalFile

I have tried several syntaxes without success, you can find the script and the different syntaxes that I tried here.

Does anyone have any idea what I'm doing wrong?

There are several questions on Stackoverflow that treats some kind of this problem, but none of them has been helpful.

If my first intent is to replace the lines with sed, any other solution would be appreciated.

eqtèöck
  • 971
  • 1
  • 13
  • 27
  • What is the purpose of `''` in `sed -i '' ...` ? – Lxer Lx Jul 10 '20 at 10:22
  • wut, what is the point of `$line =~ $regex` and then `/$line/` ? No, `sed` works with regex, not with exact line. Also why backreference `(https....)`? Do you intent to match `(` `)`? and why `https:\/\/foo` - there is no `foo` in input. @LxerLx To specify backup suffix – KamilCuk Jul 10 '20 at 10:22
  • @KamilCuk, my intent is that if the line matches the regex (`$line =~ $regex`), then change the all line. Sed does not work with exact line? ok I will search in this direction, thank you – eqtèöck Jul 10 '20 at 10:29
  • `$line =~ $regex), then change the all line.` makes no sense. `sed` already works on all lines in file. It's like you are looping line number times over each line. – KamilCuk Jul 10 '20 at 10:29

1 Answers1

3

Your script has some errors:

  • The regex is just wrong, it just doesn't match the input.
  • sed works with regexes, not on exact characters. So /$line/ would have been parsed as a regex.
  • Variables do not expand single quotes. '/$line/' matches literally 5 characters $line. To expand variables use double quotes. stackoverflow difference between single and double quotes in bash.
  • There is no point in looping each line while executing sed. sed already executes on each line...
  • function func() { is supported on bash as a extension to support strange syntax, it shouldn't be used. Just func() { to define a function instead. bash-hackers wiki deprecated and obsolete syntax.
  • Following command changes the 9th line successfully is followed by a line that changes 12th line.
  • because the lines that I want to change have some particular characters the < and > have not special meaning in regex, however \< \> is a GNU sed extension. Learn regexes with regex crosswords.
  • To read a line of input use IFS= read -r line. bashfaq how to read a file line by line.

Just:

replaceTextWithFile() {
    local originalFile="./file.txt"
    local regex="<iframe src=\"https:\/\/.*\" frameborder"
    local replacementFile="./new_content"
    # note -i '' on macos I think
    sed -i -e "/$regex/r $replacementFile" -e '//d' "$originalFile"
}

Note that the regex could be simpler written within single quotes, so that " doesn't need escaping:

    local regex='<iframe src="https:\/\/.*" frameborder'

or you can even use a different regex delimiter in sed to make it just simply:

    local regex='<iframe src="https://.*" frameborder'
    sed "\~$regex~r $replacementFile"

Tested on repl.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • I made a mistake in my regex when writing my question. But still, your answer @KamilCuk is correct and solve my problem. I also highly appreciate all the interesting advices! Thank you!! – eqtèöck Jul 10 '20 at 13:03