Replacing multiple lines of text using sed - bash

I have a file input.txt containing the following text
Total users:
abc
xyz
pqrs
The number of users is subject to change but there will be atleast one user all the time. I want to replace the user names with wild card '*' using sed
Total users:
*
Is there a way by which I can look for 'Total users:' string and replace everything after that with a *. Some thing like
sed s/Total users: \n[Till EOF]/Toal users: \n*/g

One way:
$ sed '3,$s/\S*$/*/' file
Total users:
*
*
*
This does the substitution s/\S*$/*/ from the third line in the file till the last line 3,$ where the substitution replaces any none whitespace characters till the end of line with a single *. Modified the substitution command as appropriate for your actual file as this will fail if you allow spaces in usernames. A more robust replacement might be:
$ sed -r '3,$s/(\s+).*/\1*/' file
Total users:
*
*
*
This will replace after the initial whitespace with a single *. Use the -i option if you want to store the changes back to the file:
$ sed -ri '3,$s/(\s+).*/\1*/' file
Edit:
To replace all users with a single *:
$ sed -r '3{s/(\s+).*/\1*/;q}' file
Total users:
*
Although creating this file would have been much quicker than asking a question.

This might be what you need
sed -e "/Total users:/b; s|^\([[:blank:]]*\)[^[:blank:]]\+\(.*\)|\1*\2|" input.txt

This will do it:
sed -e '/^Total users:/ s/.*/&\n\n */; q' input.txt
When it finds a line starting with Total users:, it replaces with itself, appends two line breaks and asterisk, and exits without processing any further lines.
If you are using a more limited version of sed where you cannot use ; to separate multiple commands, you can write like this to work around:
sed -e '/^Total users:/ s/.*/&\n\n */' -e '/^Total users:/ q' input.txt
Which is more verbose, but more portable.

Related

Find two string in same line and then replace using sed

I am doing a find and replace using sed in a bash script. I want to search each file for words with files and no. If both the words are present in the same line then replace red with green else do nothing
sed -i -e '/files|no s/red/green' $file
But I am unable to do so. I am not receiving any error and the file doesn't get updated.
What am I doing wrong here or what is the correct way of achieving my result
/files|no/ means to match lines with either files or no, it doesn't require both words on the same line.
To match the words in either order, use /files.*no|no.*files/.
sed -i -r -e '/files.*no|no.*files/s/red/green/' "$file"
Notice that you need another / at the end of the pattern, before s, and the s operation requires / at the end of the replacement.
And you need the -r option to make sed use extended regexp; otherwise you have to use \| instead of just |.
This might work for you (GNU sed):
sed '/files/{/no/s/red/green/}' file
or:
sed '/files/!b;/no/s/red/green/' file
This method allows for easy extension e.g. foo, bar and baz:
sed '/foo/!b;/bar/!b;/baz/!b;s/red/green/' file
or fee, fie, foe and fix:
sed '/fee/!b;/fi/!b;/foe/!b;/fix/!b;s/bacon/cereal/' file
An awk verison
awk '/files/ && /no/ {sub(/red/,"green")} 1' file
/files/ && /no/ files and no have to be on the same line, in any order
sub(/red/,"green") replace red with green. Use gsub(/red/,"green") if there are multiple red
1 always true, do the default action, print the line.

How to change the character after specific exact character using SED command?

I was writing a bash script to change all the B'10' values to B'12' in files.
So I have a file where B'10' is mentioned many times. And it can also be B'1010101010" with different length. All this has to be B'12121212". I tried to change with SED command which is :
sed -i -r "/[B'][10]+/s/10/12/g" filename
sed -i -r "/[B'][[0-9][0-9]]*[10]+/s/10/12/g" filename
I had to specify it twice, for only match B'10' and many B'1010101010..". If I only specified the second command, it was ignoring the single B'10' matches. So, This command is changing the values but it is changing for all the "10" matches it can find. But I need to change only after exactly B and single column near B character.
All the help is appreciated!! Thank you.
if your sed supports labels:
sed ':1 s/\(B\x27\(12\)*\)10/\112/; t1' file # or
sed -E ':1 s/(B\x27(12)*)10/\112/; t1' file
:1 label 1,
(B\x27(12)*) matches B' followed by zero or more 12s, puts it into capturing group 1,
\1 expands to value kept in capturing group 1,
t1 means "if a successful substitution is performed, go back to label 1".
try it by gnu sed before using -i option;
sed -E ":s s/\b(B')((12)*)10(10|\"|')/\1\212\4/ ;ts" filename
When you want to change all 10's after B', start with the last 10 in 1010...10.
After changing the last, do it again and replace the new last 10.
echo "B'1010101010111213" | sed -r ":a; s/B'((10)*)(10)/B'\112/; ta"

