210

I have a situation where I want a bash script to replace an entire line in a file. The line number is always the same, so that can be a hard-coded variable.

I'm not trying to replace some sub-string in that line, I just want to replace that line entirely with a new line.

Are there any bash methods for doing this (or something simple that can be thrown into a .sh script).

Yu Shen
  • 2,770
  • 3
  • 33
  • 48
user788171
  • 16,753
  • 40
  • 98
  • 125

11 Answers11

307

Not the greatest, but this should work:

sed -i 'Ns/.*/replacement-line/' file.txt

where N should be replaced by your target line number. This replaces the line in the original file. To save the changed text in a different file, drop the -i option:

sed 'Ns/.*/replacement-line/' file.txt > new_file.txt
chepner
  • 497,756
  • 71
  • 530
  • 681
  • 1
    Is there a way to make sed modify the file in question instead of dumping to screen? – user788171 Jun 21 '12 at 19:42
  • I am now hit with sed: illegal option -- i is there a fix for this? – user788171 Jun 21 '12 at 19:56
  • sudo port install sed? are you on a mac? if so, just use the *nix version – Clayton Stanley Jun 23 '12 at 01:39
  • 14
    For me it says: `sed: -e expression #1, char 26: unknown option to ``s'` and my line is: `sed -i '7s/.*/http://localhost:8080/ASDF/services/REWS.REWSHttpSoap12Endpoint//' $TCE_SVN_HOME\trunk\tce\EWC\WebContent\WEB-INF\web.xml`. Any idea? – Danijel Sep 04 '13 at 07:51
  • 6
    Each `/` in the replacement text would be interpreted as the closing slash of the `s` command unless escaped (`\/`). The easiest thing to do, though, is to pick a different, unused character as the delimiter. For example, `sed -i '7s{.*}{http://...}' $TCE_SVN_HOME/trunk...`. – chepner Sep 04 '13 at 12:57
  • 6
    That explanation is a little confusing and does not seem to work. If you guys have problems with sed and its delimiter setting capabilities you may want to have a look at this: http://www.grymoire.com/Unix/Sed.html#uh-2 It says the first character behind the `s` determines the delimiter. So to change your command to use for example the `#` it would be like this: `sed -i '7s#.*#http://localhost:8080/ASDF/services/REWS.REWSHttpSoap12Endpo‌​int/#'` – func0der Aug 11 '15 at 18:11
  • 1
    If the `-i` command doesn't work on your machine, check the man-page. For me the command required a backup file extension after the flag, which you can leave empty: `-i ''`, and in some other version of sed the syntax is `--in-place=''`. – Leon S. Jan 24 '17 at 06:24
  • 10
    To expand on @meonlol's, macOS requires an `-e` if `-i` is given, as well; thus, the correct command becomes: `sed -e 'Ns/.*/replacement-line/' -i '' file.txt` – ELLIOTTCABLE May 16 '17 at 02:49
  • `sed` streams the entire file, but as noted in this answer, specifying the line number (if known) helps: in my case, a ~2-fold increase in execution speed (GNU sed 4.5). You can `grep -n` or `ripgrep` (`rg`) to find line numbers, based on pattern searches. – Victoria Stuart Apr 24 '18 at 18:56
  • 6
    Use **double quotes** if the replacement-line is not fixed, e.g. it's a variable, like this: `sed -i "Ns/.*/foo=$myVar" file.txt`. – gsamaras Jan 30 '19 at 14:53
  • @gsamaras You have to be careful with `myVar` in that case, though. Its value needs to be properly escaped, e.g. if `myVar=3/4`, you'll get an error from `sed`; the value would have to be `myVar='3\/4'` instead. – chepner Feb 05 '19 at 16:53
  • What about the last line? I guess `N=$(wc -l file | awk '{print $1;}'`, but cumbersome. – Erwann Feb 22 '22 at 11:52
  • `sed` uses `$` as the address of the last line: `$s/.../.../`. – chepner Feb 22 '22 at 12:48
49

