Replacing environment variables in a properties file - linux

In Linux, say I have the following file (e.g. conf.properties):
HOST_URL=http://$HOSTNAME
STD_CONFIG=http://$HOSTNAME/config
USER_CONFIG=http://$HOSTNAME/config/$unconfigured
I want to create another file with all the environment variables replaced...e.g. say the environment variable $HOSTNAME is 'myhost' and $unconfigured is not set, a script should produce the following output:
HOST_URL=http://myhost
STD_CONFIG=http://myhost/config
USER_CONFIG=http://myhost/config/
I was thinking this could be done in a simple one-liner with some sort of sed/awk magic, but I'm no expert and my searches have been in vein, so appreciate any help.
Edit:
I should mention that the file can really be any format text file, for example xml. I just want to replace anything that looks like an env variable with whatever is currently set in the environment.

This is what envsubst is for.
echo 'Hello $USER'
Hello $USER
echo 'Hello $USER' | envsubst
Hello malvineous
You would probably use it more like this though:
envsubst < input.txt > output.txt
envsubst seems to be part of GNU gettext.

sed 's/$HOSTNAME/myhost/g;s/$unconfigured//g' yourfile.txt > another_file.txt
update:
Based on updates in your question, this won't be a good solution.
update2 :
This is based on an answer to a related question. I've hacked at it (I'm unfamiliar with perl) to remove undefined vars.
perl -p -e 's/\$\{([^}]+)\}/defined $ENV{$1} ? $ENV{$1} : $&/eg; s/\$\{([^}]+)\}//eg' yourfile.txt
Should work for any input text file, however you will need to define vars using the ${...} format which simplifies the string matching.
(rant regarding the evilness of eval moved to a separate post so as not to confuse readers)

