${arr[#]} in sed command - shell

#!/bin/sh
arr=(
a
b
c
)
sed "s/abc/${arr[#]}/" file
sh -x this_script.sh show the result with error:
+ arr=(a b c)
+ sed s/abc/a b c/ file
sed: -e expression #1, char 5: unterminated `s' command
it should be:
+ sed 's/abc/a b c/' file
there's already double quotation in this script, why need declare a variable to make it work:
x=${arr[#]}
sed "s/abc/$x/" file

You can use ${arr[*]} instead of ${arr[#]} to be treated it like a single string:
sed "s/abc/${arr[*]}/" <<< "abc"
a b c

Related

Use a variable as replacement in bash sed command

I am using the sed command on Ubuntu to replace content.
This initial command comes from here.
sed -i '$ s/$/ /replacement/' "$DIR./result/doc.md"
However, as you can see, I have a slash in the replacement. The slash causes the command to throw:
sed: -e expression #1, char 9: unknown option to `s'
Moreover, my replacement is stored in a variable.
So the following will not work because of the slash:
sed -i "$ s/$/ $1/" "$DIR./result/doc.md"
As stated here and in duplicate, I should use another delimiter. If I try with #:
sed -i "$ s#$# $1#" "$DIR./result/doc.md"
It gives the error:
sed: -e expression #1, char 42: unterminated `s' command
My question is:
How can I use a variable in this command as well as other delimiter than / ?
Don't use sed here; perl and awk allow more robust approaches.
sed doesn't allow variables to be passed out-of-band from code, so they always need to be escaped. Use a language without that limitation, and you have code that always works, no matter what characters your data contains.
The Short Answer: Using perl
The below is taken from BashFAQ #21:
inplace_replace() {
local search=$1; shift; local replace=$1; shift
in="$search" out="$replace" perl -pi -e 's/\Q$ENV{"in"}/$ENV{"out"}/g' "$#"
}
inplace_replace '#' "replacement" "$DIR/result/doc.md"
The Longer Answer: Using awk
...or, using awk to do a streaming replacement, and a shell function to make that file replacement instead:
# usage as in: echo "in should instead be out" | gsub_literal "in" "out"
gsub_literal() {
local search=$1 replace=$2
awk -v s="${search//\\/\\\\}" -v r="${rep//\\/\\\\}" 'BEGIN {l=length(s)} {o="";while (i=index($0, s)) {o=o substr($0,1,i-1) r; $0=substr($0,i+l)} print o $0}'
}
# usage as in: inplace_replace "in" "out" /path/to/file1 /path/to/file2 ...
inplace_replace() {
local search=$1 replace=$2 retval=0; shift; shift
for file; do
tempfile=$(mktemp "$file.XXXXXX") || { retval |= $?; continue; }
if gsub_literal "$search" "$replace" <"$file" >"$tempfile"; then
mv -- "$tempfile" "$file" || (( retval |= $? ))
else
rm -f -- "$tempfile" || (( retval |= $? ))
fi
done
}
TL;DR:
Try:
sed -i '$ s#$# '"$1"'#' "$DIR./result/doc.md"
Long version:
Let's start with your original code:
sed -i '$ s/$/ /replacement/' "$DIR./result/doc.md"
And let's compare it to the code you referenced:
sed -i '$ s/$/abc/' file.txt
We can see that they don't exactly match up. I see that you've correctly made this substitution:
file.txt --> "$DIR./result/doc.md"
That looks fine (although I do have my doubts about the . after $DIR ). However, the other substitution doesn't look great:
abc --> /replacement
You actually introduced another delimeter /. However, if we replace the delimiters with '#' we get this:
sed -i '$ s#$# /replacement#' "$DIR./result/doc.md"
I think that the above is perfectly valid in sed/bash. The $# will not be replaced by the shell because it is single quoted. The $DIR variable will be interpolated by the shell because it is double quoted.
Looking at one of your attempts:
sed -i "$ s#$# $1#" "$DIR./result/doc.md"
You will have problems due to the shell interpolation of $# in the double quotes. Let's correct that by replacing with single quotes (but leaving $1 unquoted):
sed -i '$ s#$# '"$1"'#' "$DIR./result/doc.md"
Notice the '"$1"'. I had to surround $1 with '' to basically unescape the surrounding single quotes. But then I surrounded the $1 with double quotes so we could protect the string from white spaces.
Use shell parameter expansion to add escapes to the slashes in the variable:
$ cat file
foo
bar
baz
$ set -- ' /repl'
$ sed "s/$/$1/" file
sed: 1: "s/$/ /repl/": bad flag in substitute command: 'r'
$ sed "s/$/${1//\//\\\/}/" file
foo /repl
bar /repl
baz /repl
That is a monstrosity of leaning toothpicks, but it serves to transform this:
sed "s/$/ /repl/"
into
sed "s/$/ \/repl/"
The same technique can be used for whatever you choose as the sed s/// delimiter.

Replace a string in multiple files using sed

I would like to find and replace a string in multiple files using bash command. I am using sed which I am not really familiar with.
My variables:
$FILE = (/home/user/file1.txt, /home/user/file2.txt)
$REL = 5.0
My code:
for f in ${FILES[#]}; do sed -i "$f" "s/__ver__ =*/__ver__=$REL/g";
output:
sed: -e expression #1, char 2: unknown command: `/'
sed: -e expression #1, char 2: unknown command: `/'
What is wrong with my expression?
1) The filename should be specified as the last argument for sed expression:
2) bash's for loop should ended with done keyword
for f in ${FILES[#]}; do sed -i "s/__ver__ =*/__ver__=$REL/" "$f"; done
If your files have similar naming format you can avoid for loop:
sed -i 's/__ver__ =*/__ver__=$REL/' /home/user/file[2].txt

unix scripting error with sed command

I used the below command to replace the "mppu" word with "hihi" and it was working right.
sed 's/mppu/'`echo "hihi"`'/' memo.cir
but when I was trying the below command
sed 's/mppu/'`echo "hi hi"`'/' memo.cir
then it gives error as
sed: -e expression #1, char 9: unterminated `s' command.
I really don't why it is giving such error as i just added a space in hihi
the correct syntax will be as follows.
Solution 1st: For simple replacement of a string.
sed 's/old_text/new_text/' Input_file
You need not to mention echo and all there.
Solution 2nd: In case you want to search for a string and then do a substitution of any string then following may help you in same.
sed '/look_for_string/s/old_text/new_test/' Input_file
Solution 3rd: If you want to change multiple/all occurrences of any string into a single line then following may help you too by adding g at last of command.
sed '/look_for_string/s/old_text/new_text/g' Input_file
Solution 4th: In case you want to do the changes into Input_file itself then following may help you in same.
sed -i '/look_for_string/s/old_text/new_text/g' Input_file
EDIT1: As per OP, code should replace a variable's value to line then following may help you in same then.
VAL="SINGH"
sed 's/tools/'"$VAL"'/' Input_file
EDIT2: Adding an example for OP for substituting the nth number of a string if it matched a string.
Let's say following is my Input_file.
cat Input_file
31116441
AAA,hi,hi,hi,hji
AAA
AA
BBB
BB
Now to substitute the 3rd occurrence of string(hi) for a line which stars from AAA we could do following then.
sed '/AAA/s/hi/cha_cha_ch_ch_cha/3' Input_file
31116441
AAA,hi,hi,cha_cha_ch_ch_cha,hji
AAA
AA
BBB
BB
You need to quote the output from external command. set -x helps to debug what is going wrong.
$ set -x
# can also use: echo 'asd mppu foo' | sed 's/mppu/'"`echo "hi hi"`"'/'
$ echo 'asd mppu foo' | sed 's/mppu/'"$(echo "hi hi")"'/'
+ echo 'asd mppu foo'
++ echo 'hi hi'
+ sed 's/mppu/hi hi/'
asd hi hi foo
You can see that hi hi is within outer single quotes. Without double quotes, you'd get
$ echo 'asd mppu foo' | sed 's/mppu/'`echo "hi hi"`'/'
+ echo 'asd mppu foo'
++ echo 'hi hi'
+ sed s/mppu/hi hi/
sed: -e expression #1, char 9: unterminated `s' command
Similarly, newlines would cause issue
$ echo 'asd mppu foo' | sed 's/mppu/'"$(printf "hi hi\nabc")"'/'
+ echo 'asd mppu foo'
++ printf 'hi hi\nabc'
+ sed 's/mppu/hi hi
abc/'
sed: -e expression #1, char 12: unterminated `s' command
With variables instead of external command:
$ r='hi hi'
$ echo 'asd mppu foo' | sed 's/mppu/'"$r"'/'
+ echo 'asd mppu foo'
+ sed 's/mppu/hi hi/'
asd hi hi foo
$ echo 'asd mppu foo' | sed 's/mppu/'$r'/'
+ sed s/mppu/hi hi/
sed: -e expression #1, char 9: unterminated `s' command
+ echo 'asd mppu foo'
See Why does my shell script choke on whitespace or other special characters? for more details on this topic
you can use
sed 's/old/new/g' file
if you want to replace variable,you can use the follow tow methods
sed -i 's/XXXXX/${WEEK_DAY}/g' ==> sed -i "s/XXXXX/${WEEK_DAY}/g"
sed -i 's/XXXXX/${WEEK_DAY}/g' ==> sed -i 's/XXXXX/'${WEEK_DAY}'/g'

Bash expand variable containing sed pattern correctly

I have a script like this:
#!/bin/bash
xx="-e \"s|a|b|g\""
sed -i $xx file
But sed breaks with message:
sed: -e expression #1, char 1: unknown command: `"'
Using set -x I can see that the command is being expanded to sed -i -e '"s|a|b|g"' file, so I guess the double quotes are why it is not working.
How to fix this?
I'm not sure exactly why you want to do what you're doing but I think that this might help:
$ cat file
a a
$ xx=( -e 's|a|b|g' -e 's|b|c|g' )
$ sed "${xx[#]}" file
c c
Use an array to store each argument to sed. Use "${xx[#]}" to safely expand the array, passing each element as a single argument.
You can build up the array like this:
$ xx=()
$ xx+=( -e 's|a| b c |g' )
$ xx+=( -e 's|c| d |g' )
$ sed "${xx[#]}" file
b d b d
You could try expanding the strings using eval, but it is not often recommended by bash aficionados.
#!/bin/bash
xx="-e 's|b|a|g'"
eval sed -i "$xx" file
You can see it getting expanded when using eval, below is the snippet from set +x
++ xx='-e '\''s|a|b|g'\'''
++ eval sed -i '-e '\''s|a|b|g'\''' file
+++ sed -i -e 's|a|b|g' file
To see it in action:-
$ cat file
line1/script
alaalaala++
line1/script
line2/script
line3/script
alaalaala--
line1/script
$ ./script.sh ; cat file
line1/script
blbblbblb++
line1/script
line2/script
line3/script
blbblbblb--
line1/script

bash : sed : regex in variable

I'm getting totally crazy with the following script.
The following command works as expected :
echo a | sed 's/a/b/'
Output :
b
But this script doesn't :
test="'s/a/b/'"
echo a | sed $test
Output :
sed: -e expression #1, char 1: unknown command : `''
I should really be stupid, but I don't see what I am missing.
Thanks,
test="'s/a/b/'"
echo a | sed $test
is equivalent to:
test="'s/a/b/'"
echo a | sed "'s/a/b/'"
Obviously sed doesn't understand the command with both " and ', It interprets ' as a command. You can use either one of them:
test='s/a/b/'
Or
test='s/a/b/'
This is because your double wrapping your string. test="'s/a/b'". Sed then gets 's/a/b/' as literal string. You only want sed to receive s/a/b/.
You only need to wrap the string in one set of quotes, otherwise the inner set of quotes will be interpreted as part of the argument.
you may want this:
kent$ test="s/a/b/"
kent$ echo a | sed ${test}
b
or
kent$ echo a | sed $test
b
or
test=s/a/b/

Resources