I actually used this script to replace a line of code in the cron file on our company's UNIX servers awhile back. We executed it as normal shell script and had no problems:

#Create temporary file with new line in place
cat /dir/file | sed -e "s/the_original_line/the_new_line/" > /dir/temp_file
#Copy the new file over the original file
mv /dir/temp_file /dir/file

This doesn't go by line number, but you can easily switch to a line number based system by putting the line number before the s/ and placing a wildcard in place of the_original_line.

Kyle
  • 14,036
  • 11
  • 46
  • 61
27

Let's suppose you want to replace line 4 with the text "different". You can use AWK like so:

awk '{ if (NR == 4) print "different"; else print $0}' input_file.txt > output_file.txt

AWK considers the input to be "records" divided into "fields". By default, one line is one record. NR is the number of records seen. $0 represents the current complete record (while $1 is the first field from the record and so on; by default the fields are words from the line).

So, if the current line number is 4, print the string "different" but otherwise print the line unchanged.

In AWK, program code enclosed in { } runs once on each input record.

You need to quote the AWK program in single-quotes to keep the shell from trying to interpret things like the $0.

EDIT: A shorter and more elegant AWK program from @chepner in the comments below:

awk 'NR==4 {$0="different"} { print }' input_file.txt

Only for record (i.e. line) number 4, replace the whole record with the string "different". Then for every input record, print the record.

Clearly my AWK skills are rusty! Thank you, @chepner.

EDIT: and see also an even shorter version from @Dennis Williamson:

awk 'NR==4 {$0="different"} 1' input_file.txt

How this works is explained in the comments: the 1 always evaluates true, so the associated code block always runs. But there is no associated code block, which means AWK does its default action of just printing the whole line. AWK is designed to allow terse programs like this.

steveha
  • 74,789
  • 21
  • 92
  • 117
  • 3
    A little shorter: `awk 'NR==4 {$0="different"} { print }' input_file.txt`. – chepner Jun 21 '12 at 19:37
  • 2
    @chepner: A little shorter: `awk 'NR==4 {$0="different"}1' input_file.txt` – Dennis Williamson Jun 21 '12 at 19:42
  • @DennisWilliamson, I don't even know how that works! What does that trailing `1` do? (Note: I tested it and it does indeed work! I just don't understand how.) – steveha Jun 21 '12 at 19:59
  • 2
    I figured there'd be a way to abbreviate the unconditional print! @stevaha: the 1 is just a true value, meaning a "pattern" that always matches. And the default action for a match is to print the current line. – chepner Jun 21 '12 at 20:25
25

Given this test file (test.txt)

Lorem ipsum dolor sit amet,
consectetur adipiscing elit. 
Duis eu diam non tortor laoreet 
bibendum vitae et tellus.

the following command will replace the first line to "newline text"

$ sed '1 c\
> newline text' test.txt

Result:

newline text
consectetur adipiscing elit. 
Duis eu diam non tortor laoreet 
bibendum vitae et tellus.

more information can be found here

http://www.thegeekstuff.com/2009/11/unix-sed-tutorial-append-insert-replace-and-count-file-lines/

Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
Eric Robinson
  • 2,025
  • 14
  • 22
9

in bash, replace N,M by the line numbers and xxx yyy by what you want

i=1
while read line;do
  if((i==N));then
    echo 'xxx'
  elif((i==M));then
    echo 'yyy'
  else
    echo "$line"
  fi
  ((i++))
done  < orig-file > new-file

EDIT

In fact in this solution there are some problems, with characters "\0" "\t" and "\"

"\t", can be solve by putting IFS= before read: "\", at end of line with -r

IFS= read -r line

but for "\0", the variable is truncated, there is no a solution in pure bash : Assign string containing null-character (\0) to a variable in Bash But in normal text file there is no nul character \0

perl would be a better choice

perl -ne 'if($.==N){print"xxx\n"}elsif($.==M){print"yyy\n"}else{print}' < orig-file > new-file
Community
  • 1
  • 1
