I am trying to replace an existing header in a file based on the contents of another file.

File1 - names.in:


File2- Datafile.tsv:

chr  begin  end  genes  genes genes genes  genes

File2 contains more lines but I want to replace each occurrence of the string genes in the first line with the successive entries from file1, so that the header becomes:

chr  begin  end  name1.Corrected  name2.Corrected  name3.Corrected  name4.Corrected name5.Corrected

I tried the following script:

sed -i -e "1s/\tgenes/\t$genenames\.Corrected/g" Datafile.tsv

But when I run it like so:

./sed.sh names.in 

I get the output as:

chr  begin  end  .in.Corrected  .in.Corrected  .in.Corrected  .in.Corrected  .in.Corrected

I can understand that the script does not read from the input.



for x in $(<names.in)
sed -i -s "s/ genes/ ${x}.Corrected/" Datafile.tsv

cat Datafile.tsv

chr  begin  end  name1.Corrected  name2.Corrected name3.Corrected name4.Corrected  name5.Corrected
  • do not use /g flag which will apply to all occurences (you want to replace one by one)
  • replace my ' ' by '\t'

  • $names.in would expand to "the value of names variable" (concatenate to .in), this would likely expand to .in

  • $(< name.in ) read file name.in, before command execution and replace with content of file ( end-of-line turn to white)

a more comprehensive shell

cat "$1" | while read x
sed -i -s "s/ genes/ ${x}.Corrected/" "$2"

called as

 myscript.sh names.in  Datafile.tsv
| improve this answer | |
  • 1
    If this is a huge file you're going to open/edit the file N times just to replace N occurrences of genes. Also, it will substitute on every line that matches not only on the first line. – don_crissti Sep 21 '15 at 13:32
  • Hi Archemar, may I ask why you have passed input as $(<name.in) instead of "$1"or simply use "$names.in"? – user3668772 Sep 21 '15 at 13:41

If I understand your correctly, you need to read the names from file1 and replace each occurrence of the string genes with successive entries from file1 but only in the first line of file2. If so, you could do this (assuming that you never have a string with genes as a substring, e.g. mygenes):

while read name
    sed -i "1s/genes/$name.Corrected/" file2
done < file1

Alternatively, you could also simply concatenate the contents of file1 and replace everything in one go:

names=$(perl -pe 's/\n/.Corrected /' file1)
sed -i "1s/genes .*/$names/" file2
| improve this answer | |

Nothing beats a while read loop when processing text but just in case you wanted to process your datafile.tsv only once:

sed 's|\(.*\)|1s/genes/\1.Corrected/|' names.in | sed -f - datafile.tsv

The first sed turns names.in into a sed script:


which is then executed by the second sed.
If all your genes columns are consecutive (like in your sample input) you could also run:

tr \\n \\t <names.in | \
sed -E 's|\t|.Corrected&|g;s|(.*).$|1s/genes.*genes/\1/|' | \
sed -f - datafile.tsv

which is similar to the first one, as the last sed executes the following sed script:

1s/genes.*genes/name1.Corrected name2.Corrected name3.Corrected name4.Corrected name5.Corrected/

Note that these won't edit your file in-place. To do that either use sed with -i (if supported) or redirect > the output to e.g. newfile then mv newfile datafile.tsv

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.