Adding a line to a file using sed in a shell script - bash

I have a file which has 109 lines.
I perform the two operations on the line shown below.
# Delete line 74
sed -i '74d' Test.txt
# Add the entry to line 109
sed -i "109iThis is the string" Test.txt
I see line 74 getting deleted from my Test.txt, but for some reasons, now my Test.txt has only 108 lines, and I don’t see the This is the string being added to line 109.
I am not sure what the error is. How can I fix it?

You may use this POSIX sed command:
sed -i.bak '74d; $ a\
This is the string
' file
This will delete 74th line from file and append a line in the end and will save changes inline.
Note that this will work with gnu-sed as well.

Jonathan already mentioned the potential issues with using sed -i (non-standard, behaves in different ways when supported depending on implementation, etc.). Avoid them by using ed to edit files:
ed -s Test.txt <<EOF
109a
This is the string
.
74d
w
EOF
Note how this appends, and then deletes. Because ed acts on entire files, not a stream of lines, commands to act on specific lines can be in any order.

If you remove a line, the file has only 108 lines left. Correct your second command accordingly:
sed -i "108iThis is the string" Test.txt

Line number 109 does not exist (you removed one, 109-1=108), you must add it before you can enter text into it.
Solution:
sed -i '$ a <text>' Test.txt
The new line will be added with the selected text.

Related

Add file content in another file after first match only

Using bash, I have this line of code that adds the content of a temp file into another file, after a specific match:
sed -i "/text_to_match/r ${tmpFile}" ${fileName}
I would like it to add the temp file content only after the FIRST match.
I tried using addresses:
sed -i "0,/text_to_match//text_to_match/r ${tmpFile}" ${fileName}
But it doesn't work, saying that "/" is an unknown command.
I can make addresses work if I use a standard replacement "s/to_replace/with_this/", but I can't make it work with this sed command.
It seems like I can't use addresses if my sed command starts with / instead of a letter.
I'm not stuck with addresses, as long as I can insert the temp file content into another file only once.
You're getting that error because if you have an address range (ADDR1,ADDR2) you can't put another address after it: sed expects a command there and / is not a command.
You'll want to use some braces here:
$ seq 20 > file
$ echo "new content" > tmpFile
$ sed '0,/5/{/5/ r tmpFile
}' file
outputs the new text only after the first line with '5'
1
2
3
4
5
new content
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
I found I needed to put a newline after the filename. I was getting this error otherwise
sed: -e expression #1, char 0: unmatched `{'
It appears that sed takes the whole rest of the line as the filename.
Probably more tidy to write
sed '0,/5/ {
/5/ r tmpFile
}' file
Full transparency: I don't use sed except for very simple tasks. In reality I would use awk for this job
awk '
{print}
!seen && $0 ~ patt {
while (getline line < f) print line
close(f)
seen = 1
}
' patt="5" f=tmpFile file
Glenn Jackman provided with an excellent answer to why the OP's attempt did not work.
In continuation to Glenn Jackman's answer, if you want to have the command on a single line, you should use branching so that the r command is at the end.
Editing commands other than {...}, a, b, c, i, r, t, w, :, and # can be followed by a <semicolon>, optional <blank> characters, and another editing command. However, when an s editing command is used with the w flag, following it with another command in this manner produces undefined results. [source: POSIX sed Standard]
The r,R,w,W commands parse the filename until end of the line. If whitespace, comments or semicolons are found, they will be included in the filename, leading to unexpected results.[source: GNU sed manual]
which gives:
sed -e '1,/pattern/{/pattern/ba};b;:a;r rfile' file
GNU sed also allows s///e to shell out. So there's this one-liner using Glenn's tmpFile and file.
sed '0,/5/{//{p;s/.*/cat tmpFile/e}}' file
// to repeat the previous pattern match (helps if it's longer than /5/)
p to print the matching line
s/.*/cat tmpFile/e to empty the pattern buffer and stick a the cat tmpFile shell command in there and e execute it and dump the output in the stream
You have 2 forward slashes together, right next to each other in the second sed example.

Sed and delete lines matching "String1 AND string 2"

I can't find how to make that, I want to remove every line containing ACE and REE. only if both words are present.
sed -i '/ACE/d' $1
I would suggest using awk, which would allow you to specify both patterns separately:
awk '!(/ACE/ && /REE/)' file
All lines are printed, except for those where both patterns match.
The advantage of this approach is that it would work regardless of the order in which the two strings appear.
To achieve an "in-place" edit, you can go for the standard approach:
awk '!(/ACE/ && /REE/)' file > tmp && mv tmp file
i.e. output to a temporary file and then overwrite the source.
Try this method
sed -i '/ACE.*RRE/d' FileName
Or
sed -i '/\(ACE\|CH3\).*\(CH3\|ACE\)/d' FileName
Example:
cat sample
Output:
336 ACE CH3 1.00
123 ACE 321 test
This ACE for testing CH3
Command:
sed '/ACE.*CH3/d' sample
Output:
123 ACE 321 test
If you need to match both words but you don't know the order you need to try both cases like so:
sed -i '/\(ABC.*DEF\)\|\(DEF.*ABC\)/d' FileName
It will match any row with either ABC.*DEF or DEF.*ABC and then remove it.
NOTE: With the pattern similar to the following you will also match the case where ABC occurs 2 times and DEF 0 times, and that is not what you want.
sed -i '/\(ABC\|DEF\).*\(ABC\|DEF\)/d' FileName

