Search & Replace String Value in YAML with format using Shell - sed

I have YAML find and replace value with correct YAML format (with space and quote). Below Sample YAML, I am able to replace jdbcUrl value using below Sed command. But, need help how to prefix a space and quote of the value using Sed.
Below Sed will find and replace a required jdbcUrl. But, it won't prefix a space (YAML standard) and add quote of the value.
Script for find and replace URL:
DB_URL='jdbc:mysql://localhost:3306/sd?autoReconnect=true'
sed -i -e 's, MYDATABASE,'$DB_URL',g' input.yaml
Sample Input Yaml:
- name: AP_DB
description: "datasource"
jndiConfig:
name: jdbc/AP_DB
definition:
type: RDBMS
configuration:
jdbcUrl: MYDATABASE
username: username
password: password
driverClassName: com.mysql.jdbc.Driver
Required Output Yaml:
- name: AP_DB
description: "datasource"
jndiConfig:
name: jdbc/AP_DB
definition:
type: RDBMS
configuration:
jdbcUrl: 'jdbc:mysql://localhost:3306/sd?autoReconnect=true'
username: username
password: password
driverClassName: com.mysql.jdbc.Driver

You seem to have few misconceptions that seem to hinder you from solving this:
Your input file is invalid YAML and the replacement of MYDATABASE is not going to fix that. You cannot have a scalar value for name and a mapping (starting with key description) at the same time. I assume that your file would need to look like:
- name: AP_DB
description: "datasource"
jndiConfig:
name: jdbc/AP_DB
definition:
type: RDBMS
configuration:
jdbcUrl: MYDATABASE
username: username
password: password
driverClassName: com.mysql.jdbc.Driver
Adding quotes to the value assigned to DB_URL in the shell doesn't make a difference
you are not using the shell, but sed to make the changes. Your shell is just used to invoke sed
You invoke sed with -i which overwrites your input.yaml, that makes it difficult to see if the output is correct, and you need to roll-back your changes
The space is not a prefix, it is the whitespace that normally needs to followi YAML's value indicator (:)
You match that space in your matching pattern, but you don't have it in your substitution pattern, nor are there any quotes in the substitution pattern. You probably think there are surrounding $DB_URL, but of course they are not.
The quotes around the URL in your output are superfluous
If you really want the output as you indicate there are several options. First of all you could just change the relevant line in your YAML to include the quotes
jdbcUrl: 'MYDATABASE'
and slightly change your sed command:
sed -e 's,MYDATABASE,'$DB_URL',g' < input.yaml
If you cannot change the input.yaml, you can just add the quotes (and the space) to the sed substitution:
sed -e 's, MYDATABASE, "'$DB_URL'",g' < input.yaml
Or not use the single quotes, and concacatenate a prefix and postfix to $DB_URL but use double quotes, which do allow $DB_URL to be expanded:
sed -e "s, MYDATABASE, '$DB_URL',g" < input.yaml
Once you verify that any of these solutions work you can re-add the inplace replacment option -i to sed.
sed is not the right kind of tool for this, especially not because you seem unfamiliar with it and with YAML. Use a proper YAML parser to do these kind of things. They tend to keep working when simple pattern matching no longer gets the job done. And the parser's dumping mechanism knows when to insert quotes instead of dumbly inserting them when they are not needed. A parser would also indicate that your input is invalid YAML right from the start.
It is bit more code to do this e.g. in Python, but at least it only matches mapping values that are exactly your substitution string, and doesn't try to do substitutions on keys, sequence items, within YAML comments, or on mapping values like ORIG_MYDATABASE, if these happen to be in the file. Preventing that from happening using sed can be quite a challenge.
Such a Python program could look like subst.py:
import sys
from pathlib import Path
from ruamel.yaml import YAML
val = sys.argv[1]
subst = sys.argv[2]
file_name = Path(sys.argv[3])
def update(d, val, sub):
if isinstance(d, dict):
for k in d:
v = d[k]
if v == val:
d[k] = sub
else:
update(v, val, sub)
elif isinstance(d, list):
for item in d:
update(item, val, sub)
yaml = YAML()
yaml.preserve_quotes = True # to preserve superfluous quotes in the input
data = yaml.load(file_name)
update(data, val, subst)
yaml.dump(data, file_name)
and be invoked from the shell, just like sed would have to be, by using:
python subst.py MYDATABASE $DB_URL input.yaml
Of course there will be no quotes in the output around the URL, as they are superfluous and not in the input file, but the superfluous quotes around datasource are preserved.

