2

How can I use sed to locate a string, and add text from another file after the string?

File 1:

stage ('Clone Repo31') {

        steps {
                git credentialsId: '', url: '/stash/scm/'
        }
    }
    stage ('Zip Repo31') {
        steps {
        sh"""
            tar --exclude='*.tar' -cvf .tar *
        """
        }
    }
    steps {
            git credentialsId: '', url: '/stash/scm/'
    }
}
stage ('Zip Repo32') {
    steps {
    sh"""
        tar --exclude='*.tar' -cvf .tar *
    """
    }
}

File 2:

randomRepo.git
differentRandomRepo.git

I want to be able to use sed to read the second file, and add the contents of each line from the second file after each occurance of stash/scm/

Desired output:

       stage ('Clone Repo31') {

        steps {
                git credentialsId: '', url: '/stash/scm/randomRepo.git'
        }
    }
    stage ('Zip Repo31') {
        steps {
        sh"""
            tar --exclude='*.tar' -cvf .tar *
        """
        }
    }
    steps {
            git credentialsId: '', url: '/stash/scm/differentRandomRepo.git'
    }
}
stage ('Zip Repo32') {
    steps {
    sh"""
        tar --exclude='*.tar' -cvf .tar *
    """
    }
}

Can this be done with sed? I'm having issues reading it from a list file and it's confusing since it has a lot of slashes in it. I've been able to use normal sed substitution but I don't know how to do substitution by reading another file.

Lucid
  • 43
  • 5
  • Why is this being closed as off topic? I am writing an answer for it, and it is about programming! – Enlico Mar 26 '20 at 20:12
  • @EnricoMariaDeAngelis This question doesn't show any research, which is exactly what the downvote buttons tooltip is labeled. The question should at least show what has been tried to solve the problem. – zett42 Mar 26 '20 at 21:10
  • Does this make it offtopic? – Enlico Mar 26 '20 at 21:44

3 Answers3

3

In the following I present an almost pure sed solution.

sed has an r command to read files, so you could in principle use that to read the file2. However, no subsequent command will affect the lines read from the file, so I cannot think of any way of using the r command effectively to do what you ask.

However, a solution is possible if file1 and file2 are both given in input to sed.

In the following, in order to distinguish the two files, I put a marker line (-----) that I give for granted is not in file2; it could be anywhere in file1 without creating any problems, however.

cat file2 <(echo '-----') file1 | sed -f script.sed

where script.sed is the following:

1{                     # only on line 1
  :a                   # begin while
  /-----/!{            # while the line does not contain the marker
    N                  # append the following line
    ba                 # end while
  }                    # here the pattern space is a multiline containing the list
  s/\n-----//          # remove the last newline and the marker
  h                    # put the multiline in the hold space
  d                    # delete, as we don't want to print anything so far
}                      # that's it, lines from 1 to the marker are processed
/stash\/scm\//{        # for lines matching this pattern
  G                    # we append the full hold space
  s/'\n\([^\n]*\)/\1'/ # and position the first entry in the list appropriately
  x                    # then we swap pattern and hold space
  s/[^\n]*\n//         # remove the first element of the list
  x                    # and swap again
}                      # now the hold space has one item less
Enlico
  • 23,259
  • 6
  • 48
  • 102
  • I tried running this script and it doesn't seem to do anything to the files. I'm not sure why but I appreciate the effort and I will try it out some more later for testing. Thank you. – Lucid Mar 27 '20 at 14:20
  • @Lucid, I had mistakely left the `-n` option there in the command, which prevents printing the pattern space everytime at the end of the script. Secondly, since I have used single quotes in the script, you **must** have the script in a file. Alternatively, if you want to copy the script directly in the command line, you have to change each `'` to `'"'"'` (which is an unreadable _closing single quote, double quoted single quote, opening single quote again_). – Enlico Mar 27 '20 at 14:36
2

This is a bash script that uses sed and reads File_2 (The file containing the replacements) line by line, thus reading one replacement at a time. I then replaced the lines in File_1 with a sed script.

while IFS= read -r line; do
    sed -i "0,/\/stash\/scm\/'/{s|/stash/scm/'|/stash/scm/${line}'|}" File_1.txt
done < File_2.txt

Some tricks used to do this:

  1. sed '0,/Apple/{s/Apple/Banana/}' input_filename Replace only the first occurrence in filename of the string Apple with the string Banana
  2. Using double quotes for the sed script to allow for variable expansion ${line}
  3. Making sure the search string to replace was being changed each iteration. This was done by including the ending single quote char ' for the search argument in the sed script s|/stash/scm/'|
  4. Reading a file line by line in a bash script