Sed/Awk to delete second occurence of string - platform independent

I'm looking for a line in bash that would work on both linux as well as OS X to remove the second line containing the desired string:
Header
1
2
...
Header
10
11
...
Should become
Header
1
2
...
10
11
...
My first attempt was using the deletion option of sed:
sed -i '/^Header.*/d' file.txt
But well, that removes the first occurence as well.
How to delete the matching pattern from given occurrence suggests to use something like this:
sed -i '/^Header.*/{2,$d} file.txt
But on OS X that gives the error
sed: 1: "/^Header.*/{2,$d}": extra characters at the end of d command
Next, i tried substitution, where I know how to use 2,$, and subsequent empty line deletion:
sed -i '2,$s/^Header.*//' file.txt
sed -i '/^\s*$/d' file.txt
This works on Linux, but on OS X, as mentioned here sed command with -i option failing on Mac, but works on Linux , you'd have to use
sed -i '' '2,$s/^Header.*//' file.txt
sed -i '' '/^\s*$/d' file.txt
And this one in return doesn't work on Linux.
My question then, isn't there a simple way to make this work in any Bash? Doesn't have to be sed, but should be as shell independent as possible and i need to modify the file itself.
Since this is file-dependent and not line-dependent, awk can be a better tool.
Just keep a counter on how many times this happened:
awk -v patt="Header" '$0 == patt && ++f==2 {next} 1' file
This skips the line that matches exactly the given pattern and does it for the second time. On the rest of lines, it prints normally.
I would recommend using awk for this:
awk '!/^Header/ || !f++' file
This prints all lines that don't start with "Header". Short-circuit evaluation means that if the left hand side of the || is true, the right hand side isn't evaluated. If the line does start with Header, the second part !f++ is only true once.
$ cat file
baseball
Header and some other stuff
aardvark
Header for the second time and some other stuff
orange
$ awk '!/^Header/ || !f++' file
baseball
Header and some other stuff
aardvark
orange
This might work for you (GNU sed):
sed -i '1b;/^Header/d' file
Ignore the first line and then remove any occurrence of a line beginning with Header.
To remove subsequent occurrences of the first line regardless of the string, use:
sed -ri '1h;1b;G;/^(.*)\n\1$/!P;d' file

Write output of command to specific line

I need to write the output of a command to a specific line in a document. I can not just append it like so COMMAND | cat >> file, I need it to be added between two lines without replacing one or the other. I'm sure you must be able to do this via sed.
The following solution works when the output of COMMAND is only 1 line (inserting to line 4):
COMMAND | sed -i "4i \`cat` FILE"
Use that command:
command | sed -i '3r /dev/stdin' file
That inserts text after the 3rd line and reads from stdin (all output from command).

How to add a # before any line containing a matching pattern in BASH?

I need to add a # before any line containing the pattern "000", e.g., consider this sample file:
This is a 000 line.
This is 000 yet ano000ther line.
This is still yet another line.
If I run the command, it should add # to the front of any files where "000" was found. The result would be this:
#This is a 000 line.
#This is 000 yet ano000ther line.
This is still yet another line.
The best I can do is a while loop, like this, which seems too complicated:
while read -r line
do
if [[ $line == *000* ]]
then
echo "#"$line >> output.txt
else
echo $line >> output.txt
fi
done < file.txt
How can I add a # to the front of any line where a pattern is found?
The following sed command will work for you, which does not require any capture groups:
sed /000/s/^/#/
Explanation:
/000/ matches a line with 000
s perform a substitution on the lines matched above
The substitution will insert a pound character (#) at the beginning of the line (^)
This might work for you (GNU sed):
sed 's/.*000/#&/' file
the question was how to add the poundsign to those lines in a file so to make tim coopers excellent answer more complete i would suggest the following:
sed /000/s/^/#/ > output.txt
or you could consider using sed's ability to in-place edit the file in question and also make a backup copy like:
sed -i.bak /000/s/^/#/ file.txt
the "-i" option will edit and save the file inline/in-place
the "-i.bak" option will also backup the original file to file.txt.bak in case you screw up something.
you can replace ".bak" with any suffix to your liking.
eg: "-i.orignal" will create the original file as: "file.txt.orignal"
or use ed:
echo -e "g/000/ s/^/#/\nw\nq" | ed -s FILENAME
or open file in ed :
ed FILENAME
and write this command
g/000/ s/^/#/
w
q
Try this GNU sed command,
$ sed -r '/000/ s/^(.*)$/#\1/g' file
#This is a 000 line.
#This is 000 yet ano000ther line.
This is still yet another line.
Explanation:
/000/ sed substitution only works on the line which contans 000. Once it finds the corresponding line, it adds # symbol infront of it.
And through awk,
$ awk '/000/{gsub (/^/,"#")}1' file
#This is a 000 line.
#This is 000 yet ano000ther line.
This is still yet another line
gsub function is used to add # infront of the lines which contains 000.

Resources