Using -f operator on a string that contains curly braces - string

Consider the following here-string that I am trying to use a template for my Nagios definitions.
$blankDefinition = #"
define host{
use windows-server ; Inherit default values from a template
host_name {0} ; The name we're giving to this host
alias {0} ; A longer name associated with the host
address {1} ; IP address of the host
}
"#
I have a script that determines which of my servers are not in nagois that should be. As it loops I'm trying to have it spit out a definion for me so that I can copy and paste into my Nagios config.
$comparison | %{
$blankDefinition -f $($_.serverName),$($_.ipAddress)
}
Which nets me the following error:
Error formatting a string: Input string was not in a correct format..
Something like this works just fine
#"
Testing
One
{0}
Three
"# -f "Twelve"
So then I found out that it is because of the other curly braces. Is there a way that i can format my string using the -f while keeping the braces? Or do i have to use variable subsitution only?

Adding another set of curly braces seems to work:
$blankDefinition = #"
define host{{
use windows-server ; Inherit default values from a template
host_name {0} ; The name we're giving to this host
alias {0} ; A longer name associated with the host
address {1} ; IP address of the host
}}
"#
$blankDefinition -f 'foo', 'bar', 'foo'
Output:
define host{
use windows-server ; Inherit default values from a template
host_name foo ; The name we're giving to this host
alias foo ; A longer name associated with the host
address bar ; IP address of the host
}

Related

Assigning one variable to another in Bash

ip="192.168.1.1"
if [ -n "$(ip = 192.168.1.1)" ];
then
IPADDR=$(ip addr show |grep 'inet '|grep -v 127.0.0.1 |awk '{print $2}'| cut -d/ -f1)
else
"${ip}"="${IPADDR}"
fi
echo "${IPADDR}"
Im trying to assigning ip="192.168.1.1" to variable IPADDR
the error im geting atm is
Object "=" is unknown, try "ip help".
./test: line 7: 192.168.1.1=: command not found
Here ...
if [ -n "$(ip = 192.168.1.1)" ];
... you are executing the command ip, passing arguments = and 192.168.1.1, and capturing its standard output. These are not valid arguments for that command, so ip emits an error message to its standard error, but nothing to its standard output.
The test [ -n "$(ip = 192.168.1.1)" ] evaluates whether the captured output is non-empty, so this test fails, and consequently, the else block is executed. Here ...
"${ip}"="${IPADDR}"
... is not a variable assignment but rather a command, because the text to the left of the = is not (just) an identifier. The IPADDR variable is unset or null, so that expands to 192.168.1.1=. The shell reports that there is no command of that name.
It's hard to be sure what you were really trying to accomplish, but going with the question title, if you want to assign a value to a variable then the syntax is
variable_name=value
The variable_name must be an identifier. The value is subject to expansion and quote removal, but not word splitting, so either of these would work for what you appear to be trying to do
ip="${IPADDR}"
or
ip=${IPADDR}
. With that said, the code does not make sense, because it does not assign a value to variable IPADDR before attempting that assignment.
Earlier, it is possible that you meant
if [ "$ip" != 192.168.1.1 ]
... which tests whether the value of variable $ip, considered as a string, is equal to the string 192.168.1.1. Even then, however, the code does not make sense overall.

How to extract key value pairs from a file when values span multiple lines?

