Variable `FROM` doesnt get expanded in `sed -i 's/$FROM/$TO/g'` - bash

Someone with better experience then me regarding bashscript that could take a look at this code and explain why no string replacements is taking place. If I run the same command (text input instead of variables) in the terminal, then it works.
#!/bin/bash
echo "important to escape every \"/\" character"
read -p "Specify the old string you want to replace? (from) " FROM
read -p "Specify the new string you want to use instead? (to) " TO
cp ../backup/mysql/dump.sql ../backup/mysql/dump.sql.backup.$(date +"%Y-%m-%d-%H-%M-%S") \
&& sed -i 's/$FROM/$TO/g' ../backup/mysql/dump.sql

Use double quotes for variables expansion instead of single quotes: "s/$FROM/$TO/g"

You can even have the cp implicitly within the sed command itself using -i(inplace) option.
sed -i.backup.$(date +"%Y-%m-%d-%H-%M-%S") "s/$FROM/$TO/g" ../backup/mysql/dump.sql
This will create the backup file as well.

Related

Prevent expansion of \c in echo argument

I'm building a shell script with echo. I have something like:
echo "sed -i \"\\|charlie\.url\\s*=\\s*.*|c\\charlie.url = ${CHARLIE_URL}\" foo.conf" >> bar.sh
i.e. replace the line in foo.conf containing the current charlie.url (not necesarily at the begining, 'cause the line could be commented) for a new line with a new url.
I would expect the output to bar.sh to be
sed -i "\|charlie\.url\s*=\s*.*|c\charlie.url = ${CHARLIE_URL}" foo.conf
Nevertheless, the c\\charlie is interpreted as c \c harlie, instead of
c\ charlie, which generates the following output:
sed -i "\|charlie\.url\s*=\s*.*|c
I have found that I could prevent this by using single instead of doubles quotes, but in that case ${CHARLIE_URL} (which I do need to expand) does not get expanded.
How should my echo argument look like?
I'm using dash (#!/bin/sh under Ubuntu), but I could also use bash or zsh.
Instead of echo, you can try cat :
cat << EOF >> bar.sh
sed -i "\|charlie\.url\s*=\s*.*|c\charlie.url = ${CHARLIE_URL}" foo.conf
EOF
#!/bin/bash
# bash will expand content within "" and stuff like $URL gets expanded
# also double-backslash reduces to a single; bash wouldn't expand stuff within ''
URL="https..."
echo double: "c\\charlie.url, URL $URL"
echo single: 'c\\charlie.url, URL $URL'
# if you need to output a \\
echo "\\\\"
The command line is using your first slash to quote your second one. If CHARLIE_URL=foo, your echo is actually outputting
sed -i "\|charlie\.url\s*=\s*.*|c\charlie.url = foo" foo.conf
Try using single quotes, but close/open them around the variable.
echo 'sed -i "\\|charlie\.url\\s*=\\s*.*|c\\charlie.url = '"${CHARLIE_URL}"'" foo.conf'
This produces
sed -i "\\|charlie\.url\\s*=\\s*.*|c\\charlie.url = foo" foo.conf
You could also, as mentioned, quote the slash that quotes the slash, and then quote the slash being quoted by the other slash, so that the subsequent iterations boil them down to what you want... but that's generally a mess, and leads to leaning-toothpick syndrome.
Someone made a good here-doc suggestion already, so I won't repeat that, but you can also use a "here string" which I generally prefer.
cat <<< "sed -i '\|charlie\.url\s*=\s*.*|c\charlie.url = ${CHARLIE_URL}' foo.conf"
I switched the double-quotes in your script for singles, assuming what you want is to output the literal value of $CHARLIE_URL into your script. If you want the script to use the variable, with whatever value is assigned at runtime, then quote that too -
cat <<< "sed -i '\|charlie\.url\s*=\s*.*|c\charlie.url = \${CHARLIE_URL}' foo.conf"

Search sub string then Replace Line in a file without regex

I want to run a script to search the /etc/bash.bashrc file for the substring
PS1=
and replace the entire line with:
PS1='\[\e[36m\]\h\[\e[m\]\[\e[33m\]#\[\e[m\]\[\e[33m\]\u\[\e[m\]:\[\e[32m\]\W\[\e[m\]>\\$ '
This new line is intended to change the cli prompt.
I have tried and tried sed in a bash script but I couldn't get the regex right.
[Edit] This code now works:
#!/bin/bash
custom_prompt='${debian_chroot:+($debian_chroot)}\[\e[36;40m\]\u\[\e[m\]\[\e[93m\]#\[\e[m\]\[\e[36m\]\h\[\e[m\]:\[\e[92m\]\w\[\e[m\]\[\e[92m\]\\$\[\e[m\]\[\e[93m\]>\[\e[m\]\'
### Setup Bash Prompt
# replace each \ for double \\ in the prompt string
sed_custom_prompt=$(<<<"$custom_prompt" sed 's/\\/\\\\/g')
# add this to /etc/bashrc for global effect
sed -i "s/PS1=.*/PS1=\"$sed_custom_prompt\"/" testrc
The only problem is that it does PS1= " string "
rather than PS1 = ' string ' with back tics.
I need a simple old fashioned non-regex script that finds a string and replaces a line in a file. Regex can find the string but my original statement messed up the substitution.
I don't care if it is perl, awk or bash. I just need something that works.
Instead of writing scripts to replace the existing PS1 jest overwrite it it's much simpler.
echo PS1="This is my prompt" >> /etc/bash.bashrc
This wil append the new PS1 to the end of the file and since this in the end it will overwrite the default PS1 initialization.
You should escape every \ to make sure they aren't lost.
EDIT: The PS1 string should be wrapped with double quotes as well.
$ custom_prompt="\[\e[36m\]\h\[\e[m\]\[\e[33m\]#\[\e[m\]\[\e[33m\]\u\[\e[m\]:\[\e[32m\]\W\[\e[m\]>\\$ "
$ sed_custom_prompt=$(<<<"$custom_prompt" sed 's/\\/\\\\/g')
$ sed -i "s/PS1=.*/PS1=\"$sed_custom_prompt\"/" testrc
$ source testrc
laptop#user:~>$
The following code works on my laptop. The problem was in last \ character in the string of your PS1 variable (I removed it):
#! /bin/bash
custom_prompt='${debian_chroot:+($debian_chroot)}\[\e[36;40m\]\u\[\e[m\]\[\e[93m\]#\[\e[m\]\[\e[36m\]\h\[\e[m\]:\[\e[92m\]\w\[\e[m\]\[\e[92m\]\\$\[\e[m\]\[\e[93m\]>\[\e[m\] '
### Setup Bash Prompt
# replace each \ for double \\ in the prompt string
sed_custom_prompt=$(<<<"$custom_prompt" sed 's/\\/\\\\/g')
# add this to /etc/bashrc for global effect
sed -i "s/PS1=.*/PS1='$sed_custom_prompt'/" testrc
exit 0
p.s. I personally like to add the time to the PS1 so I know how long ago a command is exited. Also, you can immediately time stuff if you add it (\D{%H}:\D{%M}).
Try this:
replace='PS1="\[\e[36m\]\h\[\e[m\]\[\e[33m\]#\[\e[m\]\[\e[33m\]\u\[\e[m\]:\[\e[32m\]\W\[\e[m\]>\\$ "'
perl -i -spe 's/^PS1=.*$/$repl/' -- -repl="$replace" -- /etc/bash.bashrc
Note: Maybe remove the -i (in-place edit) in the first run to check if it works.

echo a one-liner bash script with variables

I would like to do that:
i="1"; echo -e '#!/usr/bin/env bash\nmyprogram -i "input_${i}.txt"'
and pipe it to a job scheduler.
However, this doesn't replace the variable i by its value. Instead, I obtain this:
#!/usr/bin/env bash
myprogram -i "input_${i}.txt"
I played a bit with option -e of echo and with single-/double-quote but could not make it work. For instance, I get this:
i="1"; echo -e "#!/usr/bin/env bash\nmyprogram -i \"input_${i}.txt\""
-bash: !/usr/bin/env: event not found
My bash version is 4.1.2.
Try this:
i="1"; echo -e '#!/usr/bin/env bash\nmyprogram -i '"\"input_${i}.txt\""
You can echo single- and double-quoted strings at the same time.
Try also escaping the exclamation mark:
\! should be okay, and will not be read as an "event" by bash.
i="1"; echo -e '#!/usr/bin/env bash\nmyprogram -i "input_'"${i}"'.txt"'
Basically, use single quotes until you need to interpolate, then close the single quotes, open the double quotes, add the interpolation, close the double quotes, reopen single quotes, and finish the string. In the shell, quotation marks don't delimit a word; they just change the interpretation of the part of a word falling between them.

Append or modify keys in conf files using sed/bash one-liner

I often have to modify files such as sysctl.conf, and I'm familiar with using sed to replace existing values.
Is there a way to append the new key/value pair to the file if sed wasn't able to replace it?
For instance, using this example: modify config file using bash script
sed -c -i "s/\($TARGET_KEY *= *\).*/\1$REPLACEMENT_VALUE/" $CONFIG_FILE
How could I add the $TARGET_KEY = $REPLACEMENT_VALUE new line to $CONFIG_FILE using the same sed expression with slight changes?
And on a related topic, how can I force creation of $CONFIG_FILE if it didn't exist?
You can't easily do it all in a single sed call. It's probably simplest to make it two steps:
if grep -q "$TARGET_KEY *= " $CONFIG_FILE; then
sed -c -i "s/\($TARGET_KEY *= *\).*/\1$REPLACEMENT_VALUE/" $CONFIG_FILE
else
echo "$TARGET_KEY = $REPLACEMENT_VALUE" >>$CONFIG_FILE
fi
There are a few things you should probably do to make the above more robust, however:
Your regular expression is not anchored, which means that trying to set 'PORT' will also find/change 'SUPPORT', etc.
It won't match if the config file might have tabs as well as spaces
It fails if the replacement value has slashes in it.
You should always quote parameter expansions like $CONFIG_FILE, in case the file path contains spaces or shell metacharacters.
It's safe in this instance, but potentially confusing to use single backslashes in a double-quoted string when you want a literal backslash. The shell leaves them alone when it doesn't recognize what comes after it as a special sequence, but it would be clearer if you doubled them.
What does -c do on your version of sed? Neither my Linux nor Mac versions support such an option.
So I would do it this way (the ^I's represent literal tab characters, and the ^A's literal control-A characters, entered by for example typing control-V first on the command line):
if grep -q "^[ ^I]*$TARGET_KEY[ ^I]*=" "$CONFIG_FILE"; then
sed -i -e "s^A^\\([ ^I]*$TARGET_KEY[ ^I]*=[ ^I]*\\).*$^A\\1$REPLACEMENT_VALUE^A" "$CONFIG_FILE"
else
echo "$TARGET_KEY = $REPLACEMENT_VALUE" >> "$CONFIG_FILE"
fi
Use below pseudo code:
if (grep -q key); then
sed...
else
echo key=value >> $CONFIG_FILE
fi

Backticks returns filename with spaces and surrounding command fails

I am trying to do something like "copy the newest file in a directory." I have come up the following command simple command using backticks, which works fine for filenames without embedded white space:
cp -rp `ls -1d searchstring | head -1` destination
As you can see this should work fine when the returned file has no space within it. However, this will obviously not work when there is such a space.
I need either a way to handle the output of the backticks, or some alternate approach.
You can treat the result of the command substitution as a single word by adding double quotes around it:
cp -rp "`ls -t searchstring | head -n 1`" destination
The double quotes are not needed when assigning to a variable. a=`uptime` is equivalent to a="`uptime`".

Resources