Here's a more automated way to change a "key : new value", in your yaml file:
yaml_file="input.yaml"
key="jdbcUrl"
new_value="'jdbc:mysql://localhost:3306/sd?autoReconnect=true'"
sed -r "s/^(\s*${key}\s*:\s*).*/\1${new_value}/" -i "$yaml_file"

Related

Pass shell variable to mongodb script

I am using jenkins and mongodb on centos 7 server.
I want to insert some jenkins build data on success to my mongo database.
this is the code i'm running in my post build task shell:
echo 'password' | su -
mongo jenkinsdb <<\EOF
db.history.insert({wokspace:$WORKSPACE,remote_url:$GIT_URL,branch:$GIT_BRANCH,type:"back",date:$(date
'+%Y-%m-%d %H:%M:%S'),description:$short_description})
db.history.find()
EOF
The problem is mongo is interpreting the Jenkins environnement variables as in simple strings.
How can i pass those variables to the mongo script ?
You are preventing the variable interpolation by using \ before EOF. See https://tldp.org/LDP/abs/html/here-docs.html:
Quoting or escaping the "limit string" at the head of a here document disables parameter substitution within its body. The reason for this is that quoting/escaping the limit string effectively escapes the $, `, and \ special characters, and causes them to be interpreted literally.
serene% a=1
serene% cat <<T
heredoc> $a
heredoc> T
1
serene% cat <<\T
heredoc> $a
heredoc> T
$a
Remove the backslash if you want interpolation to happen.

how to do sed in-line replacement without backup file if the original file is not changed ? in case the file was not changed

I would like to retain a backup file, only if sed altered the original file.
for example:
I have the following file:
# cat test
This is example file
abcd
efgh
process with sed so there is nothing to change:
# sed -i.BAK "s/AAAA/BBBB/" test
The "test" file is not changed because nothing matched. In this case, I would like to avoid the backup file that was created:
# md5sum test*
d3ca57583595576338ad6f9a01276cd5 test
d3ca57583595576338ad6f9a01276cd5 test.BAK
I learned that what I was asking is not possible by "sed" as I suspected by RTFM.
I solved by adding "if [ grep ... ] " on the expression needed to replace.
The "sed" is performed if and only if the expression exists.
Thanks for the people that commented.

Trouble using sed to replace contents of .coveralls.yml configuration in Travis-CI

I'm trying to take an environment variable in travis-ci and replace the contents of a file at runtime using sed.
The file in question contains:
service_name: travis-ci
repo_token: COVERALLS_TOKEN
On an ubuntu system, using sed -i 's/COVERALLS_TOKEN/ASDF/g' .coveralls.yml in the command line works, but carrying that over to the travis-ci configuration something like sed -i 's/COVERALLS_TOKEN/$COVERALLS_TOKEN/g' .coveralls.yml doesn't pull the environment variable.
What really throws me off is that I have a project today where the below .travis.yml entry works, but adapting it to this circumstances it doesn't.
Original implementation, still works today
sed -ri 's/^MY_ENV_VAR=/MY_ENV_VAR='$MY_ENV_VAR'/' .env
Adaptation (doesn't work)
sed -ri 's/^COVERALLS_TOKEN/$COVERALLS_TOKEN/' .coveralls.yml
You have two problems with your command. First, the ^ means it will only match COVERALLS_TOKEN where it occurs at the very beginning of a line. Since it's not at the beginning of a line in your YAML file, there is no match and the sed command does nothing.
Second, there is no variable substitution inside single quotation marks.
So remove the ^and use double quotes instead of single ones:
sed -ri "s/COVERALLS_TOKEN/$COVERALLS_TOKEN/" .coveralls.yml
Some notes:
The variable $COVERALLS_TOKEN must be set in the shell at the time that you run the sed command.
The substitution will fail with a syntax error if the value of $COVERALLS_TOKEN contains the delimiter you use on the substitution command. The command above uses /, but you can change that if needed - just pick something that doesn't occur in the token string.
The token value will not be quoted in any way in the YAML file. Normally that's ok, but if there are any weird characters in the value, you will need to put quotes around it in the YAML by adding them to the replacement side of the substitution command as well:
sed -ri "s/COVERALLS_TOKEN/'$COVERALLS_TOKEN'/" .coveralls.yml
The single quotes suppress variable expansion.
This is works for me.
sed -ri 's,IMAGE_REPOSITORY,'"$IMAGE_REPO"',g' chart/values.yaml

Replace specials characters with sed

I am trying to use a sed command to replace specials characters in my file.
The characters are %> to replace by ].
I'am using sed -r s/\%>\/\]\/g but i have this error bash: /]/g: No such file or directory, looks like sed doesn't like it.
Put your sed code inside quotes and also add the file-path you want to work with and finally don't escape the sed delimiters.
$ echo '%>' | sed 's/%>/]/g'
]
ie,
sed 's/%>/]/g' file
To complement Avinash Raj's correct and helpful answer:
Since you were using an overall unquoted string (neither single- nor double-quoted), you were on the right track by \-escaping individual characters in your sed command.
However, you neglected to \-quote >, which is what caused your problem:
> is one of the shell's so-called metacharacters
Metacharacters have special meaning and separate words
Thus, s/\%>\/\]\/g is mistakenly split into 2 arguments by >:
s/\% is passed to sed - as s/%, because the shell removes the \ instances (a process called quote removal).
As you can see, this is not a valid sed command, but that doesn't even come into play - see below.
>\/\]\/g is interpreted by the shell (bash), because it starts with output-redirection operator >; after quote removal, the shell sees >/]/g, tries to open file /]/g for writing, and fails, because your system doesn't have a subdirectory named ] in its root directory.
bash tries to open an output file specified by a redirection before running the command and, if it fails to open the file, does not run the command - which is what happened here:
bash complained about the nonexistent target directory and aborted processing of the command - sed was never even invoked.
Upshot:
In a string that is neither enclosed in single nor in double-quotes, you must \-quote:
all metacharacters: | & ; ( ) < > space tab
additionally, to prevent accidental pathname expansion (globbing): * ? [
Also note that if you need to quote (escape) characters for sed,you need to add an extra layer of quoting; for instance to instruct sed to use a literal . in the regex, you must pass \\. - two backslashes - so that sed sees the properly escaped \..
Given the above, it is much simpler to (habitually) use single quotes around your sed command, because it ensures that the string is passed as is to sed.
Let's compare a working version of your command to the one from Avinash Raj's answer (leaving out the -r for brevity):
sed s/\%\>\/\]\/g # ok - all metachars. \-quoted, others are, but needn't be quoted
sed s/%\>/]/g # ok - minimum \-quoting
sed 's/%>/]/g' # simplest: single-quoted command
I'm not sure whether I got the question correctly. If you want to replace either % or > by ] then sed is not required here. Use tr in this case:
tr '%>' ']' < input.txt
If you want to replace the sequence %> by ] then the sed command as shown by #AvinashRaj is the way to go.

sed command to extract from xml

I'm using my mac terminal to do a script, it basically does:
wget http://p2.edms-pr.ccomrcdn.com/player/player_dispatcher.html?section=radio&action=listen_live
This file returns an XML which I can save to txt or XML, I'm saving it as "url.xml"
<PlayerContent>
<ListenLiveInitialize>
<StreamInfo>
<stream id="4694" primary_location="rtmp://cp58082.live.edgefcs.net/live/COR_5103_OR#s5137?auth=daEaIcRcbb.afahbOdwbWdjdYcEdYaOaDdc-bn7nM7-4q-PN0X1_3nqDHom4EBvmEuwr&aifp=1234&CHANNELID=4694&CPROG=_&MARKET=PREMIERE&REQUESTOR=EDMS-PR&SERVER_NAME=p2.edms-pr.ccomrcdn.com&SITE_ID=13293&STATION_ID=EDMS-PR&MNM=_&TYPEOFPLAY=0" backup_location=""/>
</StreamInfo>
<JustPlayed/>
I want to used SED to return the AUTH code inside "primary_location". So basically I want to store
daEaIcRcbb.afahbOdwbWdjdYcEdYaOaDdc-bn7nM7-4q-PN0X1_3nqDHom4EBvmEuwr
on a variable.
I found this online but it doesn't seem to be working.
sed -n 's/.*\(auth=......................................... ...........................\).*/\1/p' url.xml
Try
sed -n 's|^<stream.*auth\=\(.*\)\&ai.*|\1|p' url.xml
which reads the file and matches the line up to the = before the auth code, stores everything from there up to the & in &ai as \1 which is then substituted for the whole pattern space.
You have a stray space () in the middle of your .s!
This is neater and will output auth= with the value (it looks like it's a string of alphanumerics with hyphens and underscores):
% grep -o 'auth=[[:alnum:]_-]\+' url.xml
You could even use it like so:
% eval $(grep -o 'auth=[[:alnum:]_-]\+' url.xml)
% echo ${auth}
daEaIcRcbb.afahbOdwbWdjdYcEdYaOaDdc-bn7nM7-4q-PN0X1_3nqDHom4EBvmEuwr
Works on OSX.