Home > database >  Editing a file using awk/sed in file ( like sed -i )
Editing a file using awk/sed in file ( like sed -i )

Time:05-09

I have a file that has this :

cat textfile.csv

Name,Age,City
Jen,40,San Diego
Jen,,NYC
Bob,29,Miami,
,30,Boston
Jen,,NYC
Bob,21,Seattle

instead of using vim to manually do it .. is there a way I can use awk/sed to edit the 2nd Jen's age from ,, to whatever I want?

Also notice how there are 2 Jen,,NYC ... I want to be able to choose the 2nd Jen,,NYC (above the seattle one) and edit that one

Eventually this file will grow and there maybe more than 1 Bob/Jen so I want to be able to target the specific Jen I want and edit the field (whether that be name,age,city) if needed.

CodePudding user response:

Here an attempt at a solution:

modify.bash

#!/bin/bash

# Arguments
#   $1: name to find
#   $2: previous line
#   $3: new age
if [[ $# -eq 3 ]]
then
    name="$1"
    previous="$2"
    newage="$3"
else
    echo "Usage: $0 <name> <previous> <newage>"
    exit 1
fi

# CSV input file
csvfile="textfile.csv"
if [[ ! -f "$csvfile" ]]
then
    echo "ERROR: cannot find file $csvfile"
    exit 1
fi

# awk script
if [[ ! -f modify.awk ]]
then
    echo "ERROR: missing awk script modify.awk"
    exit 1
fi

awk -f modify.awk -v previous="$previous" name="$name" newage="$newage"  textfile.csv
  • this bash script it just a wrapper to the awk script, to process arguments and do some validations.
  • -v of awk allows you to create variables that awk can use in its code.
  • the variable values must be double quoted to handle spaces properly.
  • this script prints the result to the screen without modifying the original file. That output can be redirected to a file using >newfile.

modify.awk

BEGIN { FS="," }
$0~previous {
    f = 1
    print
    next
}
f && $0~name {
    f = 0
    print $1 "," newage "," $3
    next
}
{ f = 0; print }
  • since awk needs to match on a pattern which is stored in a variable, use syntax $0~VARIABLE instead of /pattern/.
  • if the previous pattern is found, set f = 1.
  • when f == 1 AND (here &&) the name is found, print the name (first field), the new age (stored in a variable), and the city. And do not forget to set f back to 0.
  • lastly reset f, print the line and keep going.

Using these scripts

  • Modifies the age of Jen in the line that follows Boston city.

    $ modify.bash Jen Boston 99
    
  • same as before, but modifies the Jen after the "San Diego" line. This illustrates how to set $previous when there is a space involved.

    $ modify.bash Jen "San Diego" 99
    
  • same again, but this time specify the entire previous line. Since the pattern can be anything, as long as it matches, it is ok.

    $ modify.bash Jen ",30,Boston" 99
    

CodePudding user response:

Using nested sed expressions, the exact line of the match can be targetted. To target the second line starting with Jen, the first must be excluded with 0,/^Jen/! followed by a nested expression to prevent the command from affecting further matches.

$ sed '0,/^Jen/!{0,/^Jen/s/,/&31/}' input_file
Name,Age,City
Jen,40,San Diego
Jen,31,NYC
Bob,29,Miami,
,30,Boston
Jen,,NYC
Bob,21,Seattle
$ sed '0,/^Jen/!{0,/^Jen/!{0,/^Jen/s/,/&31/}}'
Name,Age,City
Jen,40,San Diego
Jen,,NYC
Bob,3129,Miami,
,3130,Boston
Jen,31,NYC
Bob,21,Seattle

CodePudding user response:

I would use GNU AWK for this task following way, say you want set age of 2nd Jen,,NYC to 35 and let textfile.csv content be

Name,Age,City
Jen,40,San Diego
Jen,,NYC
Bob,29,Miami,
,30,Boston
Jen,,NYC
Bob,21,Seattle

then

awk -i inplace 'BEGIN{FS=OFS=","}$0=="Jen,,NYC"{cnt =1;if(cnt==2){$2=35}}{print}' textfile.csv

will alter textfile.csv content to

Name,Age,City
Jen,40,San Diego
Jen,,NYC
Bob,29,Miami,
,30,Boston
Jen,35,NYC
Bob,21,Seattle

Explanation: I inform GNU AWK that both field separator (FS) and output field separator (OFS) is ,. Then if currently processed line ($0) is Jen,,NYC I increase cnt value by 1. if value of cnt is equal to 2 I set 2nd field value to be 35. For each line, change or not, I print it. You might elect to first run without -i inplace so GNU AWK will output to standard output, check if it did give what you desire, then apply -i inplace.

(tested in gawk 4.2.1)

  • Related