96

I want to insert multiple lines into a file using shell script. Let us consider my input file contents are: input.txt:

abcd
accd
cdef
line
web

Now I have to insert four lines after the line 'cdef' in the input.txt file. After inserting my file should change like this:

abcd
accd
cdef
line1
line2
line3
line4
line
web

The above insertion I should do using the shell script. Can any one help me?

smarber
  • 4,829
  • 7
  • 37
  • 78
user27
  • 1,557
  • 4
  • 16
  • 16

12 Answers12

123

Another sed,

sed '/cdef/r add.txt' input.txt

input.txt:

abcd
accd
cdef
line
web

add.txt:

line1
line2
line3
line4

Test:

sat:~# sed '/cdef/r add.txt' input.txt
abcd
accd
cdef
line1
line2
line3
line4
line
web

If you want to apply the changes in input.txt file. Then, use -i with sed.

sed -i '/cdef/r add.txt' input.txt

If you want to use a regex as an expression you have to use the -E tag with sed.

sed -E '/RegexPattern/r add.txt' input.txt
yoano
  • 1,466
  • 2
  • 16
  • 20
sat
  • 14,589
  • 7
  • 46
  • 65
  • 1
    Is there a way to do this the other way around (remove text found in add.txt from input.txt) ? – Sina Sep 14 '14 at 00:20
  • 3
    A variation, that allows for an anonymous file is to pipe a here document into a sed invocation and use the `r` command to read from `/dev/stdin`. – potong Aug 02 '18 at 14:19
  • Hi, is there a quick way to add 4 spaces before the added lines from `add.txt` ? – Hud Oct 16 '20 at 12:53
  • 1
    The problem with this approach is that if you have 100 occurrences of `cdef` in the file, it will paste `add.txt` contents into your file 100 times as well :( – RAM237 Aug 07 '22 at 14:02
52

Using GNU sed:

sed "/cdef/aline1\nline2\nline3\nline4" input.txt

If you started with:

abcd
accd
cdef
line
web

this would produce:

abcd
accd
cdef
line1
line2
line3
line4
line
web

If you want to save the changes to the file in-place, say:

sed -i "/cdef/aline1\nline2\nline3\nline4" input.txt
devnull
  • 118,548
  • 33
  • 236
  • 227
  • 4
    I am new to `sed` but wow I am in love with its power! If you want to append an empty newline *first* you have to escape the backslash character after the append command like this : `sed "/cdef/a\\\nline1\nline2\nline3\nline4" input.txt`. I am not sure why it works like that though, if somebody could explain this would be nice! – hdl Sep 23 '15 at 13:20
  • 2
    The current command inserts the line* on every mention of `cdef`, is there a way to make it so that it only inserts on the first time it encounters `cdef` and no more? – CMCDragonkai Oct 20 '15 at 13:32
  • 2
    MacOS `sed` doesn't implement the `a` command precisely the same as GNU `sed`, so the above won't work in MacOS without mods - at least as of macOS 10.13 ( see https://unix.stackexchange.com/a/131940/230763 ) – Mike Lutz Jul 13 '18 at 22:19
  • MacOS `sed` also doesn't treat the `-i` option the same as GNU `sed`. On MacOS, `-i` requires an argument with a backup suffix. You would want to specify `-i ''`. – Craig Buchek Feb 20 '23 at 03:48
31
sed '/^cdef$/r'<(
    echo "line1"
    echo "line2"
    echo "line3"
    echo "line4"
) -i -- input.txt
rindeal
  • 993
  • 11
  • 16
  • Missed this good answer. Shorter but less readable is `sed '/^cdef$/r' <(printf "%s\n" line{1..4}) -i -- input.txt`. – Walter A Jan 02 '19 at 10:40
  • 2
    Note that this won't work in containers when you're issuing the command from the host, as the file descriptor from `<(...)` will be created on the host and not the guest. Using the `/someline/a` method works better, especially if you do something like `sed "/someline/a$(echo 'here you can have newlines' | tr \\n @ | sed '\''s/@/\\n/g'\')" -i -- input.txt` (not a full example and not properly tested, I don't have time for it now, but this should include the building blocks for what you need). – Luc Feb 19 '21 at 00:04
19

Using awk:

awk '/cdef/{print $0 RS "line1" RS "line2" RS "line3" RS "line4";next}1' input.txt 

Explanation:

  • You find the line you want to insert from using /.../
  • You print the current line using print $0
  • RS is built-in awk variable that is by default set to new-line.
  • You add new lines separated by this variable
  • 1 at the end results in printing of every other lines. Using next before it allows us to prevent the current line since you have already printed it using print $0.

$ awk '/cdef/{print $0 RS "line1" RS "line2" RS "line3" RS "line4";next}1' input.txt
abcd
accd
cdef
line1
line2
line3
line4
line
web

To make changes to the file you can do:

awk '...' input.txt > tmp && mv tmp input.txt
jaypal singh
  • 74,723
  • 23
  • 102
  • 147
8

Here is a more generic solution based on @rindeal solution which does not work on MacOS/BSD (/r expects a file):

cat << DOC > input.txt
abc
cdef
line
DOC
$ cat << EOF | sed '/^cdef$/ r /dev/stdin' input.txt
line 1
line 2
EOF

# outputs:
abc
cdef
line 1
line 2
line

This can be used to pipe anything into the file at the given position:

$ date | sed '/^cdef$/ r /dev/stdin' input.txt

# outputs
abc
cdef
Tue Mar 17 10:50:15 CET 2020
line

Also, you could add multiple commands which allows deleting the marker line cdef:

$ date | sed '/^cdef$/ {
  r /dev/stdin
  d
}' input.txt

# outputs
abc
Tue Mar 17 10:53:53 CET 2020
line
mana
  • 6,347
  • 6
  • 50
  • 70
1

You can use awk for inserting output of some command in the middle of input.txt.
The lines to be inserted can be the output of a cat otherfile, ls -l or 4 lines with a number generated by printf.

awk 'NR==FNR {a[NR]=$0;next}
    {print}
    /cdef/ {for (i=1; i <= length(a); i++) { print a[i] }}'
    <(printf "%s\n" line{1..4}) input.txt
Walter A
  • 19,067
  • 2
  • 23
  • 43
0

This answer is easy to understand

  • Copy before the pattern
  • Add your lines
  • Copy after the pattern
  • Replace original file

    FILENAME='app/Providers/AuthServiceProvider.php'

STEP 1 copy until the pattern

sed '/THEPATTERNYOUARELOOKINGFOR/Q' $FILENAME >>${FILENAME}_temp

STEP 2 add your lines

cat << 'EOL' >> ${FILENAME}_temp

HERE YOU COPY AND
PASTE MULTIPLE
LINES, ALSO YOU CAN
//WRITE COMMENTS

AND NEW LINES
AND SPECIAL CHARS LIKE $THISONE

EOL

STEP 3 add the rest of the file

grep -A 9999 'THEPATTERNYOUARELOOKINGFOR' $FILENAME >>${FILENAME}_temp

REPLACE original file

mv ${FILENAME}_temp $FILENAME

if you need variables, in step 2 replace 'EOL' with EOL

cat << EOL >> ${FILENAME}_temp

this variable will expand: $variable1

EOL
lalo
  • 901
  • 2
  • 10
  • 15
0

suppose you have a file called 'insert.txt' containing the lines you want to add:

line1
line2
line3
line4

If the PATTERN 'cdef' REPEATS MULTIPLE TIMES in your input.txt file, and you want to add the lines from 'insert.txt' after ALL occurrences of the pattern 'cdef' , then a easy solution is:

sed -i -e '/cdef/r insert.txt' input.txt

but, if the PATTERN 'cdef' REPEATS MULTIPLE TIMES in your input.txt file, and you want to add the lines from 'insert.txt' ONLY AFTER THE FIRST OCCURRENCE of the pattern, a beautiful solution is:

printf "%s\n" "/cdef/r insert.txt" w | ed -s input.txt

Both solution will work fine in case the pattern happens only once in your input.txt file.

Diving
  • 784
  • 1
  • 9
  • 17
0

Based on @rindeal solution but with better readability of the input

sed '/^cdef$/r'<(cat <<EOF
line1
line2
line3
line4
EOF
) -i -- input.txt
Pavel Tankov
  • 429
  • 1
  • 4
  • 12
0

Putting the content over the 4th line (like you want)

sed -i "4i line1\nline2\nline3\nline4" input.txt

If you don't want to save the changes to the file in-place, remove "-i" :

sed "4i line1\nline2\nline3\nline4" input.txt

Using sed by GNU*

If you started input.txt with:

abcd
accd
cdef
line
web

this would produce:

abcd
accd
cdef
line1
line2
line3
line4
line
web
JorgeM
  • 617
  • 5
  • 11
-1

I needed to template a few files with minimal tooling and for me the issue with above sed -e '/../r file.txt is that it only appends the file after it prints out the rest of the match, it doesn't replace it.

This doesn't do it (all matches are replaced and pattern matching continues from same point)

#!/bin/bash

TEMPDIR=$(mktemp -d "${TMPDIR:-/tmp/}$(basename $0).XXXXXXXXXXXX")
# remove on exit
trap "rm -rf $TEMPDIR" EXIT

DCTEMPLATE=$TEMPDIR/dctemplate.txt
DCTEMPFILE=$TEMPDIR/dctempfile.txt

# template that will replace
printf "0replacement
1${SHELL} data
2anotherlinenoEOL" > $DCTEMPLATE

# test data
echo -e "xxy \n987 \nxx xx\n yz yxxyy" > $DCTEMPFILE

# print original for debug
echo "---8<--- $DCTEMPFILE"
cat $DCTEMPFILE
echo "---8<--- $DCTEMPLATE"
cat $DCTEMPLATE
echo "---8<---"

# replace 'xx' -> contents of $DCTEMPFILE
perl -e "our \$fname = '${DCTEMPLATE}';" -pe 's/xx/`cat $fname`/eg' ${DCTEMPFILE}
Pasi Savolainen
  • 2,460
  • 1
  • 22
  • 35
-1

if you want to do that with a bash script, that's useful.

echo $password | echo 'net.ipv4.ping_group_range=0 2147483647' |  sudo -S tee -a /etc/sysctl.conf
N'bia
  • 69
  • 7
  • 2
    Can you add some explanation to your command to help the future readers understand what has been done? – Ruli Jan 04 '21 at 09:40