2

For example, I have two files:

file1:

1
4
X
5
X
7

file2:

2
3
5
X
X
1

I want to replace X-lines of file1 with whatever is in file2 on the corresponding line:

result:

1
4
5
5
X
7

I prefer the solution using CLI commands, e.g., sed.

  • I can do that with paste and two times processing each line with sed which seems not very efficient and concise: paste file1 file2 | sed -E -e 's/^X\t(\S+)/\1/' -e 's/^(\S+)\t(\S+)/\1/' – Fibo Kowalsky Sep 24 '18 at 0:11
  • 1
    Note that sed isn't a bash command at all. Do you really want to do this in bash or can we use external tools like sed? – terdon Sep 24 '18 at 13:00
2
$ paste file1 file2 | awk -F '\t' '$1 == "X" { $1 = $2 } { print $1 }'
1
4
5
5
X
7

The paste command will generate the tab-delimited output

1       2
4       3
X       5
5       X
X       X
7       1

and the awk script simply sets the first tab delimited field to the contents of the second field if the first is the character X, and then prints the first (now possibly modified) field.

A different approach suggested by glenn jackman which doesn't modify the incoming data but just selects the field to print depending on the data in the first column:

$ paste file1 file2 | awk -F '\t' '{ print $1 == "X" ? $2 : $1 }'

Using GNU sed:

$ paste file1 file2 | sed -E -e '/^X/s/^[^\t]+\t//' -e 's/\t.*$//'
1
4
5
5
X
7

The two sed expressions do the following:

  1. If the current line starts with X, then the first substitution removes everything up to and including the first tab character. This effectively moves the second column of the paste output into the first column if the first column is an X.
  2. The second expression removes the tab character and anything after it. This removes the data we're not interested in.
  • Though this assumes that lines in both files don't contain any tabs... – don_crissti Sep 24 '18 at 14:38
  • 1
    Somewhat simpler (debatable?) awk: awk '{print $1 == "X" ? $2 : $1}' – glenn jackman Sep 24 '18 at 14:40
  • @glennjackman Yes, possibly. I'll include it. – Kusalananda Sep 24 '18 at 16:15
3

If file2 will fit in memory, then you could use awk. Have it read in file2 first, then when it's processing file1, if it sees "X", replace it from the file2 array:

$ awk 'NR == FNR { lines[NR]=$0; } NR != FNR { if ($0 == "X") print lines[FNR]; else print $0 }' file2 file1

Re-formatted, that's:

$ awk 'NR == FNR { lines[NR]=$0; } 
       NR != FNR { if ($0 == "X") print lines[FNR]; 
                   else           print $0 
                 }' file2 file1

Notice that file2 is the first filename; file1 is the second filename.

  • shorter as awk 'NR==FNR{ lines[NR]=$0; next}; $0=="X"{ $0=lines[FNR]}; 1' file2 file1 – don_crissti Sep 24 '18 at 18:57
0

With bash

while IFS= read -r -u3 f1; IFS= read -r -u4 f2; do
    [[ $f1 == X ]] && echo "$f2" || echo "$f1"
done 3<file1 4<file2

or more generally, POSIX

while IFS= read -r f1 <&3; IFS= read -f f2 <&4; do
    [ "$f1" = X ] && echo "$f2" || echo "$f1"
done 3<file1 4<file2

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.