Search sub string then Replace Line in a file without regex - bash

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.

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"

Using value inside a variable without expanding

I am trying to find and replace a specific text content using the sed command and to run it via a shell script.
Below is the sample script that I am using:
fp=/asd/filename.txt
fd="sed -i -E 's ($2).* $2:$3 g' ${fp}"
eval $fd
and executing the same by passing the arguments:
./test.sh update asd asdfgh
But if the argument string contains $ , it breaks the commands and it is replacing with wrong values, like
./test.sh update asd $apr1$HnIF6bOt$9m3NzAwr.aG1Yp.t.bpIS1.
How can I make sure that the values inside the variables are not expanded because of the $?
Updated
sh file test.sh
set -xv
fp="/asd/filename.txt"
sed -iE "s/(${2//'$'/'\$'}).*/${2//'$'/'\$'}:${3//'$'/'\$'}/g" "$fp"
text file filename.txt
hello:world
Outputs
1)
./test.sh update hello WORLD
sed -iE "s/(${2//'$'/'\$'}).*/${2//'$'/'\$'}:${3//'$'/'\$'}/g" "$fp"
++ sed -iE 's/(hello).*/hello:WORLD/g' /asd/filename.txt
2)
./test.sh update hello '$apr1$hosgaxyv$D0KXp5dCyZ2BUYCS9BmHu1'
sed -iE "s/(${2//'$'/'\$'}).*/${2//'$'/'\$'}:${3//'$'/'\$'}/g" "$fp"
++ sed -iE 's/(hello).*/hello:'\''$'\''apr1'\''$'\''hosgaxyv'\''$'\''D0KXp5dCyZ2BUYCS9BmHu1/g' /asd/filename.txt
In both the case , its not replacing the content
You don't need eval here at all:
fp=/asd/filename.txt
sed -i -E "s/(${2//'$'/'\$'}).*/\1:${3//'$'/'\$'}/g" "$fp"
The whole sed command is in double quotes so variables can expand.
I've replaced the blank as the s separator with / (doesn't really matter in the example).
I've used \1 to reference the first capture group instead of repeating the variable in the substitution.
Most importantly, I've used ${2//'$'/'\$'} instead of $2 (and similar for $3). This escapes every $ sign as \$; this is required because of the double quoting, or the $ get eaten by the shell before sed gets to see them.
When you call your script, you must escape any $ in the input, or the shell tries to expand them as variable names:
./test.sh update asd '$apr1$HnIF6bOt$9m3NzAwr.aG1Yp.t.bpIS1.'
Put the command-line arguments that are filenames in single quotes:
./test.sh update 'asd' '$apr1$HnIF6bOt$9m3NzAwr.aG1Yp.t.bpIS1'
must protect all the script arguments with quotes if having space and special shell char, and escape it if it's a dollar $, and -Ei instead of -iE even better drop it first for test, may add it later if being really sure
I admit i won't understant your regex so let's just get in the gist of solution, no need eval;
fp=/asd/filename.txt
sed -Ei "s/($2).*/$2:$3/g" $fp
./test.sh update asd '\$apr1\$HnIF6bOt\$9m3NzAwr.aG1Yp.t.bpIS1.'

sed command correct usage in bash [duplicate]

