Add multiple lines in file using bash script - macos

Using a bash script, I am trying to insert a line in a file (eventually there will be 4 extra lines, one after the other).
I am trying to implement the answer by iiSeymour to the thread:
Insert lines in a file starting from a specific line
which I think is the same comment that dgibbs made in his own thread:
Bash: Inserting a line in a file at a specific location
The line after which I want to insert the new text is very long, so I save it in a variable first:
field1=$(head -2 file847script0.xml | tail -1)
The text I want to insert is:
insert='newtext123'
When running:
sed -i".bak" "s/$field1/$field1\n$insert/" file847script0.xml
I get the error:
sed: 1: "s/<ImageAnnotation xmln ...": bad flag in substitute command: 'c'
I also tried following the thread
sed throws 'bad flag in substitute command'
but the command
sed -i".bak" "s/\/$field1/$field1\n$insert/" file847script0.xml
still gives me the same error:
sed: 1: "s/\/<ImageAnnotation xm ...": bad flag in substitute command: 'c'
I am using a Mac OS X 10.5.
Any idea of what am I doing wrong? Thank you!

Good grief, just use awk. No need to worry about special characters in your replacement text or random single-character commands and punctuation.
In this case it looks like all you need is to print some new text after the 2nd line so that's just:
$ cat file
a
b
c
$ insert='absolutely any text you want, including newlines
slashes (/), backslashes (\\), whatever...'
$ awk -v insert="$insert" '{print} NR==2{print insert}' file
a
b
absolutely any text you want, including newlines
slashes (/), backslashes (\), whatever...
c

Isn't it easier to do it by line number? If you know it's the second line or the nth line (and grep will tell you line numbers if you are pattern matching) then you can simply use sed to find the correct line and then append a new line (or 4 new lines).
cat <<EOF > testfile
one two three
four five six
seven eight nine
EOF
sed -re '2a\hello there' testfile
will output
one two three
four five six
hello there
seven eight nine

Related

sed swallows whitespaces while inserting content

I am trying to insert a new line before the first match with sed. The content that I wanna insert starts with spaces but sed or bash swallows the whitespaces.
hello.txt
line 1
line 2
line 3
The content to insert in my case coming from a variable this way:
content=" hello"
The command that I have created:
sed -i "/line 2/i $content" hello.txt
Result:
line 1
hello
line 2
line 3
Expected result:
line 1
hello
line 2
line 3
It seems that bash swallows the whitespaces. I have tried to use quotes around the variable this way: sed -i "/line 2/i \"$content\"" but unfortunately it does not work. After playing with this for 2 hours I just decided that I ask help.
That's how GNU sed works, yes - any whitespace between the command i and the start of text is eaten. You can add a backslash (At least, in GNU sed; haven't tested others) to keep the spaces:
$ sed "/line 2/i \\$content" hello.txt
line 1
hello
line 2
line 3
POSIX sed i always requires a backslash after the i and then a newline with the text to insert on the next line (And to insert multiple lines, a backslash at the end of all but the last), instead of the one-line version that's a GNU extension.
sed "/line 2/i\\
$content
" hello.txt
I don't have a non-GNU version handy to test, so I don't know if the leading whitespace in the variable will be eaten, but I suspect not.
You need to use a backslash instead:
content="\ hello"
Otherwise, it's viewed as part of the separated between the /i and the hello.
Note also that the $content variable could end up including all sorts of characters that sed would interpret differently... so be careful.
ed would work well here:
ed hello.txt <<END_ED
/line 2/i
$content
.
wq
END_ED

SED's Substituted string is considered as one-line string, whereas it contains newline character

I am testing the sed command to substitute one line with 3 lines and, then, to delete the last line. (I could have substituted it with only the 2 first lines, but this is deliberately stated like this to showcase the main issue).
Let's say that I have the following text :
// ##OPTION_NAME: xxxx
I want to replace the token ##OPTION_NAME by ##OP-NAME and surround it by 2 new lines; Like so :
// ##OP-START
// ##OP-NAME: xxxx
// ##OP-END
To illustrate this, I put this text in a code.c file, and the sed commands in a sed script named script.sed.
Then, I call the following shell command :
Shell command
sed -f script.sed code.c
script.sed
# Begin by replacing patterns by their equivalents, surrounding them with ##OP-START and ##OP-END lines
s/\(.*\)##OPTION_NAME:\(.*\)/\1##OP-START\n\1##OP-NAME:\2\n\1##OP-END/g
The problem
Now, I add another sed command in script.sed to delete the line containing ##OP-END. Surprise ! all 3 lines are removed !
# Begin by replacing patterns by their equivalents, surrounding them with ##OP-START and ##OP-END lines
s/\(.*\)##OPTION_NAME:\(.*\)/\1##OP-START\n\1##OP-NAME:\2\n\1##OP-END/g
# Last parse; delete ##OP-END
/##OP-END/d
I tried \r\n instead of \n in the sustitution command
s/\(.*\)##OPTION_NAME:\(.*\)/\1##OP-START\n\1##OP-NAME:\2\n\1##OP-END/g, but it does not work.
I also tested on ##OP-START to see if it makes some difference,
but alas ! All 3 lines were removed too.
It seems that sed is considering it as one line !
This is not a surprise, d operates on the pattern space, not on a per line basis. After the modification with the s command, your pattern space contains 3 lines. The content of it matches the expression and gets therefore deleted.
To delete this line from the pattern space, you need to use the s command again:
s/\(.*\)##OPTION_NAME:\(.*\)/\1##OP-START\n\1##OP-NAME:\2\n\1##OP-END/g$
s/\n\/\/ ##OP-END//
About pattern and hold space: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html#tag_20_116_13