while IFS= read -r line; do
    echo $line
done < File_2.txt

Read File line by line in bash

Lenna
  • 1,220
  • 6
  • 22
  • 1
    Thank you soooo much!!!! This works. I had issues with bash while reads and now I think that I will understand it so much better now. I did notice one thing that was probably an issue on my end. When I ran your command verbatim, the text input file I used for File2.txt had extra spaces on each line and made the single quote ' appear on the next-line. I fixed this by using the command: `tr -d " \t" < infile.txt > outfile.txt` on the File2.txt and it works flawlessly!!! You are a hero – Lucid Mar 27 '20 at 14:16
  • Actually....It's kind of embarrassing but the whitespace/single-quote-on-new-line issue was fixed when I opened it in a proper text editor like sublime....LOL. I was just looking at it in nano and it had misplaced the single quote. So the above command isn't even needed...HAH – Lucid Mar 27 '20 at 15:07
  • 1
    Totally honest mistake :). Try ```nano --softwrap```. Also ```cntrl -``` will zoom out your terminal and ```cntrl +``` will zoom you in. I personally put ```alias nano='nano --smooth --positionlog --linenumbers --softwrap'``` in my ```~/.bashrc``` – Lenna Mar 27 '20 at 15:16
  • Hey Lenna, I'm trying to modify the script to also name the .tar files the same as what we added above. This is the code: `while IFS= read -r line; do # Replace only the first match: sed -e "0,/cvf'/{s|cvf'|${line}'|}" test2.txt done < shortened.txt` I can't quite get this to work, any ideas? – Lucid Mar 30 '20 at 17:46
  • 1
    I'm not sure I understand what you are describing. Could you edit your original post and in the desired output section show what you mean? – Lenna Mar 30 '20 at 17:52
  • I created another question, in-case you'd like to look at that. Would be easier than messing with the above code. https://stackoverflow.com/questions/60933382/replace-every-occurrence-of-a-word-with-each-line-from-an-list-file – Lucid Mar 30 '20 at 17:56
  • 1
    Hey Lenna, I was still getting the quote to appear on the next-line, but I figured out it's because I was copying files from my windows machine. It apparently adds `^M` at the end of the line after the script runs, but I was able to find a fix and it's this: `sed 's/\r//' file > file.new ` – Lucid Mar 31 '20 at 15:45
  • 1
    Good solution! For those one character removals I am a personal fan of ```tr -d ' – Lenna Mar 31 '20 at 18:36
2

You want to have lines like

sed 's#'/stash/scm/'#&something_from_file2#' file1 

You can make these lines with

    # Note:
    # / is not a delimiter, but part of the path
    # % is the delimiter in the current sed-command
    # # is the delimiter in the generated command.

sed 's%.*%s#/stash/scm/#\&&#%' file2

You can generate these commands on the fly and execute them on file1.

sed -f <(sed 's%.*%s#/stash/scm/#\&&#%' file2) file1

One problem left. Both commands will substitute all matches.
I will use the single quote given after the match. When something is put before the single quote in /stash/scm/' this is different than the match files when you look for the /stash/scm/' string including the quote.
You want to generate lines like

s#(/stash/scm/)(')#\1randomRepo.git\2#
s#(/stash/scm/)(')#\1differentRandomRepo.git\2#

Each substition should be done only once, so we consider file2 as one long line using the option -z:

sed -rzf <(sed 's%.*%s#(/stash/scm/)('\'')#\\1&\\2#%' file2) file1 
Walter A
  • 19,067
  • 2
  • 23
  • 43
  • Would there be anyway to expand it to re-name all the .tar files? I would be looking to name the .tar files the same name of the url, and if I replace /stash/scm/ with -cvf it's not adding text after it. – Lucid Mar 30 '20 at 14:01
  • What do you exactly want? Something like changing the line `tar --exclude='*.tar' -cvf .tar *` into `tar --exclude='*.tar' -cvf .tar /stash/scm/differentRandomRepo.tar` ? That woild be difficult for `sed`, perhaps post a new question. – Walter A Mar 30 '20 at 14:11
  • I would like to append the .tar after the -cvf flag. So it would look like: `tar --exclude='*.tar' -cvf stash/scm/differentRandomRepo.tar ` – Lucid Mar 30 '20 at 14:23