Replacing/removing square brackets in a string

I have the following text in a file:
Names of students
[Name:Anna]
[Name:Bob]
[Name:Carla]
[Name:Daniel]
[ThisShouldNotBeBeRemoved]
End of all names
Blablabla
I want to remove all lines of the text file where there is an occurrence of the string in the format of [Name:xxx], xxx being a name as a string of any length and consisting of any characters.
I have tried the following, but it wasn't successful:
$ sed '/\[Name:*\]/d' file > new-file
Is there any other way I could approach this?
I would use grep with -v
-v, --invert-match
Invert the sense of matching, to select non-matching lines. (-v is specified by POSIX.)
grep -v "\[Name:"
You need to use .* not just * ...
sed '/\[Name:.*\]/d' file > new-file
* on it's own is meaningless in this particular circumstance. Adding . before it signifies "match any character zero or more times" — which I think is what you're wanting to do.
If you wanted to do an in-place edit to the original file without re-directing to a new one:
Linux:
sed -i '/\[Name:.*\]/d' file
macOS:
sed -i '' '/\[Name:.*\]/d' file
* note - this overwrites the original file.
You missed out something,
sed '/\[Name:.*\]/d' file > new-file
This would remove your lines that match.
.* This matches any character zero or more than once.
sed '/\[Name:[[:alpha:]]+\]/d' file
Names of students
[ThisShouldNotBeBeRemoved]
End of all names
Blablabla
OR if you don't want to create new file then try this,
sed -i '/[Name:.*]/d' file

Use sed to edit crontab

