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 anA
,2
in line beginning with aB
,3
in line beginning with aC
,
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...