I'm a few weeks into bash scripting and I haven't advanced enough yet to get my head wrapped around this problem. Any help would be appreciated!
I have a "script.conf" file that contains the following:
key1=value1
key2=${HOME}/Folder
key3=( "k3v1" "k3 v2" "k3v3")
key4=( "k4v1"
"k4 v2"
"k4v3"
)
key5=value5
#key6="Do Not Include Me"
In a bash script, I want to read the contents of this script.conf file into an array. I've learned how to handle the scenarios for keys 1, 2, 3, and 5, but the key4 scenario throws a wrench into it with it spanning across multiple lines.
I've been exploring the use of sed -n '/=\s*[(]/,/[)]/{/' which does capture key4 and its value, but I can't figure out how to mix this so that the other keys are also captured in the matches. The range syntax is also new to me, so I haven't figured out how to separate the key/value. I feel like there is an easy regex that would accomplish what I want... in plain-text: "find and group the pattern ^(.*)= (for the key), then group everything after the '=' char until another ^(.*)= match is found, rinse and repeat". I guess if I do this, I need to change the while read line to not handle the key/value separation for me (I'll be looking into this while I'm waiting for a response). BTW, I think a solution where the value of key4 is flattened (new lines removed) would be acceptable; I know for key3 I have to store the value as a string and then convert it to an array later when I want to iterate over it since an array element apparently can't contain a list.
Am I on the right path with sed or is this a job for awk or some other tool? (I haven't ventured into awk yet). Is there an easier approach that I'm missing because I'm too deep into the forest (like changing the while read line in the LoadConfigFile function)?
Here is the code that I have so far in script.sh for processing and capturing the other pairs into the $config array:
__AppDir=$(dirname $0)
__AppName=${__ScriptName%.*}
typeset -A config #init config array
config=( #Setting Default Config values
[key1]="defaultValue1"
[key2]="${HOME}/defaultFolder"
[QuietMode]=0
[Verbose]=0 #Ex. Usage: [[ "${config[Verbose]}" -gt 0 ]] && echo ">>>Debug print"
)
function LoadConfigFile() {
local cfgFile="${1}"
shopt -s extglob #Needed to remove trailing spaces
if [ -f ${cfgFile} ]; then
while IFS='=' read -r key value; do
if [[ "${key:0:1}" == "#" ]]; then
#echo "Skipping Comment line: ${key}"
elif [ "${key:-EMPTY}" != "EMPTY" ]; then
value="${value%%\#*}" # Delete in-line, right comments
value="${value%%*( )}" # Delete trailing spaces
value="${value%%( )*}" # Delete leading spaces
#value="${value%\"*}" # Delete opening string quotes
#value="${value#\"*}" # Delete closing string quotes
#Manipulate any variables included in the value so that they can be expanded correctly
# - value must be stored in the format: "${var1}". `backticks`, "$var2", and "doubleQuotes" are left as is
value="${value//\"/\\\"}" # Escape double quotes for eval
value="${value//\`/\\\`}" # Escape backticks for eval
value="${value//\$/\\\$}" # Escape ALL '$' for eval
value="${value//\\\${/\${}" # Undo the protection of '$' if it was followed by a '{'
value=$(eval "printf '%s\n' \"${value}\"")
config[${key}]=${value} #Store the value into the config array at the specified key
echo " >>>DBG: Key = ${key}, Value = ${value}"
#else
# echo "Skipped Empty Key"
fi
done < "${cfgFile}"
fi
}
CONFIG_FILE=${__AppDir}/${__AppName}.conf
echo "Config File # ${CONFIG_FILE}"
LoadConfigFile ${CONFIG_FILE}
#Print elements of $config
echo "Script Config Values:"
echo "----------------------------"
for key in "${!config[#]}"; do #The '!' char gets an array of the keys, without it, we would get an array of the values
printf " %-20s = %s\n" "${key}" "${config[${key}]}"
done
echo "------ End Script Config ------"
#To convert to an array...
declare -a valAsArray=${config[RequiredAppPackages]} #Convert the value from a string to an array
echo "Count = ${#valAsArray[#]}"
for itemCfg in "${valAsArray[#]}"; do
echo " item = ${itemCfg}"
done
As I mentioned before, I'm just starting to learn bash and Linux scripting in general, so if you see that I'm doing some taboo things in other areas of my code too, please feel free to provide feedback in the comments... I don't want to start bad habits early on :-).
*If it matters, the OS is Ubuntu 14.04.
EDIT:
As requested, after reading the script.conf file, I would like for the elements in $config[#] to be equivalent to the following:
typeset -A config #init config array
config=(
[key1]="value1"
[key2]="${HOME}/Folder"
[key3]="( \"k3v1\" \"k3 v2\" \"k3v3\" )"
[key4]="( \"k4v1\" \"k4 v2\" \"k4v3\" )"
[key5]="value5"
)
I want to be able to convert the values of elements 'key4' and 'key3' into an array and iterated over them the same way in the following code:
declare -a keyValAsArray=${config[keyN]} #Convert the value from a string to an array
echo "Count = ${#keyValAsArray[#]}"
for item in "${keyValAsArray[#]}"; do
echo " item = ${item}"
done
I don't think it matters if \n is preserved for key4's value or not... that depends on if declare has a problem with it.
A shell is an environment from which to call tools with a language to sequence those calls. It is NOT a tool to manipulate text. The standard UNIX tool to manipulate text is awk. Trying to manipulate text in shell IS a bad habit, see why-is-using-a-shell-loop-to-process-text-considered-bad-pr‌​actice for SOME of the reasons why
You still didn't post the expected result of populating the config array so I'm not sure but I think this is what you wanted:
$ cat tst.sh
declare -A config="( $(awk '
{ gsub(/^[[:space:]]+|([[:space:]]+|#.*)$/,"") }
!NF { next }
/^[^="]+=/ {
name = gensub(/=.*/,"",1)
value = gensub(/^[^=]+=/,"",1)
n2v[name] = value
next
}
{ n2v[name] = n2v[name] OFS $0 }
END {
for (name in n2v) {
value = gensub(/"/,"\\\\&","g",n2v[name])
printf "[%s]=\"%s\"\n", name, value
}
}
' script.conf
) )"
declare -p config
$ ./tst.sh
declare -A config='([key5]="value5" [key4]="( \"k4v1\" \"k4 v2\" \"k4v3\" )" [key3]="( \"k3v1\" \"k3 v2\" \"k3v3\")" [key2]="/home/Ed/Folder" [key1]="value1" )'
The above uses GNU awk for gensub(), with other awks you'd use [g]sub() instead.

What is wrong in my script, tell me please

I need that scipt enter in my router on page and find the code and compared with earlier this IP information recorded, and if not changed, then stop the script.
I run this script - $ perl ~/test.pl
no error, but file my_ip.txt is not created.
In originals script must check my ip through host example.dyndns.org, but my ip is gray.
So I need to be determined through a router
#!/usr/bin/perl
use LWP::UserAgent;
my $routeraddress = `addr admin:Tavolzhansky#192.168.1.1/RST_conn_status.htm`;
if ($routeraddress =~ /var info_get_wanip="((\d+\.){3}(\d+))"/) {
my $ip = "$1.$2.$3.$4";
#Добавлено:
open (FILE,"my_ip.txt");
my #lines = <FILE>;
$old_ip = $lines[0]; #Считываем IP из файла
$old_ip =~ s/^\s+|\s+$//g; #trim
close(FILE);
if ($old_ip eq $ip) {
die "IP not changed"; # Выходим из скрипта, если IP не изменился
}
open (FILE,">my_ip.txt");
print FILE $ip; # Записываем в файл новый IP
close(FILE);
...... (this code is OK)
}
Do not bring the end of the code because the problem by connecting to the router
$ perl -cw -e 'print `addr admin:foo#192.168.1.1/bar`'
Possible unintended interpolation of #192 in string at -e line 1.
-e syntax OK
It doesn't work because #192 is interpolated as an array, and you are running the command addr admin:Tavolzhansky.168.1.1/RST_conn_status.htm instead of the command you meant to.
This would have been very easy to spot and fix if you would use warnings, if you would step through the code with the debugger, or as Andy suggests, if you would examine return values.
To get #192 to not be interpolated, escape the #:
my $routeraddress = `addr admin:Tavolzhansky\#192.168.1.1/RST_conn_status.htm`;
It's not working because of this:
my $routeraddress = `addr admin:Tavolzhansky#192.168.1.1/RST_conn_status.htm`;
if ($routeraddress =~ /var info_get_wanip="((\d+\.){3}(\d+))"/) {
...
}
Try adding a couple debug prints to confirm like:
my $routeraddress = `addr admin:Tavolzhansky#192.168.1.1/RST_conn_status.htm`;
print "Router IP = $routeraddress/n";
if ($routeraddress =~ /var info_get_wanip="((\d+\.){3}(\d+))"/) {
...
} else {
print "Router address didn't match.\n";
}
You should use the strict and warning pragmas, but as you said this worked when you were getting your IP from a different source, the rest of the code will work.
I'm guessing you're reading an HTML page and expecting to see this string somewhere:
var info_get_wanip="xxx.xxx.xxx.xxx";
However, you need to confirm if $routeraddress is actually getting that, it looks like
my $routeraddress = `addr admin:Tavolzhansky#192.168.1.1/RST_conn_status.htm`;
Is just essentially making a get request for the page, but isn't returning "var info_get_wanip="xxx.xxx.xxx.xxx";". If you sort the assignment of the variable $routeraddress, then it'll work.
I can't say how, as I don't know what it's returning now.

Assigning one variable to another in Bash?

I have a doubt. When i declare a value and assign to some variable, I don't know how to reassign the same value to another variable. See the code snippet below.
#/bin/sh
#declare ARG1 to a
a=ARG1
#declaring $a to ARG2
ARG2=$`$a`
echo "ARG 2 = $ARG2"
It should display my output as
ARG 2 = ARG1
...but instead the actual output is:
line 5: ARG1: command not found
ARG 2 = $
To assign the value associated with the variable dest to the variable source, you need simply run dest=$source.
For example, to assign the value associated with the variable arg2 to the variable a:
a=ARG1
arg2=$a
echo "ARG 2 = $arg2"
The use of lower-case variable names for local shell variables is by convention, not necessity -- but this has the advantage of avoiding conflicts with environment variables and builtins, both of which use all-uppercase names by convention.
You may also want to alias rather than copy the variable. For example, if you need mutation. Or if you want to run a function multiple times on different variables. Here's how it works
Example:
C=cat
declare -n VAR=C
VAR+=" says Hi"
echo "$C" # prints "cat says Hi"
Example with arrays/dictionaries:
A=(a a a)
declare -n VAR=A # "-n" stands for "name", e.g. a new name for the same variable
VAR+=(b)
echo "${A[#]}" # prints "a a a b"
That is, VAR becomes effectively the same as the original variable. Instead of copying, you're adding an alias. Here's an example with functions:
function myFunc() {
local -n VAR="$1"
VAR="Hello from $2"
echo "I've set variable '$1' to value '$VAR'"
}
myFunc Inbox Bob # I've set variable 'Inbox' to value 'Hello from Bob'
myFunc Luke Leia # I've set variable 'Luke' to value 'Hello from Leia'
echo "$Luke" # Hello from Leia
Whether you should use these approaches is a question. Generally, immutable code is easier to read and to reason about (in almost any programming language). However, sometimes you really need to get stuff done in a certain way. Hope this answer helps you then.

bash - How to find string from file and get its position?

File services - contains many records like this one:
define service {
host_name\t\t\t\tHOSTNAME
...
...
}
File hosts - contains records:
define host {
host_name\t\t\t\tHOSTNAME
...
...
}
and I need to go to hosts, somehow get name of HOSTNAME from first record, then go to file services and find all records with that HOSTNAME and put them to other file. Then do it for every HOSTNAME in hosts.
What I don't know is primary how to get the HOSTNAME from file hosts and then how to get a whole record in file services to a variable. I have prepared a regex (hope it's right) ^define.*host_name\t\t\t\t$HOSTNAME.*}
Please give me a few advices or examples how to get wanted result.
The files you provide look very much like nagios configuration files.
sed might be your friend here, as it allows you to slice the file into smaller parts, eg:
:t
/^define service {/,/}$/ { # For each line between these block markers..
/}$/!{ # If we are not at the /end/ marker
$!{ # nor the last line of the file,
N; # add the Next line to the pattern space
bt
} # branch (loop back) to the :t label.
} # This line matches the /end/ marker.
/host_name[ \t]\+HOSTNAME\b/!d; # delete the block if wrong host.
}
That example lifted from the sed faq 4.21, and adapted slightly. You could also look at question 4.22 which appears to address this directly:
http://sed.sourceforge.net/sedfaq4.html#s4.22
Like the previous answer, I'm also inclined to say you're probably better off using another scripting language. If you need a different interpreter to get this done anyway, might as well use something you know.
This task a bit too complex for a bash script. I would use Perl:
#!/usr/bin/perl
use warnings;
use strict;
open my $SRV, '<', 'services' or die $!;
open my $HST, '<', 'hosts' or die $!;
my %services;
{ local $/ = "\n}";
while (my $service = <$SRV>) {
my ($hostname) = $service =~ /^\s*host_name\t+(.+?)\s*$/m;
push #{ $services{$hostname} }, $service if defined $hostname;
}
}
while (my $line = <$HST>) {
if (my ($host) = $line =~ /^\s*host_name\t+(.+?)\s*$/) {
if (exists $services{$host}) {
print "===== $host =====\n";
print "$_\n" for #{ $services{$host} };
} else {
warn "$host not found in services!\n";
}
}
}

Resources