2

I would like to replace the values 3 and 4 for 0 and 1, respectively, in all fields in the first, third, fiveth row and so on until the end of the following data set:

 2 4 3 0
 2 4 3 0
 3 0 4 4
 3 0 4 4
 4 2 4 3
 4 2 4 3
 2 3 4 2
 2 3 4 2

So, the desired result is:

 2 1 0 0
 2 4 3 0
 0 0 1 1
 3 0 4 4
 1 2 1 0
 4 2 4 3
 2 0 1 2
 2 3 4 2

I'm using the following code to do that:

awk '{for (i = 1; i <= NR; i=(i+2)) 
    if($i == 3) {$i = 0}
    if($i == 4) {$i = 1} 
}
END {print $0}' b.temp

However, the output for this code is only the values in the last row of b.temp file (2 3 4 2).

How can I do that? The code needs to that for any numer of rows and fields. The solution can be in awk, sed or other alternative in shell script.

Thanks in advance

5

With sed:

sed 'y/34/01/;n' file

Which means:

  • Replace 3 and 4 by 0 and 1 in this line and print it;
  • Get next line and print it;
  • Get next line and repeat cycle.

This would however fail if the data contained, for example, 14, transforming it to 11. To work around that, opt for

sed 's/\<4\>/1/g;s/\<3\>/0/g;n' file

Those \< and \> matches the beginning and end of a word.

  • Thank you for the answer. If I want do to a similar thing as before but start in the second row, like replace the values 3 and 4 by 1 and 0 (inverse order of the main question) in the second, fourth, sixth row and so on. Is it possible to do that usin a similar aproach? – andrec May 11 '20 at 12:15
  • @andrec Yes, you just need to put n before the y and invert the replacement order inside y command. I.e., sed 'n;y/34/10/' file. – Quasímodo May 11 '20 at 12:17
  • 1
    Thank you again! – andrec May 11 '20 at 12:34
5

The problem with your approach is that you have only one print statement, in the END block. The value of $0 in that block is the content of the last line of your file. So, your awk code will only print the last line of the file.

Also, note that awk applies the operations to every line unless you have a condition before the { ... } blocks (such as END which applies only to end-of-file). So, your code would try to check the 1st, 3rd etc field of every line and if it is 3 or 4 replace that with 0 or 1 respectively ... but never print the result.

In order apply a rule to every odd line, you can check if NR%2 is one (or simply non-zero):

awk 'NR%2{for (i=1;i<=NF;i++) if ($i==3 || $i==4) $i-=3}1' b.temp

The 1, when occuring outside of a rule, is an awk shorthand notation for "print the line resulting from all transformations performed".

0

More OPtimised command would be there below is my try

Command

awk 'NR==1||NR==3||NR==5||NR==7{gsub("3","0",$0)}1' file | awk 'NR==1||NR==3||NR==5||NR==7{gsub("4","1",$0)}1'

output

 2 1 0 0
 2 4 3 0
 0 0 1 1
 3 0 4 4
 1 2 1 0
 4 2 4 3
 2 0 1 2
 2 3 4 2
  • Besides needlessly calling awk twice (just do two gsubs), this would replace "44" with "11" (and 333 with 000). – Jeff Schaller May 18 '20 at 0:06

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.