I am writing a sed command that should uncomment an entry in crontab. Is there a better way to do this? The first option that comes to mind is sed.
Example:
crontab -l
# 5 * * * 3 bash test.sh
The sed command should uncomment this entry. This is what I have now.
sed "s#\# 5 * * * 3 bash test.sh#5 * * * 3 bash test.sh#"
Obviously this sed command doesn't do the task.
ps:the sed command will eventually make its way into a script.
Sed is great for matching specific regular expressions and manipulating text in certain ways, but this doesn't seem to me to be one of them. While you can use sed for this task, the result is perhaps overly complex and fragile.
Your initial attempt was:
sed "s#\# 5 * * * 3 bash test.sh#5 * * * 3 bash test.sh#"
This fails because the * character is a special character within your regular expression, and is translated as "zero or more of the previous atom" (in this case, a space). Strictly speaking, you may get this sed script to work by escaping the asterisks in your regex (not required in your replacement pattern).
But this only helps for this specific pattern. What if one of your co-workers decides to run this script at 6 minutes after the hour instead of 5, in order to avoid conflict with another script? Or there's a space after the comment character? Suddenly your sed substitution fails.
To uncomment out every commented occurrence of the script in question, you might use:
crontab -l | sed '/# *\([^ ][^ ]* *\)\{5\}[^ ]*test\.sh/s/^# *//' | crontab -
If you're using a more modern sed, you could replace this BRE with a slightly shorter ERE:
crontab -l | sed -E '/# *([^ ]+ *){5}[^ ]*test\.sh/s/^# *//' | crontab -
This takes the output of crontab -l, which is obviously your complete crontab, manipulates it with sed, and then writes a new crontab based on its output using crontab -. The sed script matches searches for lines matching what looks like a valid crontab (to avoid actual comments that simply mention your script), then does a simple substitution to remove only the comment character at the start. The matched pattern breaks out like this:
# * - Matches the comment character followed by zero or more spaces
([^ ]+ +){5} - five non-space strings, followed by spaces
[^ ]* - Any number of non-space characters, which lead up to:
test\.sh - your script.
Note, however, that this doesn't match all valid crontab times, which might include tags like #reboot, #weekly, #midnight, etc. Check out man 5 crontab for details.
A non-sed alternative like awk might be in order. The following awk solution makes more sense to me:
crontab -l | awk -v script="test.sh" '
{ field=6 }
/^# / { field++ }
index($field,script) { sub(/^#/,"") }
1' \
| crontab -
While it's just a little longer, I find it easier to read and understand.
Whether using sed or other tool whould not make much a difference.
I'd use sed also.
But to properly modify what cron is using, please mind to use crontab command.
Do NOT try just editing the data file (e.g. /etc/crontab on some systems). Cron won't pick up the changes!!!.
You might use a pipe:
crontab -l | sed -e "s#\# 5 \* \* \* 3 bash test.sh#5 * * * 3 bash test.sh#"| crontab
to perform the change.
Nevertheless, would it not just be simpler to add the functionality of enabling/disabling into the script being run?

How to apply two different sed commands on a line?

Q1:
I would like to edit a file containing a set of email ids such that all the domain names become generic.
Example,
peter#yahoo.com
peter#hotmail.co.in
philip#gmail.com
to
peter_yahoo#generic.com
peter_hotmail#generic.com
philip_gmail#generic.com
I used the following sed cmd to replace # with _
sed 's/#/_/' <filename>
Is there a way to append another sed cmd to the cmd mentioned above such that I can replace the last part of the domain names with #generic.com?
Q2:
so how do I approach this if I had text at the end of my domain names?
Example,
peter#yahoo.com,i am peter
peter#hotmail.co.in,i am also peter
To,
peter_yahoo.com#generic.com,i am peter
peter_hotmail.co.in#generic.com,i am also peter
I tried #(,) instead of #(.*)
it doesn't work and I cant think of any other solution
Q3:
Suppose if my example is like this,
peter#yahoo.com
peter#hotmail.co.in,i am peter
I want my result to be as follows,
peter_yahoo.com#generic.com
peter_hotmail.co.in#generic.com,i am peter,i am peter
How do i do this with a single sed cmd?
The following cmd would result in,
sed -r 's!#(.*)!_\1#generic.com!' FILE
peter_yahoo.com#generic.com
peter_hotmail.co.in,i am peter,i am peter#generic.com
And the following cmd wont work on "peter#yahoo.com",
sed -r 's!#(.*)(,.*)!_\1#generic.com!' FILE
Thanks!!
Golfing =)
$ cat FILE
Example,
peter#yahoo.com
peter#hotmail.co.in
philip#gmail.com
$ sed -r 's!#(.*)!_\1#generic.com!' FILE
Example,
peter_yahoo.com#generic.com
peter_hotmail.co.in#generic.com
philip_gmail.com#generic.com
In reply to user1428900, this is some explanations :
sed -r # sed in extended regex mode
s # substitution
! # my delimiter, pick up anything you want instead !part of regex
#(.*) # a literal "#" + capture of the rest of the line
! # middle delimiter
_\1#generic.com # an "_" + the captured group N°1 + "#generic.com"
! # end delimiter
FILE # file-name
Extended mode isn't really needed there, consider the same following snippet in BRE (basic regex) mode :
sed 's!#\(.*\)!_\1#generic.com!' FILE
Edit to fit your new needs :
$ cat FILE
Example,
peter#yahoo.com,I am peter
peter#hotmail.co.in
philip#gmail.com
$ sed -r 's!#(.*),.*!_\1#generic.com!' FILE
Example,
peter_yahoo.com#generic.com
peter#hotmail.co.in
philip#gmail.com
If you want only email lines, you can do something like that :
sed -r '/#/s!#(.*),.*!_\1#generic.com!' FILE
the /#/ part means to only works on the lines containing the character #
Edit2:
if you want to keep the end lines like your new comments said :
sed -r 's!#(.*)(,.*)!_\1#generic.com\2!' FILE
You can run multiple commands with:
sed -e cmd -e cmd
or
sed -e cmd;cmd
So, in your case you could do:
sed -e 's/#/_/' -e 's/_.*/_generic.com/' filename
but it seems easier to just do
sed 's/#.*/_generic.com/' filename
sed 's/\(.*\)#\(.*\)\..*/\1_\2#generic.com/'
Expression with escaped parentheses \(.*\) is used to remember portions of the regular expression. The "\1" is the first remembered pattern, and the "\2" is the second remembered pattern.
The expression \(.*\) before the # is used to remember beginning of the email id (peter, peter, philip).
The expression \(.*\)\. after the # is used to remember ending of the email id (yahoo, hotmail, gmail). In other words, it says: take something between # and .
The expression .* at the end is used to match all trailing symbols in the e-mail id (.com, .co.in, .co.in).

Resources