I am trying to create automated test and I do not understand why this happens, the error is:
Test case: Verify that circuit breaker has status CLOSED -> Test FAILED, EXPECTED VALUE: CLOSED, ACTUAL VALUE: "CLOSED", WILL ABORT
Why does it not compare Strings even if they are? I am new to bash scripting so this is probably something pretty obvious.
Function which is calling Spring Boot Actuator
My code looks like:
function testCircuitBreaker() {
echo "Start Circuit Breaker Test"
EXEC="docker run --rm -it --network=my-network alpine"
#Verify that circuit breaker is closed via health endpoint
assertEqual "CLOSED" "$($EXEC wget movie-composite:8080/actuator/health -qO - | jq .components.movieCircuitBreaker.details.state)" "Verify that circuit breaker has status CLOSED"
#Three slow calls to get TimeoutException
for ((n = 0; n < 3; n++)); do
assertCurl 500 "curl -k https://$HOST:$PORT/movie-composite/MOV_ID_REVS_RECS?delay=3 $AUTH -s"
message=$(echo $RESPONSE | jq -r .message)
assertEqual "Did not observe any item or terminal signal within 2000ms" "${message:0:57}"
done
}
Assertion Function:
function assertEqual() {
local expected=$1
local actual=$2
local message=$3
printf "Test case: $message -> "
if [ "$actual" = "$expected" ]; then
echo "Test OK (actual value: $actual)"
return 0
else
echo "Test FAILED, EXPECTED VALUE: $expected, ACTUAL VALUE: $actual, WILL ABORT"
return 1
fi
}
jq outputs well-formed JSON by default, so strings will be quoted. As an example:
$ jq .foo <<<'{"foo":"bar"}'
"bar"
Therefore your assertEqual "CLOSED" "$(... | jq .components.movieCircuitBreaker.details.state)" ... command is comparing CLOSED with "CLOSED" (note the extra quotes). You could quote the expected string, as #rtx13 suggests, but I think it's clearer to have jq output just the contents of the field. You can do that with the --raw-output flag:
With this option, if the filter's result is a string then it will be written directly to standard output rather than being formatted as a JSON string with quotes. This can be useful for making jq filters talk to non-JSON-based systems.
$ jq -r .foo <<<'{"foo":"bar"}'
bar
You could try preserving the quotes around CLOSED by changing:
assertEqual "CLOSED" "$($EXEC wget movie-composite:8080/actuator/health -qO - | jq .components.movieCircuitBreaker.details.state)" "Verify that circuit breaker has status CLOSED"
to
assertEqual '"CLOSED"' "$($EXEC wget movie-composite:8080/actuator/health -qO - | jq .components.movieCircuitBreaker.details.state)" "Verify that circuit breaker has status CLOSED"
The single quotes around "CLOSED" preserve the quotation marks when passed to the assertEqual function.
Related
I have two functions in Bash. One is a generic run function, that accepts an input and evaluates it, while printing the command, and testing the exit code. This is used in a large script to ensure each command executes successfully before continuing.
The second one is a complex function, that is doing some Git history parsing. The problematic line is the only one shown.
I am calling this function from a for-loop, that iterates over a list of terms to search. The issue is that spaces are not being handled correctly, when between other words. I have tried running my script though shell-checking websites, and all of the suggestions seem to break my code.
function run() {
echo "> ${1}"
eval "${1}"
# Test exit code of the eval, and exit if non-zero
}
function searchCommitContents() {
run 'result=$(git log -S'"${1}"' --format=format:%H)'
# Do something with result, which is a list of matching SHA1 hashes for the commits
echo "${result}"
}
# Main
declare -a searchContents=('foo' 'bar' ' foo ' 'foo bar')
for i in "${searchContents[#]}"
do
searchCommitContents "${i}"
done
Here is the output I get:
> result=$(git log -Sfoo --format=format:%H)
<results>
> result=$(git log -Sbar --format=format:%H)
<results>
> result=$(git log -S foo --format=format:%H)
<results>
> result=$(git log -Sfoo bar --format=format:%H)
fatal: ambiguous argument 'bar': unknown revision of path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
I tried to add additional single and double-quotes to various areas of the code, such that the 'foo bar' string would not resolve to two different words. I also tried adding an escape to the dollar sign, like so: -s'"\${1}"' based on other questions on this site.
Why are you printing result=$(? It's an internal variable, it can be anything, there is no need for it in logs.
Print the command that you are executing, not the variable name.
run() {
echo "+ $*" >&2
"$#"
}
searchCommitContents() {
local result
result=$(run git log -s"${1}" --format=format:%H)
: do stuff to "${result}"
echo "$result"
}
issue with an input that has a space in the middle.
If you want quoted string, use printf "%q" or ${...#Q} for newer Bash, but I don't really enjoy both quoting methods and just use $*. I really like /bin/printf from GNU coreutils, but it's a separate process... while ${..#Q} is the fastest, it's (still) not enough portable for me (I have some old Bash around).
# compare
$ set -- a 'b c' d
$ echo "+ $*" >&2
+ a b c d
$ echo "+$(printf " %q" "$#")" >&2
+ a b\ \ c d
$ echo "+" "${##Q}" >&2
+ 'a' 'b c' 'd'
$ echo "+$(/bin/printf " %q" "$#")" >&2
+ a 'b c' d
See these lines:
> result=$(git log -Sfoo bar --format=format:%H)
fatal: ambiguous argument 'bar': unknown revision of path not in the working tree.
Specifically this: -Sfoo bar. It should be -S"foo bar" or -S "foo bar". Because to pass an argument with spaces, we need to quote the argument. But, each time the argument pass through a command/function layer, one layer of quote ('', "") is extracted. So, we need to nest the quote.
So in this line:
declare -a searchContents=('foo' 'bar' ' foo ' 'foo bar')
change 'foo bar' to '"foo bar"' or "'foo bar'" or "\"foo bar\"".
This is a case of 2 layers nested quotes. The more the layer, the trickier it gets. Here's an example of 4 layers quotes I once did.
Below is my bash script.
#!/bin/bash
message=$1
hostname=$2
severity=$3
eventname=$4
tagpath=$5
appname=$6
data="{"action":"EventsRouter","method":"add_event","data":[{"summary":"$message"},"device":"$hostname","message": "$message $eventname $tagpath","component":"$appname","severity":"$severity","evclasskey":"nxlog","evclass":"/nxlog/perf","monitor":"localhost"}],"type":"rpc","tid":1}"
echo "Total number of args : $#"
echo "message = $message"
echo "hostname = $hostname"
echo "appname = $appname"
echo "data = $data"
curl -u uname:password -k https://myurlcom/zport/dmd/evconsole_router -d $data
and when i try to run with sh tcp.sh value value value value value value
host:
'host,component:host,severity:host,evclasskey:nxlog,evclass:/nxlog/perf,monitor:localhost}],type:rpc,tid:1}'
is not a legal name (unexpected end of input) Total number of args : 6
message = message hostname = test appname = host data = curl: option
-d: requires parameter
I see that data has no value included.
This json has to be sent in this order for it to be accepted in the endpoint. Help me correct this.
Using jq to safely generate the desired JSON:
#!/bin/bash
parameters=(
--arg message "$1"
--arg hostname "$2"
--arg severity "$3"
--arg eventname "$4"
--arg tagpath "$5"
--arg appname "$6"
)
data=$(jq -n "${parameters[#]}" '
{action: "EventsRouter",
method: "add_event",
data: [ {summary: $message,
device: $hostname,
message: "\($message) \($eventname\) \($tagpath)",
component: $appname,
severity: $severity,
evclasskey: "nxlog",
evclass: "/nxlog/perf",
monitor: "localhost"
}
],
type: "rpc",
tid: 1
}'
curl -u uname:password -k https://myurlcom/zport/dmd/evconsole_router -d "$data"
Assuming:
message="my_message"
hostname="my_host"
severity="my_severity"
eventname="my_event"
tagpath="my_path"
appname="my_app"
If you run:
data="{"action":"EventsRouter","method":"add_event","data":[{"summary":"$message"},"device":"$hostname","message": "$message $eventname $tagpath","component":"$appname","severity":"$severity","evclasskey":"nxlog","evclass":"/nxlog/perf","monitor":"localhost"}],"type":"rpc","tid":1}"
you will get an error, because there is a not escaped white space before the string "my_event"
my_event: command not found
What happened? Since your json input has a lot of words between double quotes, you will have to enclose the whole string into single quotes, in order to preserve the double quotes inside of the string. But between single quotes, the bash variables will not be replaced by their value. So you will need to close the single quotes before each variable and reopen these again immediately after.
So that line of your script must become:
data='{"action":"EventsRouter","method":"add_event","data":[{"summary":"'$message'"},"device":"'$hostname'","message": "'$message $eventname $tagpath'","component":"'$appname'","severity":"'$severity'","evclasskey":"nxlog","evclass":"/nxlog/perf","monitor":"localhost"}],"type":"rpc","tid":1}'
If you execute:
echo "$data"
you will get:
{"action":"EventsRouter","method":"add_event","data":[{"summary":"my_message"},"device":"my_host","message": "my_message my_event my_path","component":"my_app","severity":"my_severity","evclasskey":"nxlog","evclass":"/nxlog/perf","monitor":"localhost"}],"type":"rpc","tid":1}
which is correct, I assume: the double quotes didn't disappear from your json data structure.
Platform CentOS Linux release 7.6.1810, working in bash.
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
This is an idiom I've seen recommended for parsing text in bash in general and in particular for returning multiple values from a function.
IFS=":" read A B <<< $(echo ONE:TWO)
I'm getting unexpected behaviour when I call a function, yyy in the example here
IFS=":" read Y1 Y2 <<< $(yyy)
where yyy itself also wants to do a similar call.
The effect is that that within yyy() even though I explicitly specify the IFS
IFS=":" read C1 C2 <<< $( echo "A:B" )
The fields are parsed, but both values are assigned to C1, it gets the value "A B". If the function is called in isolation it works as expected.
This is a test case, distilled down from a much larger script. I want to know what is happening with IFS here. In the failure case (the second example below) setting IFS=":" in the caller somehow cause the result fields to be aggregated. The first and third calls to yyy() below work as expected, output shown after the code.
#!/bin/bash
debug() { echo "$1" 1>&2 ; }
yyy() {
debug "in yyy"
# why are the two values assigned to A here if the caller specified IFS?
IFS=":" read A B <<< $(echo ONE:TWO)
debug "A=$A"
debug "B=$B"
echo "$A:$B"
}
# this works as expected
read Y1 Y2 <<< $(yyy)
echo -e "===\n"
# this cause the read in yyy() to aggregate
IFS=":" read Y1 Y2 <<< $(yyy)
echo -e "===\n"
# This is a workaround that enables yyy() to work correctly
# But why do I need to do this?
OUT="$(yyy)"
IFS=":" read Y1 Y2 <<< $(echo $OUT)
This is the output
in yyy
A=ONE B=TWO
===
in yyy
A=ONE TWO B=
===
in yyy
A=ONE B=TWO
Note that in the second case A gets the value ONE TWO
This seems to be a bug in bash-4.2 as discussed here, IFS incorrectly splitting herestrings in bash 4.2. Should work on the versions above that.
These are the results on the same version as you have - GNU bash, version 4.2.46(2). When I ran the function yyy in debug mode ( by setting set -x in prompt ).
++ IFS=:
++ read A B
+++ echo ONE:TWO
++ debug 'A=ONE TWO'
++ echo 'A=ONE TWO'
A=ONE TWO
++ debug B=
++ echo B=
B=
++ echo 'ONE TWO:'
The above is snippet of the output from the debug mode output. As you can see when the echo ONE:TWO is printed as a result of the command substitution, no word splitting is expected to happen because the line doesn't contain any character of the default IFS value (space/tab or a newline)
So you would expect reading the the whole string with IFS=: expected to split the string and put the values in the constituent variables A and B, but somehow the : character is lost and a string ONE TWO is stored as the first variable value.
Look at the output of the function execution in GNU bash, version 4.4.12(1) which exhibits the right behavior.
++ IFS=:
++ read A B
+++ echo ONE:TWO
++ debug A=ONE
++ echo A=ONE
A=ONE
++ debug B=TWO
++ echo B=TWO
B=TWO
++ echo ONE:TWO
There have been lot of IFS related bugs up to version 4.4.0 bash/CHANGES. So a personal recommendation is to upgrade your bash version to a more recent stable one. Also see Trying to split a string into two variables
Similar bug on version 4.4.0(1)-release
You would expect the ONE:TWO to be unmodified when the $(..) is expanded because for reasons mentioned earlier. But here too the delimit character is lost and the variable A is set to ONE TWO
IFS=":" read A B <<< $(echo ONE:TWO)
echo "$A"
ONE TWO
Surprisingly the above code works on 4.2.46(2), which means the 4.4.0(1) broke a functionality which used to work in the earlier releases.
I have a relatively simple BASH script to send mail from my Raspberry Pi. The first argument is the Subject line and the second is a string of data files to be attached.
It is basically working when I specify the message body as a file (line 6). But if I try to create a text sting containing the date as the message body it fails (line7). Here is my script:
#!/bin/bash
#echo $2
# To
TO="me#hotmail.com"
# Message
MESSAGE="output/MessageBody.txt"
MESSAGEx="Midnight `date '+%Y-%m-%d %H:%M:%S %Z'` Pi report"
echo $MESSAGE
echo $MESSAGEx
temp=$(echo $2 | tr ";" "\n")
declare -a attargs
for att in $temp; do
attargs+=( "-A" "$att" )
done
# Sending email using /bin/mail
/usr/bin/mail -s "$1" "$TO" ${attargs[#]} < $MESSAGEx
Here is the output from this command
/usr/pgms/sendtome.sh "test message" "/mnt/usbdrive/output/JSONstart.txt;/mnt/usbdrive/output/Outback_error.log;/mnt/usbdrive/output/OutbackReaderPrint.txt"
when I specify MESSAGEx as the message body:
/mnt/usbdrive/output/MessageBody.txt
Midnight 2019-08-14 07:40:31 MDT Pi report
/usr/pgms/sendtome.sh: line 22: $MESSAGEx: ambiguous redirect
If I use MESSAGE, ie the text file reference, it works.
How can it create a message body text paragraph which contains the date or some other item? Thanks....RDK
There's a number of issues here.
You should generally quote strings. Without quoting, the string after < is split (hence the error message) and the array you took so much care to collect will lose its purpose.
The thing after < needs to be the name of a file. In Bash you can use a here string <<<"$MESSAGEx" but the common and simple portable solution is to echo (or better printf) its value into a pipe.
You should prefer lower case for your private variable names, but this is mainly a stylistic recommendation. (There are reserved variables like PATH and SHELL which you really don't want to clobber; POSIX reserves upper case variable names for system use.)
Here's a refactoring which attempts to address these concerns.
#!/bin/bash
to="me#hotmail.com"
# Message
#msgfile="output/MessageBody.txt"
msgbody="Midnight `date '+%Y-%m-%d %H:%M:%S %Z'` Pi report"
#echo "$msgfile"
#echo "$msgbody"
declare -a attargs
for att in $(echo "$2" | tr ";" "\n"); do
attargs+=( "-A" "$att" )
done
/usr/bin/mail -s "$1" "${attargs[#]}" "$to"<<< "$msgbody"
Perhaps a better design would be to just shift the first argument and then use "$#" as the list of files to attach.
I want to create a data structure Person like a Map and want to pass it to a function in bash script. In the method I want to retrieve “Person” like Person[Name] ,Person[Age] , Person [Dept] as Mark , 10 and Finance respectively. etc. But I am not able to get and getting the output as mentioned in the comment. Need some guidance here how to that or what I am doing wrong.
Here is the script
#!/bin/bash -e
getValue(){
local Person=$1
echo Person[Name]
}
Person[Name]=”Mark”
Person [Age]=”10”
Person [Dept]=”Finance”
echo ${Person[Name]} # why is it printing Finance.I am expecting it to be printed as Mark
getValue Person # output is coming as Person
getValue ${Person} # output is coming as Finance
getValue ${Person[#]} # output is coming as Finance
You have to define Person as an associative array.
Here is the running code if you are using bash version 4 or above.
#!/bin/bash -e
function getValue(){
person=$(declare -p "$1")
declare -A person_arr=${person#*=}
echo ${person_arr[Name]}
echo ${person_arr[Age]}
echo ${person_arr[Dept]}
}
declare -A Person
Person[Name]="X"
Person[Age]=10
Person[Dept]="Finance"
echo ${Person[Name]}
echo ${Person[Age]}
echo ${Person[Dept]}
getValue "Person"