Find and replace text containing a new line

In bash, how can I find and replace some text containing a new line?
I want to match exactly 2 lines as specified (I can't match them separately as both lines appear at different places separately & I only want to replace where both lines appear consecutively). Using sed I was able to find and replace the individual lines and new line separately, but not together!
In case if needed, below are the lines I want to find and replace (from multiple files at once!):
} elseif ($this->aauth->is_member('Default')) {
$form_data['userstat'] = $this->aauth->get_user()->id;
In general you can used sed -z which tells sed to use the null-character to split lines. Assume you have the file text containing
Hello World
This is a line
line1
line2
Hello World, again
line1
line2
end
Executing sed -z -e 's/line1\nline2/xxx/g' text yields
Hello World
This is a line
xxx
Hello World, again
xxx
end
You can add * (that is <space><star>) to handle inconsistent white spaces.
In your specific case if you want to delete the second line you can use a block statement to advance to the next line and delete it if it matches the second line
sed -e '/line1/{n;/line2/d}' text
This might work for you (GNU sed):
sed -i 'N;s/first line\nsecond line/replacement/;P;D' file ...
Keep a moving window of two lines in the pattern space and replace when necessary.
N.B. -i option updates file(s) in place.
Also using a range and the change command:
sed -i '/first line/,/second line/c\replacement1\nreplacement2\netc' file ...

Sed substitution places characters after back reference at beginning of line

I have a text file that I am trying to convert to a Latex file for printing. One of the first steps is to go through and change lines that look like:
Book 01 Introduction
To look like:
\chapter{Introduction}
To this end, I have devised a very simple sed script:
sed -n -e 's/Book [[:digit:]]\{2\}\s*(.*)/\\chapter{\1}/p'
This does the job, except, the closing curly bracket is placed where the initial backslash should be in the substituted output. Like so:
}chapter{Introduction
Any ideas as to why this is the case?
Your call to sed is fine; the problem is that your file uses DOS line endings (CRLF), but sed does not recognize the CR as part of the line ending, but as just another character on the line. The string Introduction\r is captured, and the result \chapter{Introduction\r} is printed by printing everything up to the carriage return (the ^ represents the cursor position)
\chapter{Introduction
^
then moving the cursor to the beginning of the line
\chapter{Introduction
^
then printing the rest of the result (}) over what has already been printed
}chapter{Introduction
^
The solution is to either fix the file to use standard POSIX line endings (linefeed only), or to modify your regular expression to not capture the carriage return at the end of the line.
sed -n -e 's/Book [[:digit:]]\{2\}\s*(.*)\r?$/\\chapter{\1}/p'
As an alternative to sed, awk using gsub might work well in this situation:
awk '{gsub(/Book [0-9]+/,"\\chapter"); print $1"{"$2"}"}'
Result:
\chapter{Introduction}
A solution is to modify the capture group. In this case, since all book chapter names consist only of alphabetic characters I was able to use [[:alpha:]]*. This gave a revised sed script of:
sed -n -e 's/Book [[:digit:]]\{2\}\s*\([[:alpha:]]*\)/\\chapter{\1}/p'.

What is the meaning of "0,/xxx" in sed?

A sed command used in a script as following:
sed -i "0,/^ENABLE_DEBUG.*/s/^ENABLE_DEBUG.*/ENABLE_DEBUG = YES/" MakeConfig
I knows that
s/^ENABLE_DEBUG.*/ENABLE_DEBUG = YES/
is to substitutes line prefix
ENABLE_DEBUG as ENABLE_DEBUG = YES
But no idea about the meaning of
0,/^ENABLE_DEBUG.*/
Anyone can help me?
0,/^ENABLE_DEBUG.*/ means that the substitution will only occur on lines from the beginning, 0, to the first line that matches /^ENABLE_DEBUG.*/. No substitution will be made on subsequent lines even if they match /^ENABLE_DEBUG.*/
Other examples of ranges
This will substitute only on lines 2 through 5:
sed '2,5 s/old/new/'
This will substitute from line 2 to the first line after it which includes something:
sed '2,/something/ s/old/new/'
This will substitute from the first line that contains something to the end of the file:
sed '/something/,$ s/old/new/'
POSIX vs. GNU ranges: the meaning of line "0"
Consider this test file:
$ cat test.txt
one
two
one
three
Now, let's apply sed over the range 1,/one/:
$ sed '1,/one/ s/one/Hello/' test.txt
Hello
two
Hello
three
The range starts with line 1 and ends with the first line after line 1 that matches one. Thus two substitutions are made above.
Suppose that we only wanted the first one replaced. With POSIX sed, this cannot be done with ranges. As NeronLeVelu points out, GNU sed offers an extension for this case: it allows us to specify the range as 0,/one/. This range ends with the first occurrence of one in the file:
$ sed '0,/one/ s/one/Hello/' test.txt
Hello
two
one
three
Thus, the range 0,/^ENABLE_DEBUG/ ends with the first line that begins with ENABLE_DEBUG even if that line is the first line. This requires GNU sed.

Resources