How to replace a string on the 5th line of multiple text files?

I want to replace the 5th line of multiple text files (file1.txt,file2.txt,file3.txt,file4.txt) with the string " Good Morning " using a single terminal command.

All the text files are located on my ~/Desktop.

Note: My desktop consists of 6 .txt files.I want to apply the change to above mentioned 4 text files only.


Here are a few approaches. I am using brace expansion (file{1..4}.txt) which means file1.txt file2.txt file3.txt file4.txt

  1. Perl

    perl -i -pe 's/.*/ Good Morning / if $.==5' file{1..4}.txt
    

    Explanation:

    • -i: causes perl to edit the files in place, changing the original file.

      If -i is followed with a file extension suffix, then a backup is created for every file that is modified. Ex: -i.bak creates a file1.txt.bak if file1.txt is modified during the execution.

    • -p: means read the input file line by line, apply the script and print it.

    • -e: allows you to pass a script from the command line.

    • s/.*/ Good Morning /: That will replace the text in the current line (.*) with Good Morning.

    • $. is a special Perl variable that holds the current line number of the input file. So, s/foo/bar/ if $.==5, means replace foo with bar only on the 5th line.

  2. sed

    sed -i '5s/.*/ Good Morning /' file{1..4}.txt
    

    Explanation:

    • -i: Like for perl, edit file in place.

    By default, sed prints each line of the input file. The 5s/pattern/replacement/ means substitute pattern with replacement on the 5th line.

  3. Awk

    for f in file{1..4}.txt; do 
        awk 'NR==5{$0=" Good Morning "}1;' "$f" > foobar && mv foobar "$f"; 
    done
    

    Explanation:

    awk has no equivalent to the -i option¹ which means that we need to create a temporary file (foobar) which is then renamed to overwrite the original. The bash loop for f in file{1..4}.txt; do ... ; done simply goes through each of file{1..4}.txt, saving the current file name as $f. In awk, NR is the current line number and $0 is the content of the current line. So, the script will replace the line ($0) with " Good Morning " only on the 5th line. 1; is awk for "print the line".

    ¹Newer versions do as devnull showed in his answer.

  4. coreutils

    for f in file{1..4}.txt; do 
        (head -4 "$f"; echo " Good Morning "; tail -n +6 "$f") > foobar && 
        mv foobar "$f"; 
    done 
    

    Explanation:

    The loop is explained in the previous section.

    • head -4: print the first 4 lines

    • echo " Good Morning ": print " Good Morning "

    • tail -n +6: print everything from the 6th line to the end of the file

    The parentheses ( ) around those three commands allow you to capture the output of all three (so, 1st 4 lines, then " Good morning ", then the rest of the lines) and redirect them to a file.


You could use sed:

sed '5s/^/Good morning /' file

would append Good morning on the fifth line of a file.

If you want to replace the contents on line 5 instead, say:

sed '5s/.*/Good morning/' file

If you wanted to save the changes to the file in-place, use the -i option:

sed -i '5s/.*/Good morning/' file

sed can handle more than one file at a time. You can just add more filenames onto the end of the command. You can also use bash expansions to match particular files:

# manually specified
sed -i '5s/.*/Good morning/' file1.txt file2.txt file3.txt file4.txt

# wildcard: all files on the desktop
sed -i '5s/.*/Good morning/' ~/Desktop/*

# brace expansion: file1.txt, file2.txt, file3.txt, file4.txt
sed -i '5s/.*/Good morning/' file{1..4}.txt

You can read more about brace expansions here.


GNU awk versions 4.1.0 and higher come with an extension that enable in-place editing. So you could say:

gawk -i inplace 'NR==5{$0="Good morning"}7' file

to replace line #5 in the file with Good morning!


I didn't see python solution so here it is:

import sys
import os
def open_and_replace(filename):

    with open(filename) as read_file:
        temp = open("/tmp/temp.txt","w")
        for index,line in enumerate(read_file,1):
            if  index == 5:
                temp.write("NEW STRING\n")
            else:
                temp.write(line.strip() + "\n")
        temp.close()
    os.rename("/tmp/temp.txt",filename)

for file_name in sys.argv[1:]:
    open_and_replace(file_name)

Basic idea is that for each file provided on command-line as argument, we write out a temporary file and enumerate each line in the original file. If index of the line is 5, we write out line that is different. The rest is just replacing old file with temp file Demo:

$> ls
file1.txt  file2.txt  file3.txt
$> cat file1.txt                                                               
line 1
line 2
line 3
line 4
GOOD MORNING
line 6
$> python ~/replace_5th_line.py file1.txt file2.txt  file3.txt                 
$> cat file1.txt                                                               
line 1
line 2
line 3
line 4
NEW STRING
line 6
$> cat file2.txt                                                               
line 1
line 2
line 3
line 4
NEW STRING
line 6

The same can be achieved with list comprehension. Below is a one-liner of the same script:

cat /etc/passwd | python -c 'import sys; print "\n".join(["CUSTOM"  if index == 5 else line.strip() for index,line in enumerate(sys.stdin,1)])'

or without cat

python -c 'import sys; print "\n".join(["CUSTOM"  if index == 5 else line.strip() for index,line in enumerate(sys.stdin,1)])' < /etc/passwd

What is left there is to simply redirect output of edited contents into another file with > output.txt