Common
Common

Reputation: 443

Updating a specific field with sed

I'm trying to update a specific field on a specific line with the sed command in Bourne Shell.

Lets say I have a file TopScorer.txt

Player:Games:Goals:Assists
Salah:9:9:3
Kane:10:8:4

And I need to update the 3rd Column (Goals) of a player, I tried this command and it works unless Games and Goals have the same value then it updates the first one

player="Salah"
NewGoals="10"
OldGoals=$(awk -F':' '$1=="'$player'"' TopScorer.txt | cut -d':' -f3)
sed -i '/^'$player'/ s/'$OldGoals'/'$NewGoals'/' TopScorer.txt

Output> Salah:10:9:3 instead of Salah:9:10:3

Is there any solution? Should I use delimiters and $3==... to specify that field? I also tried the option /2 for second occurrence but it's not very convenient in my case.

Upvotes: 2

Views: 1360

Answers (5)

potong
potong

Reputation: 58453

This might work for you (GNU sed):

(player=Salah;newGoals=10;sed -i "/^$name/s/[^:]*/$newGoals/3" /tmp/file)

Use a sub shell so as not to pollute the current shell (...). Use sed and pattern matching to match the first field of each record to the variable player and replace the third field of the matching record with the contents of newGoals.

P.S. If the variables are needed in further processes the sub shell is not necessary i.e. remove the ( and )

Upvotes: 2

stack0114106
stack0114106

Reputation: 8711

You can try it with Perl

$ player="Salah"
$ NewGoals="10"
$ perl -F: -lane "\$F[2]=$NewGoals  if ( \$F[0] eq $player ) ; print join(':',@F) " TopScorer.txt
Player:Games:Goals:Assists
Salah:9:10:3
Kane:10:8:4
$

or export them and call Perl one-liner within single quotes

$ export NewGoals="10"
$ export player="Salah"
$  perl -F: -lane '$F[2]=$ENV{NewGoals} if $F[0] eq $ENV{player} ; print join(":",@F) ' TopScorer.txt
Player:Games:Goals:Assists
Salah:9:10:3
Kane:10:8:4
$

Note that Perl has -i switch and you can do the replacement in-place, so

$ perl -i.bak -F: -lane '$F[2]=$ENV{NewGoals} if $F[0] eq $ENV{player} ; print join(":",@F) ' TopScorer.txt
$ cat TopScorer.txt
Player:Games:Goals:Assists
Salah:9:10:3
Kane:10:8:4
$

Upvotes: 2

RavinderSingh13
RavinderSingh13

Reputation: 133545

Could you please try following once. Advantage of this approach is that I am not hard coding field for Goals. This program will look for header's field wherever Goal is present(eg--> 4th or 5th any field), it will change for that specific column only.

1st Solution: When you need to make changes to all occurrences of player name then use following.

NewGoals=10
awk -v newgoals="$NewGoals" 'BEGIN{FS=OFS=":"} FNR==1{for(i=1;i<=NF;i++){if($i=="Goals"){field=i}}} FNR>1{if($1=="Salah"){$field=newgoals}} 1' Input_file

2nd Solution: In case you want to change a specific player's goals value to specific row only then try following.

NewGoals=10
awk -v newgoals="$NewGoals" 'BEGIN{FS=OFS=":"} FNR==1{for(i=1;i<=NF;i++){if($i=="Goals"){field=i}}} FNR>1{if($1=="Salah" && FNR==2){$field=newgoals}} 1' Input_file

Above will make changes only for row 2, you coud change it by changing FNR==2 in 2nd condition where FNR refers row number inawk. In case you want to save output into Input_file itself then you could append > temp_file && mv temp_file Input_file to above codes.

Upvotes: 1

Inian
Inian

Reputation: 85690

You can just do this with awk alone and not with sed. Also note that awk has an internal syntax to import variables from the shell. So your code just becomes

awk -F: -v pl="$player" -v goals="$NewGoals" 
    'BEGIN { OFS = FS } $1 == pl { $3= goals }1' TopScorer.txt

The -F: sets the input de-limiter as : and the part involving -v imports your shell variables to the context of awk. The BEGIN { OFS = FS } sets the output field separator to the same as input. Then we do the match using the imported variables and update $3 to the required value.

To make the modifications in-place, use a temporary file

awk -F: -v pl="$player" -v goals="$NewGoals" 
   'BEGIN { OFS = FS } $1 == pl { $3= goals }1' TopScorer.txt > tmpfile && mv tmpfile TopScorer.txt

Upvotes: 4

EchoMike444
EchoMike444

Reputation: 1692

This will work .

With the first part of sed , i try to match a full line that math the player, and i keep all fields i want to keep by using \( .

The second part , i rebuild the line with some constants and the value of \1 and the value of \2

player="Salah"
NewGoals="10"
sed "s/^$player:\([^:]*\):[^:]*:\([^:]*\)\$/$player:\1:$NewGoals:\2/"

Upvotes: 1

Related Questions