Nahuel Fouilleul
  • 18,726
  • 2
  • 31
  • 36
  • 1
    This was, for me, the easiest answer to convert into a function with a variable line number, variable new line, and a variable filepath. And for my use case, I did not need to escape any characters even though my new line contained `/`,`-`,`=` and `;`. Thank you! Ps. after having posted this comment, I scrolled down Daniel Bigham's answer. – a.t. Oct 28 '21 at 16:04
  • This answer is quite old. Now, I would use the perl command at the end, possibly passing the variables by environment. – Nahuel Fouilleul Oct 28 '21 at 20:41
7
# Replace the line of the given line number with the given replacement in the given file.
function replace-line-in-file() {
    local file="$1"
    local line_num="$2"
    local replacement="$3"

    # Escape backslash, forward slash and ampersand for use as a sed replacement.
    replacement_escaped=$( echo "$replacement" | sed -e 's/[\/&]/\\&/g' )

    sed -i "${line_num}s/.*/$replacement_escaped/" "$file"
}
Community
  • 1
  • 1
Daniel Bigham
  • 187
  • 3
  • 8
4

On mac I used

sed -i '' -e 's/text-on-line-to-be-changed.*/text-to-replace-the=whole-line/' file-name
nakeer
  • 641
  • 1
  • 9
  • 17
3

Excellent answer from Chepner. It is working for me in bash Shell.

 # To update/replace the new line string value with the exiting line of the file
 MyFile=/tmp/ps_checkdb.flag

 `sed -i "${index}s/.*/${newLine}/" $MyFile`

here
index - Line no
newLine - new line string which we want to replace.


Similarly below code is used to read a particular line in the file. This won't affect the actual file.

LineString=`sed "$index!d" $MyFile` 

here
!d - will delete the lines other than line no $index So we will get the output as line string of no $index in the file.

Kanagavelu Sugumar
  • 18,766
  • 20
  • 94
  • 101
2

Replace line with sed c\

Reference

sed c\ won't actually change the file, so you need to send its output to a temp file then cat the temp file into the original. Matching pattern not necessary because we can specify the line. Example: sed '1 c\' => will replace the text in line 1.

When writing the command, anything after the c\ part goes in a new line and should contain the new line text.


Finally, the example to "replace" line 1 in a file named original_file.txt with the text 'foo'. Note that we are taking the sed output and saving in a temp file, then outputing the temp file back into the original:

# file contents
$ cat original_file.txt
bar

# the replace command
$ sed '1 c\
  foo' original_file.txt > temp_file.txt
 
# output contents of temp_file and overwrite original
$ cat temp_file.txt > original_file.txt

# delete the temp file
$ rm -rf temp_file.txt
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Dec 14 '21 at 00:41
1

If you want to use parameters for lineNumber and replacingLine then awk not working easily (for me). I run after 1 hour try you can see below :)

lineNumber=$(grep --line-number "u want to replace line any keyword" values.txt  | cut -f1 -d:)
replacedLine="replacing new Line "
# for sed prepare => if your replacedLine have '/' character (URL or something) u must use command bellow. 
replacedLine=${replacedLine//\//\\\/}

sed -i $lineNumber's/.*/'"$replacedLine"'/'  values.txt
Talha Dilber
  • 117
  • 1
  • 3
  • The question is specifically about replacing a line with a given number. `grep` is not needed here. – sapanoia Jan 09 '22 at 14:09
-2

You can even pass parameters to the sed command:

test.sh

#!/bin/bash
echo "-> start"
for i in $(seq 5); do
  # passing parameters to sed
  j=$(($i+3))
  sed -i "${j}s/.*/replaced by '$i'!/" output.dat
done
echo "-> finished"
exit 

orignial output.dat:

a
b
c
d
e
f
g
h
i
j

Executing ./test.sh gives the new output.dat

a
b
c
replaced by '1'!
replaced by '2'!
replaced by '3'!
replaced by '4'!
replaced by '5'!
i
j
AndreH
  • 19
  • 4