"eval is evil"
This is not an answer, but a warning in response to using eval for this task. You really really really don't want to do that.
Exhibit 1: a malicious template file:
HOST_URL=http://$HOSTNAME
STD_CONFIG=http://$HOSTNAME/config
USER_CONFIG=http://$HOSTNAME/config/$unconfigured
&& cat /etc/redhat-release
An unsuspecting user:
[lsc#aphek]$ cat somefile | while read line; do echo $(eval echo `echo $line`); done
HOST_URL=http://xyz
STD_CONFIG=http://xyz/config
USER_CONFIG=http://xyz/config/
Red Hat Enterprise Linux WS release 4 (Nahant Update 9)
Note the last line!
Now, imagine the possibilities....

I'd do it like this:
# Set the $HOSTNAME and other variables
# Now evaluate the properties file as a shell script.
. config.properties
# Write the values
cat >somefile <<EOF
HOST_URL=$HOST_URL
STD_CONFIG=$STD_CONFIG
USER_CONFIG=$USER_CONFIG
EOF
Edit: Or this very nasty thing (I'm sure there's a better way)
for name in HOST_URL STD_CONFIG USER_CONFIG
echo "$name=$(eval echo `echo '$'$name`)" >>somefile
end

Thanks to #DarkDust I came up with this:
cat somefile | while read line; do echo $(eval echo `echo $line`); done > somefile.replaced

I used this oneliner to replace ${VARIABLE} style variables in a file:
TARGET_FILE=/etc/apache2/apache2.conf; for VARNAME in $(grep -P -o -e '\$\{\S+\}' ${TARGET_FILE} | sed -e 's|^\${||g' -e 's|}$||g' | sort -u); do sed -i "s|\${$(echo $VARNAME)}|${!VARNAME}|g" ${TARGET_FILE}; done
I'm pretty sure someone can do this in 1/3rd of the length using awk… feel challenged! ;)

Here is a snippet of Javascript that I like to have around for solving this exact problem:
// A Javascript version of envsubst for our builds
// Purpose: replace all ocurrences of ${VAR} with the equivalent var from the environment from stdin
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
const environment = process.env;
rl.on('line', function(line) {
const newLine = line.replace(/\$\{([a-zA-Z0-9_]+)\}/g, function(_match, variable) {
const envVar = environment[variable];
return envVar ? envVar : '';
});
process.stdout.write(`${newLine}\n`);
});
Hopefully this helps somebody else.

Here's a short one-liner that uses python's curly brace formatting to safely do the magic:
contents=\"\"\"`cat $file`\"\"\"; python -c "import os;print $contents.format(**os.environ)"
avoids evil eval
allows outputting curly braces: use {{ instead of {
no need to specify vars explicitly when calling the script
For example, given properties file settings.properties:
# my properties file
someVar = {MY_ENV_VAR}
curlyBraceVar = has {{curly braces}}
Then, do the substitution with:
$ export MY_ENV_VAR="hello"
$ file=settings.properties
$ contents=\"\"\"`cat $file`\"\"\"; python -c "import os;print $contents.format(**os.environ)"
# my properties file
someVar = hello
curlyBraceVar = has {curly braces}
A script is here: https://raw.githubusercontent.com/aneilbaboo/machome/master/bin/substenv

if you have installed nodejs you can run
npx #utft/tt -e FOO=bar /path/to/input /path/to/output
or you can run it programmatically
https://github.com/utftufutukgyftryidytftuv/tt

Related

A strange error in shell script while combining two string variables

I will post my script here
#!/bin/tcsh
echo 'Running'
set fileN = '2021-02-07-0448-04S.JKH_RR.SAC'
set fileE = '2021-02-07-0448-04S.JKH_RR_BHE.SAC'
set compR=BHR
set compT=BHT
set compR_name=BHR.SAC
set compT_name=BHT.SAC
set fileN_rot = `echo $fileN | awk '{split($0,a,".SAC"); print a[1]}'`
set fileE_rot = `echo $fileE | awk '{split($0,a,".SAC"); print a[1]}'`
echo 'output1'
echo $fileN
echo $fileE
echo 'output2'
echo $fileN_rot
echo $fileE_rot
echo 'output3'
echo $fileE_rot-$compR_name
echo $fileN_rot-$compT_name
The output is:
Running
output1
2021-02-07-0448-04S.JKH_RR_BHN.SAC 2021-02-07-0448-04S.JKH_RR_BHE.SAC
output2
2021-02-07-0448-04S.JKH_RR_BHN
2021-02-07-0448-04S.JKH_RR_BHE
output3
2021-02-07-0448-04S.JKH_RR_BHN
-BHR.SAC
2021-02-07-0448-04S.JKH_RR_BHE-BHT.SAC
echo $fileE_rot-$compR_name giving wrong output.
Here the out is copy-pasted from the output file,so -BHR.SAC showing in new line.
But in shell terminal it is showing -BHR.SAC07-0448-04S.JKH_RR_BHN.
I find it strange.
Looks like you have some control chars in your strings. Run cat -Ev script to see them and if you see ^Ms in the output then read Why does my tool output overwrite itself and how do I fix it? for how to deal with them.
Don't write scripts in [t]csh, though, as it wasn't designed for that. Writing a script in csh is like digging a hole with a toothbrush - sure you CAN kinda get there in the end but there are better alternatives. See https://www.google.com/search?q=google+csh+why+not.
Having said that, it's not obvious why you're trying to manipulate text in any shell. Shells exist to manipulate (create/destroy) files and processes and sequence calls to tools. The people who invented shell also invented tools such as awk for shell to call when appropriate to manipulate text. So, here is how to really write a shell script to do what you want (the shell part is to call awk to manipulate the text):
$ cat tst.sh
#!/usr/bin/env bash
awk '
BEGIN {
print "Running"
fileN = "2021-02-07-0448-04S.JKH_RR.SAC"
fileE = "2021-02-07-0448-04S.JKH_RR_BHE.SAC"
compR = "BHR"
compT = "BHT"
compR_name = "BHR.SAC"
compT_name = "BHT.SAC"
fileN_rot = fileN
sub(/\.SAC$/,"",fileN_rot)
fileE_rot = fileE
sub(/\.SAC$/,"",fileE_rot)
print "output1"
print fileN
print fileE
print "output2"
print fileN_rot
print fileE_rot
print "output3"
print fileE_rot "-" compR_name
print fileN_rot "-" compT_name
}
'
$ ./tst.sh
Running
output1
2021-02-07-0448-04S.JKH_RR.SAC
2021-02-07-0448-04S.JKH_RR_BHE.SAC
output2
2021-02-07-0448-04S.JKH_RR
2021-02-07-0448-04S.JKH_RR_BHE
output3
2021-02-07-0448-04S.JKH_RR_BHE-BHR.SAC
2021-02-07-0448-04S.JKH_RR-BHT.SAC
or if there really was some reason to want to do it directly in a shell (e.g. this code is in some loop manipulating files named based on these variables) then:
$ cat tst.sh
#!/usr/bin/env bash
fileN='2021-02-07-0448-04S.JKH_RR.SAC'
fileE='2021-02-07-0448-04S.JKH_RR_BHE.SAC'
compR='BHR'
compT='BHT'
compR_name='BHR.SAC'
compT_name='BHT.SAC'
fileN_rot="${fileN%*.SAC}"
fileE_rot="${fileE%*.SAC}"
echo 'output1'
echo "$fileN"
echo "$fileE"
echo 'output2'
echo "$fileN_rot"
echo "$fileE_rot"
echo 'output3'
echo "${fileE_rot}-${compR_name}"
echo "${fileN_rot}-${compT_name}"
$ ./tst.sh
output1
2021-02-07-0448-04S.JKH_RR.SAC
2021-02-07-0448-04S.JKH_RR_BHE.SAC
output2
2021-02-07-0448-04S.JKH_RR
2021-02-07-0448-04S.JKH_RR_BHE
output3
2021-02-07-0448-04S.JKH_RR_BHE-BHR.SAC
2021-02-07-0448-04S.JKH_RR-BHT.SAC

Variables with ssh and awk with perl

Trying to execute remotely a bunch of commands in a perl script
This looks like that :
$CMD1 = "/usr/sbin/mminfo -av -q \"savetime>'-1 day 18:00:00',savetime<'17:59:59'\" -r \"ssid,totalsize,nfiles,pool\"|grep \"xxxxx\"|/usr/bin/awk '!seen[\$1]++'";
print Dumper $CMD1;
$CMD = "/usr/bin/ssh xxxx\#$SRV \'$CMD1\' 2>&1";
print Dumper $CMD;
But I still have problem with the $1 in the awk command, It seems to be cancelled when running.
What I can see :
$VAR1 = '/usr/sbin/mminfo -av -q "savetime>\'-1 day 18:00:00\',savetime<\'17:59:59\'" -r "ssid,totalsize,nfiles,pool"|grep "xxxxxx"|/usr/bin/awk \'!seen[$1]++\'';
$VAR1 = '/usr/bin/ssh xxxxx#\'xxxxxx\' \'/usr/sbin/mminfo -av -q "savetime>\'-1 day 18:00:00\',savetime<\'17:59:59\'" -r "ssid,totalsize,nfiles,pool"|grep "xxxxx"|/usr/bin/awk \'!seen[$1]++\'\' 2>&1';
So the '$1' of the awk command is passed correctly to the remote but when running :
#RESU = `$CMD`;
print Dumper #RESU;
I can see that my $1 is missing (or interpretated by the remote shell as a null value) :
$VAR1 = 'awk: ligne de commande:1: !seen[]++
';
$VAR2 = 'awk: ligne de commande:1: ^ syntax error
';
$VAR3 = 'awk: ligne de commande:1: error: expression indice non valide
';
I've tried many things like quoting or double-quoting the string, creating the string with perl 'qq' function, putting value of $CMD1 directly in $CMD and escaping quotes but no way.
And of course, my awk is piped to another awk (not provided here).
I don't want a solution which runs awk localy since I've millions lines returned from the 'mminfo' command.
Any clue (or a better way to do that !) ?
You might want to break it into smaller pieces for readability, and use the multi-arg invocation of system to avoid perl having to spawn a shell. The q() function goes a long way toward avoiding quoting hell.
$mminfo = q{/usr/sbin/mminfo -av -q "savetime>'-1 day 18:00:00',savetime<'17:59:59'" -r "ssid,totalsize,nfiles,pool"};
$awk = q{/usr/bin/awk '/xxxxx/ && !seen[$1]++');
print Dumper [$mminfo, $awk];
#cmd = ( "/usr/bin/ssh", "xxxx\#$SRV", "$mminfo | $awk" );
print Dumper \#cmd;
system #cmd;
Even if you can not use modules in your final environment, you may be able to use them in your local machine. In that case you can use them to quote the command programmatically and then just copy and paste the quoted string into the script you are developing. For instance:
use strict;
use warnings;
use Net::OpenSSH;
my $quoted_cmd1 = Net::OpenSSH->shell_quote('/usr/sbin/mminfo', '-av',
-q => q(savetime>'-1 day 18:00:00',savetime<'17:59:59'),
-r => 'ssid,totalsize,nfiles,pool',
\\'|',
'grep', 'xxxxx',
\\'|',
'/usr/bin/awk', '!seen[$1]++');
my $SRV = "foo";
my $quoted_cmd = Net::OpenSSH->shell_quote('/usr/bin/ssh', "xxxx\#$SRV",
$quoted_cmd1,
\\'2>&1');
print "$quoted_cmd\n";
Which outputs...
/usr/bin/ssh xxxx#foo '/usr/sbin/mminfo -av -q '\''savetime>'\''\'"''"'-1 day 18:00:00'\''\'"''"',savetime<'\''\'\''17:59:59\'\'' -r ssid,totalsize,nfiles,pool | grep xxxxx | /usr/bin/awk '\''!seen[$1]++'\' 2>&1

Resolving env inside another env

So i would like to print my env variable which contains other variables.
For example i have:
MY_VARS="My var are:\nVAR1 = ${MY_VAR1}\nVAR2 = ${MY_VAR2}"
MY_VAR1=var1
MY_VAR2=var2
and i would like to make it possible in way like:
printf "${MY_VARS}" > my.conf
or
printf "$(echo ${MY_VARS})" > my.conf
to get sth like in my.conf file:
My var are:
VAR1 = var1
VAR2 = var2
but it dosent work.
Is ther possibility to do such thing ?
I need it to us it with Kubernetes so i can set env in my ReplicationController and use it with Kubernetes envs like SERVICE_HOST and SERVICE_PORT
There is another problem that Kubernetes is changing my MY_VARS variable to multiline inside container so it looks like:
MY_VARS=My var are:
VAR1 = ${MY_VAR1}
VAR2 = ${MY_VAR2}
Hope it's quite clear :)
My solution:
while read -r line
do
printf "$line\n" >> conf.tmp
done <<< "$CONFIG"
while read -r line
do
eval echo $line >> conf
done < conf.tmp
Where CONFIG variable is passed by me to the container and this variable contains Kubernetes service variables with IPs and Ports.
I had to make it twice because eval couldnt resolve \n.
With bash you can perform indirect references but it would not replace the value of your variable directly, only when you use it from a bash terminal.
You've got to define the vars first. The following script prints out what you want:
MY_VAR1=var1
MY_VAR2=var2
MY_VARS="My var are:\nVAR1 = ${MY_VAR1}\nVAR2 = ${MY_VAR2}"
printf "${MY_VARS}"

bash script - why value is set under function and not set outside the function

The following bash script's goal is to read CSV file ( all_words.CSV ) and print parameters and values but I have very strange problem.
When I run the script all words parameters (word1-word8) was printed - until now every thing is fine!When I want to print as word1=$word1 outside of function then from some reason word1 not get the value?
Why all parameters (word1-word8) print the values in function, and when I want to print word1 outside the function then word1 is without value?
I tried with export command but it doesn’t help as; export word1=$word1
Please advice how it can be? What the problem here?
#!/bin/bash
read_csv ()
{
CSV_LINE=2
vars=()
c=1
while IFS=, read -ra arr; do
if ((c==1)); then
vars+=("${arr[#]}")
elif ((c==CSV_LINE)); then
for ((i=0; i<${#arr[#]}; i++)); do
declare ${vars[$i]}="${arr[$i]}"
done
fi
((c++))
done < all_words.CSV
echo CSV_LINE=$CSV_LINE
echo word1=$word1
echo word2=$word2
echo word3=$word3
echo word4=$word4
echo word5=$word5
echo word6=$word6
echo word7=$word7
echo word8=$word8
}
read_csv
echo word1=$word1
.
more all_words.CSV
word1,word2,word3,word4,word5,word6,word7,word8
&^#G TR /erfernfjer *&^NHY " "" ? / $#H,#Y^%" E "R$%*&*UJ,**U&^#%%#$^&// \\,^T%!#&^YG.+___KI*&HHTY,%%#$#!%^#&,P/\06E87*UHG11#
,edehu234##!&,~hum&T%6e4
example of script output:
./readWords_from_csv.bash
CSV_LINE=2
word1=&^#G TR / erfernfjer *&^NHY " "" ? / $#H
word2=#Y^%" E "R$%*&*UJ
word3=**U&^#%%#$^&//\\
word4=^T%!#&^YG.+___KI*&HHTY
word5=%%#$#!%^#&
word6=P/\06E87*UHG11#
word7=edehu234##!&
word8=~hum&T%6e4
word1=
man bash explains under declare:
When used in a function, declare makes NAMEs local, as with the local command.
declare -g ${vars[$i]}="${arr[$i]}"
# ^^
Use declare -g to declare a variable at global level in a function. From man bash:
declare [-aAfFgilrtux] [-p] [name[=value] ...]
[...] The -g option forces
variables to be created or modified at the global scope, even
when declare is executed in a shell function. It is ignored in
all other cases. [...]
Here is a simple demonstration of the -g flag (works as expected on GNU bash, version 4.2.37):
#!/bin/bash
function f() {
declare -g V
V="hello"
}
f
echo $V
Please advice ...
Better use printf:
printf -v "${vars[$i]}" "%s" "${arr[$i]}"
Although I'd suggest using an associative array instead. It's the more appropriate solution:
#!/bin/bash
declare -A CSV_VALUES
declare -a CSV_KEYS
function read_csv {
CSV_VALUES=() CSV_KEYS=()
local VALUES I
{
IFS=, read -ra CSV_KEYS
IFS=, read -ra VALUES
} < all_words.csv
for I in "${!CSV_KEYS[#]}"; do
CSV_VALUES[${CSV_KEYS[I]}]=${VALUES[I]}
done
}
read_csv ## Perhaps pass the filename to read_csv as an argument instead?
# We can do for KEY in "${!CVS_VALUES[#]}" but the order is uncertain.
for KEY in "${CSV_KEYS[#]}"; do
echo "CSV_VALUES[$KEY]=${CSV_VALUES[$KEY]}"
done

How do you append to an already existing string?

I want append to a string so that every time I loop over it, it will add "test" to the string.
Like in PHP you would do:
$teststr = "test1\n"
$teststr .= "test2\n"
echo = "$teststr"
Returns:
test1
test2
But I need to do this in a shell script
In classic sh, you have to do something like:
s=test1
s="${s}test2"
(there are lots of variations on that theme, like s="$s""test2")
In bash, you can use +=:
s=test1
s+=test2
$ string="test"
$ string="${string}test2"
$ echo $string
testtest2
#!/bin/bash
message="some text"
message="$message add some more"
echo $message
some text add some more
teststr=$'test1\n'
teststr+=$'test2\n'
echo "$teststr"
VAR=$VAR"$VARTOADD(STRING)"
echo $VAR
thank-you Ignacio Vazquez-Abrams
i adapted slightly for better ease of use :)
placed at top of script
NEW_LINE=$'\n'
then to use easily with other variables
variable1="test1"
variable2="test2"
DESCRIPTION="$variable1$NEW_LINE$variable2$NEW_LINE"
OR
to append thank-you William Pursell
DESCRIPTION="$variable1$NEW_LINE"
DESCRIPTION+="$variable2$NEW_LINE"
echo "$DESCRIPTION"
#!/bin/bash
msg1=${1} #First Parameter
msg2=${2} #Second Parameter
concatString=$msg1"$msg2" #Concatenated String
concatString2="$msg1$msg2"
echo $concatString
echo $concatString2

Resources