I am trying to change the values in a text file using sed in a Bash script with the line,
sed 's/draw($prev_number;n_)/draw($number;n_)/g' file.txt > tmp
This will be in a for loop. Why is it not working?
Variables inside ' don't get substituted in Bash. To get string substitution (or interpolation, if you're familiar with Perl) you would need to change it to use double quotes " instead of the single quotes:
# Enclose the entire expression in double quotes
$ sed "s/draw($prev_number;n_)/draw($number;n_)/g" file.txt > tmp
# Or, concatenate strings with only variables inside double quotes
# This would restrict expansion to the relevant portion
# and prevent accidental expansion for !, backticks, etc.
$ sed 's/draw('"$prev_number"';n_)/draw('"$number"';n_)/g' file.txt > tmp
# A variable cannot contain arbitrary characters
# See link in the further reading section for details
$ a='foo
bar'
$ echo 'baz' | sed 's/baz/'"$a"'/g'
sed: -e expression #1, char 9: unterminated `s' command
Further Reading:
Difference between single and double quotes in Bash
Is it possible to escape regex metacharacters reliably with sed
Using different delimiters for sed substitute command
Unless you need it in a different file you can use the -i flag to change the file in place
Variables within single quotes are not expanded, but within double quotes they are. Use double quotes in this case.
sed "s/draw($prev_number;n_)/draw($number;n_)/g" file.txt > tmp
You could also make it work with eval, but don’t do that!!
This may help:
sed "s/draw($prev_number;n_)/draw($number;n_)/g"
You can use variables like below. Like here, I wanted to replace hostname i.e., a system variable in the file. I am looking for string look.me and replacing that whole line with look.me=<system_name>
sed -i "s/.*look.me.*/look.me=`hostname`/"
You can also store your system value in another variable and can use that variable for substitution.
host_var=`hostname`
sed -i "s/.*look.me.*/look.me=$host_var/"
Input file:
look.me=demonic
Output of file (assuming my system name is prod-cfm-frontend-1-usa-central-1):
look.me=prod-cfm-frontend-1-usa-central-1
I needed to input github tags from my release within github actions. So that on release it will automatically package up and push code to artifactory.
Here is how I did it. :)
- name: Invoke build
run: |
# Gets the Tag number from the release
TAGNUMBER=$(echo $GITHUB_REF | cut -d / -f 3)
# Setups a string to be used by sed
FINDANDREPLACE='s/${GITHUBACTIONSTAG}/'$(echo $TAGNUMBER)/
# Updates the setup.cfg file within version number
sed -i $FINDANDREPLACE setup.cfg
# Installs prerequisites and pushes
pip install -r requirements-dev.txt
invoke build
Retrospectively I wish I did this in python with tests. However it was fun todo some bash.
Another variant, using printf:
SED_EXPR="$(printf -- 's/draw(%s;n_)/draw(%s;n_)/g' $prev_number $number)"
sed "${SED_EXPR}" file.txt
or in one line:
sed "$(printf -- 's/draw(%s;n_)/draw(%s;n_)/g' $prev_number $number)" file.txt
Using printf to build the replacement expression should be safe against all kinds of weird things, which is why I like this variant.

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

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.

Sed replace on bashrc with hostname variables and double quoted

I'm tired to find a solution for sed substitution on /etc/bashrc
I like change the line
PS1="\u#\h NEW [\w]# "
for this
PS1="\u#\h.hostname.com NEW [\w]# "
where hostname.com
HOST=$(hostname)
Try several sustituions, wiht escape \ but nothing.
Also see another question, How do I escape backslashes in a sed script embedded in a bash script but I'm lost.
Usually I use "" for use ${VARIABLE} but don't work, also if use \ for escape
sed -i "s/^PS1.*$/PS1=\"\\u#\\h.${HOST}\ \[\\w\]#\ \"/g" /etc/bashrc
Use \H if you want the complete hostname in your prompt. Then you wouldn't have to worry about this sed substitution.
Change your PS1 to:
PS1="\u#\H NEW [\w]# "
If you want to use sed, the following command should work:
sed 's/\(^.*\h\)\(.*$\)/\1.'"$HOST"'\2/g' /etc/bashrc
After several test, I've got a solution.
HOST=$(hostname); sed -i "s/^PS1.*$/PS1=\"\\\u#\\\h.${HOST}\ \[\\\w\]#\ \"/g" /etc/bashrc
Afert read comment from user dogbane y change to
sed -i "s/^PS1.*$/PS1=\"\\\u#\\\H\ \[\\\w\]#\ \"/g" /etc/bashrc
Thanks...

Resources