Home > database >  Using sed in order to change a specific character in a specific line
Using sed in order to change a specific character in a specific line

Time:05-19

I'm a beginner in bash and here is my problem. I have a file just like this one:

Azzzezzzezzzezzz...
Bzzzezzzezzzezzz...
Czzzezzzezzzezzz...

I try in a script to edit this file.ABC letters are unique in all this file and there is only one per line.

I want to replace the first e of each line by a number who can be :

  • 1 in line beginning with an A,
  • 2 in line beginning with a B,
  • 3 in line beginning with a C,

and I'd like to loop this in order to have this type of result

Azzz1zzz5zzz1zzz...
Bzzz2zzz4zzz5zzz...
Czzz3zzz6zzz3zzz...

All the numbers here are random int variables between 0 and 9. I really need to start by replacing 1,2,3 in first exec of my loop, then 5,4,6 then 1,5,3 and so on.

I tried this

sed "0,/e/s/e/$1/;0,/e/s/e/$2/;0,/e/s/e/$3/" /tmp/myfile

But the result was this (because I didn't specify the line)

Azzz1zzz2zzz3zzz...
Bzzzezzzezzzezzz...
Czzzezzzezzzezzz...

I noticed that doing sed -i "/A/ s/$/ezzz/" /tmp/myfile will add ezzz at the end of A line so I tried this

sed -i "/A/ 0,/e/s/e/$1/;/B/ 0,/e/s/e/$2/;/C/ 0,/e/s/e/$3/" /tmp/myfile

but it failed

sed: -e expression #1, char 5: unknown command: `0'

Here I'm lost.

I have in a variable (let's call it number_of_e_per_line) the number of e in either A, B or C line.

Thank you for the time you take for me.

CodePudding user response:

Just apply s command on the line that matches A.

sed '
  /^A/{ s/e/$1/; }
  /^B/{ s/e/$2/; }
  # or shorter
  /^C/s/e/$3/
'

s command by default replaces the first occurrence. You can do for example s/s/$1/2 to replace the second occurrence, s/e/$1/g (like "Global") replaces all occurrences.

0,/e/ specifies a range of lines - it filters lines from the first up until a line that matches /e/.

sed is not part of Bash. It is a separate (crude) programming language and is a very standard command. See https://www.grymoire.com/Unix/Sed.html .

CodePudding user response:

Continuing from the comment. sed is a poor choice here unless all your files can only have 3 lines. The reason is sed processes each line and has no way to keep a separate count for the occurrences of 'e'.

Instead, wrapping sed in a script and keeping track of the replacements allows you to handle any file no matter the number of lines. You just loop and handle the lines one at a time, e.g.

#!/bin/bash

[ -z "$1" ] && {  ## valiate one argument for filename provided
  printf "error: filename argument required.\nusage: %s filename\n" "./$1" >&2
  exit 1
}

[ -s "$1" ] || {  ## validate file exists and non-empty
  printf "error: file not found or empty '%s'.\n" "$1"
  exit 1
}

declare -i n=1    ## occurrence counter initialized 1

## loop reading each line
while read -r line || [ -n "$line" ]; do
  [[ $line =~ ^.*e.*$ ]] || continue    ## line has 'e' or get next
  sed "s/e/1/$n" <<< "$line"            ## substitute the 'n' occurence of 'e'
  ((n  ))                               ## increment counter
done < "$1"

Your data file having "..." at the end of each line suggests your files is larger than the snippet posted. If you have lines beginning 'A' - 'Z', you don't want to have to write 26 separate /match/s/find/replace/ substitutions. And if you have somewhere between 3 and 26 (or more), you don't want to have to rewrite a different sed expression for every new file you are faced with.

That's why I say sed is a poor choice. You really have no way to make the task a generic task with sed. The downside to using a script is it will become a poor choice as the number of records you need to process increase (over 100000 or so just due to efficiency)

Example Use/Output

With the script in replace-e-incremental.sh and your data in file, you would do:

$ bash replace-e-incremental.sh file
Azzz1zzzezzzezzz...
Bzzzezzz1zzzezzz...
Czzzezzzezzz1zzz...

To Modify file In-Place

Since you make multiple calls to sed here, you need to redirect the output of the file to a temporary file and then replace the original by overwriting it with the temp file, e.g.

$ bash replace-e-incremental.sh file > mytempfile && mv -f mytempfile file
$ cat file
Azzz1zzzezzzezzz...
Bzzzezzz1zzzezzz...
Czzzezzzezzz1zzz...
  •  Tags:  
  • bash
  • Related