0

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.

6
  • But why do you filter 0,/e/? Remove it. just /A/s/e/$1/ . If it not readable, consider /A/{ s/e/$1/; }; for some readability Commented May 18, 2022 at 17:55
  • sed is a poor choice here because sed can't count. While less efficient, you would be better served by reading each line and making the substitution based on the line number, e.g. c=1; while read -r line; do sed "s/e/1/$c" <<< "$line"; ((c++)); done < file Commented May 18, 2022 at 18:04
  • @KamilCuk after reading your comment I am ashamed. I thought that doing /A/s/e/$1/ would replace all the e of the line and not only the first one. In fact your proposition worked perfectly as I wanted it. Commented May 18, 2022 at 20:36
  • s command replaces the first occurrence (by default). Commented May 18, 2022 at 20:57
  • @DavidC.Rankin, I realy don't understand how your proposition work, i did it like that c=1; while read -r line; do sed -i "s/e/1/$c" /tmp/myfile <<< "$line"; cat /tmp/myfile; ((c++)); done < /tmp/myfile adding -i to modify myfile and a cat myfile thus I can see what happen each time i go throw the loop. The result is that : the first e of all my lines are replaced by 1 the first time in loop, the second time it's the second e that are left,...(e.g. : zzzezzzezzzezzzezzzezzzezzzezzze will be zzz1zzzezzz1zzzezzzezzz1zzze...) Commented May 18, 2022 at 21:12

2 Answers 2

1

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 .

Sign up to request clarification or add additional context in comments.

Comments

0

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...

3 Comments

Thank you for your explications. I get it all. Even if it stil doesn't work the way i want it, I've learn a few tricks. The aim is to have with, in input, myfile and argument $1 $2 $3 and, in output, first e of line begining by A B C replaced by $1 $2 $3 and then back to the begining of my instructions taking new arguments with brand new values in my $1 $2 $3 arguments and them going to replace the second column of e in myfile etc. In order to have the three arguments always different I run in the loop a function that modify randomly the values of the arguments.
I have only three lines so it's ok to not use a smarter way to read the lines in my case, but it is still usefull to understand your code for the day i'll want to do that on file with more than three lines. To modify my file I only use -i on sed it's enough for what i want to do. Last but not least thanks for the idea of the tests you make befor going on in the file !
Okay, either way. If it's only 3 lines every time, I'd do it just like @KamilCuk did. One call to sed is much more efficient that 3 calls to sed. (not that is matters for 3-lines..., but generally speaking)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.