Bash Sed replace - bash

I'm working in a Zend CRUD generator and I have to replace the word "test" which is in $targetForm file with the form code of each field.
field[0]="foo"
field[1]="bar"
textareafield='$'"acme_en = new Utils_Form_Element_Textarea('acme_en',array('langblock'=>'en', 'isWysiwyg' => true));
"'$'"this->addElement("'$'"acme_en);
"'$'"this->addElement('textarea','acme_fr', array( 'label'=>__('acme'), 'langblock'=>'fr', 'isWysiwyg' => true, 'altLangElem' => "'$'"acme_en));"
for ((i=0; i<${#field[#]}; i++));
do
formfield[$i]=$textareafield
formfield[$i]=${formfield[$i]//acme/${field[$i]}}
echo ${formfield[$i]}
sed -i "s/test/test\n ${formfield[$i]}/" $targetForm
done
The command line says:
$foo_en = new Utils_Form_Element_Textarea('foo_en', array('langblock'=>'en', 'isWysiwyg' => true)); $this->addElement($foo_en); $this->addElement('textarea','foo_fr', array( 'label'=>__('foo'), 'langblock'=>'fr', 'isWysiwyg' => true, 'altLangElem' => $foo_en));
sed: -e expression #1, char 120: unterminated `s' command
$bar_en = new Utils_Form_Element_Textarea('bar_en', array('langblock'=>'en', 'isWysiwyg' => true)); $this->addElement($bar_en); $this->addElement('textarea','bar_fr', array( 'label'=>__('bar'), 'langblock'=>'fr', 'isWysiwyg' => true, 'altLangElem' => $bar_en));
sed: -e expression #1, char 120: unterminated `s' command
Maybe there's a problem with the specials character but I don't know how to solve it.

This error you get very likely has nothing to do with using \n in sed substitution (especially since you mentioned in the comments that you are using GNU sed version 4.2.1).
The real culprit is textareafield which contains a multi-line string.
What happens is that when ${formfield[$i]} gets expanded, your sed command looks like this
sed -i "s/test/test\n line1
line2
line3/"
This is problematic because without terminating lines with a literal \, sed would interpret each line as a complete command in itself, in this case
s/test/test\n line1
which is missing a / at the end, hence the error "unterminated `s' command".
To fix this, what we want is to insert a \ to the end of every line except the last, i.e.
sed -i "s/test/test\n line1\
line2\
line3/"
I got your example to work by adding two lines
for ((i=0; i<${#field[#]}; i++));
do
formfield[$i]=$textareafield
formfield[$i]=${formfield[$i]//acme/${field[$i]}}
formfield[$i]=${formfield[$i]//\;/\;\\}
formfield[$i]=${formfield[$i]%\\}
echo ${formfield[$i]}
sed -i "s/test/test\n ${formfield[$i]}/" $targetForm
done
Since all the lines in textareafield end in ;, I replaced all ; with ;\, then removed the last \ from the last line.

sed does not support \n in the substitution, but you can use a literal newline if you escape it. Also, you need to quote the ' characters. Here is one way of doing it:
for ((i=0; i<${#field[#]}; i++));
do
formfield[$i]=$textareafield
formfield[$i]=${formfield[$i]//acme/${field[$i]}}
sedexpr="${formfield[$i]}"
sedexpr="${sedexpr//\'/\'}"
sedexpr="${sedexpr//
/\\
}"
sed -i "s/test/test\
${sedexpr}/" $targetForm
done
But at this level of complexity you're probably better off switching to something like Python.

Related

Not able to add a line of text after pattern using sed in OSX

I'm trying to add a line in a file afile.xyz using my script. This is what I've done so far using sed:
n="$(grep ".method" "$m" | grep "onCreate(Landroid/os/Bundle;)V")"
sed -i '' -e '/$n/ a\
"test", /Users/username/Documents/afile.xyz
I'm getting the error:
"onCreate\(\Landroid\/ ...": bad flag in substitute command: 'g'
How do I solve this? Please do help. Thanks.
Edit: Content of n
method protected onCreate(Landroid/os/Bundle;)V
2 problems:
because the sed body is in single quotes, the variable $n will not be expanded,
the regular expression in $n contains the / dilimiters.
Try this:
n=$(...)
nn=${n//\//\\/} # escape all slashes
sed -i '' '/'"${nn}"'/ a ...
The single-quoted sed body is interrupted to append the double quoted shell variable.
You can also use a different delimiter for the RE:
sed -i '' -e "\#$n# a\\
\"test\"" /Users/username/Documents/afile.xyz

Sed insert blank line below search string then insert string on the next line

I have a sed expression in a function that I pass parameters to.
insert_after_new_line() {
if ! grep -q "${2}" "${3}"; then
sed -i "/${1}/{N;N;a ${2}}" "${3}"
fi
}
insert_after_new_line search insert textfile.txt
I am trying to have a blank line inserted below the search string and the insert string inserted after.
so
text
text
search
text
becomes
text
text
search
insert
text
but I keep getting the error
sed: -e expression #1, char 0: unmatched `{'
I tested this. works in command line
sed -i '/search/a \\ninsert' file
sed really delimiters commands by a newline. There is a ; but it does not work for all commands, mostly these which take file name as an argument. ; does not work for r R or for example a. Sed will read everything after a command, so sed interprets is like a ${2}} as a single command, in result it does not find enclosing }, cause it's been eaten by a command. You need a newline:
sed -i "/${1}/{N;N;a ${2}
}" "${3}"
or
sed -i "/${1}/{N;N;a ${2}"$'\n'"}" "${3}"
this should work:
sed -i '/search/{G;ainsert
}' file
You can replace the text by shell variable, but replace the single quotes by double quotes too.

Substitute string with file content in sed

I'm lost trying to do following substitution with sed:
# edit: to capture the full complexity of my problem,
I added the fact that filenames are contained in variables afterwards.
Solutions might therefore directly use the filenames.
given a variable I='insert.txt':
'insert.txt':
Text I wanna skip.
This is text to insert containing
spaces and new lines
given a variable M='toModify.txt':
'toModify.txt':
Insert the new text: here.
I would like to replace the 'here' from $M with the content
of $I:
Insert the new text: This is text to insert containing
spaces and new lines.
I tried:
sed -e "s/here/$(tail -n2 $I | sed -e 's/ /\\ /g' | tr '\n' '\\n')/" $M
with error:
sed unterminated `s' command
The problem is that I don't get the spaces and new lines without terminating the s command.
Any solution?
You can't replace one character with two with tr. Escaping the individual spaces is pointless anyway. The reason for the immediate error is that you end up escaping the final slash, too:
linux$ tail -n2 "$I" | sed -e 's/ /\\ /g' | tr '\n' '\\n'
This\ is\ text\ to\ insert\ containing\spaces\ and\ new\ lines\/
Escaping the spaces is pointless anyway. I guess you want something like this:
linux$ sed '1,2d;$!s/$/\\/' "$I"
This is text to insert containing\
spaces and new lines
We delete lines 1 and two; then add a backslash before every newline except the last.
linux$ sed -e "s/here/$(sed '1,2d;$!s/$/\\/' "$I")/" "$M"
Insert the new text: This is text to insert containing
spaces and new lines.
This is one detail of sed which isn't entirely portable. But the above works for me on Linux and MacOS. (Note you might need to set +H to disable csh-style history expansion aka -bash: !s/$/\\/': event not found errors).
You may use this awk:
awk 'BEGIN{prs=RS; RS=""; getline s < "insert.txt"; RS=prs}
{gsub(/here/, s)} 1' toModify.txt
Insert the new text: This is text to insert containing
spaces and new lines.
Using Perl one-liner
> cat insert.txt
This is text to insert containing
spaces and new lines
> cat toModify.txt
Insert the new text: here
> export I=insert.txt
> export M=toModify.txt
> perl -ne 'BEGIN{$x=qx(cat $ENV{M});$x=~s/here/qx(cat $ENV{I})/e; print $x;exit }'
Insert the new text: This is text to insert containing
spaces and new lines
>

"unterminated address regex" using variable in sed

I'm trying to use a variable in a sed append and hitting an issue.
The following command works as expected:
sed -i "\:#file = /mnt/var/log/hadoop-yarn/containers/application_1495965866386_0001/container_1495965866386_0001_01_000002/stderr:a file = /path/to/other/file" /etc/conf/service.conf
However if I replace the pattern with a variable I'm hitting an error:
$ echo $item
#file = /mnt/var/log/hadoop-yarn/containers/application_1495965866386_0001/container_1495965866386_0001_01_000002/stdout
$ sed -i "\:$item:a file = /path/to/other/file" /etc/conf/service.conf
sed: -e expression #1, char 122: unterminated address regex
EDIT for more info: So the 'item' variable is being populated from an array. That array is created from a readarray and grep:
$readarray LINES < <(grep "#file = /mnt/var/" /etc/conf/service.conf)
$item=${LINES[1]}
$echo $item
#file = /mnt/var/log/hadoop-yarn/containers/application_1495965866386_0001/container_1495965866386_0001_01_000002/stdout
However I've found if i populate 'item' manually it then works e.g:
$item="#file = /mnt/var/log/hadoop-yarn/containers/application_1495965866386_0001/container_1495965866386_0001_01_000002/stdout"
$sed -i "\:$item:a file = /path/to/other/file" /etc/conf/service.conf
$
So something strange seems to be happening with the readarray/grep
So the problem here turned out to be newline characters that were being pulled in as part of the grep.
This is why populating $item manually worked - no '\n'
Thanks to Ed Morton for pointing me in the right direction. While
echo "$item" | cat -v
did not show anything I added '-t' to the readarray command to trim newline characters:
$readarray -t LINES < <(grep "#file = /mnt/var/" /etc/conf/service.conf)
After that things worked as expected.

Unable to define line number in sed by variable

I'm trying to use an array to define the lines to replace using sed; I can delete the lines using a variable for the line number but I can't get sed to use the variable to define the line number to write to. The problem seems to reside in the insert line. How do you pass the value of an array as a line number to sed?
#!/bin/bash
lineNum=$(sed -n '/max_allowed_packet/=' /etc/mysql/my.cnf)
IFS= #There's a space as the delimiter#
ary=($lineNum)
#for key in "${!ary[#]}";
# do
# sed -i '$ary[$key]'d /etc/mysql/my.cnf;
# #The folllowing line errors#
# sed -i "'$ary[$key]'imax_allowed_packet = 32M" /etc/mysql/my.cnf;
# #The above line errors#
#done
#for val in "${ary[#]}";
# do
# sed -i "${val}d" /etc/mysql/my.cnf;
# sed -i "${val}imax_allowed_packet = 32M" /etc/mysql/my.cnf;
# done
for val in "${ary[#]}";
do
sed -i "${val}s/.*/imax_allowed_packet = 32M" /etc/mysql/my.cnf";
done
For the first stanza of script I get the following output:
Error: sed: -e expression #1, char 1: unknown command: `''
For the second Stanza I get the following output:
sed: -e expression #1, char 3: unknown command:
'
sed: -e expression #1, char 3: unknown command:
'
For the third Stanza I get the following output:
./test.sh: line 22: unexpected EOF while looking for matching `"'
./test.sh: line 24: syntax error: unexpected end of file
Edit, rewriting the sed commands as sed -i "${ary[$key]}" generates the following error output: sed: -e expression #1, char 3: unknown command: `
I think you're over-complicating the issue. Your script can be reduced to this:
sed 's/\(max_allowed_packet\).*/\1 = 32M/' /etc/mysql.cnf
This performs a substitution on every occurrence of max_allowed_packet, setting the rest of the line to = 32M. Add the -i switch to overwrite the file when you're happy with the result.
Problems with your attempt
Shell parameters are not expanded within single quotes, so you would need to use double quotes, e.g. sed -i "${ary[$key]}d". You can use set -x to see what is happening here - at the moment, you will see the literal string $ary[$key], rather than the array value.
If I understand your intention correctly (you want to substitute the entire line), there's no need to call sed twice:
for val in "${ary[#]}"; do
sed -i.bak "${val}s/.*/imax_allowed_packet = 32M" /etc/mysql/my.cnf
done
I have chosen to loop through the values of the array, instead of the keys, in order to simplify things a little. When using the -i option, it is always a good idea to specify a backup file, as I have done.

Resources