I want to write a command so it runs using a randomly generated uuid each time, interpolating this variable into the command.
I'm in MacOS, so I can use the uuidgen command
❯ uuidgen
E4BC7525-1EC7-4338-8F4F-8DA268CB7051
I want to interpolate this value as part of my command like that:
send-test-sqs-message: ## Send test sqs message to generate an email
aws sqs send-message "{\"identifier\":$(uuidgen), ..." --output table | cat
But the output when running make send-test-sqs-message shows that "identifier" is empty.
Running the command with set -x provides this extra output which doesn't look relevant:
+_p9k_preexec1:1> _p9k_restore_special_params +_p9k_restore_special_params:1> (( ! 0 )) +_p9k_restore_special_params:7> (( ! 0 )) +_p9k_restore_special_params:11> (( ! 0 )) +_p9k_preexec1:2> unset __p9k_trapint +_p9k_preexec1:3> trap - INT +omz_termsupport_preexec:1> [[ '' != true ]]
How can this be fixed?
Related
I'm trying to run a shell command (currently either sh or bash) which connects to a database. Because the database is still 'warming up', the command fails.
So I was trying to do a loop (let's say ... 100 tries) and each time the command fails, wait 1 second and retry.
If there's an error, this is the start of the string that is dumped to stdout: Sqlcmd: Error: <snipped>
Here's what I've been trying:
for i in $(seq 1 100)
do
X='/opt/mssql-tools/bin/sqlcmd -E -S localhost -Q "<some sql statement> "'
if [[ $X == Sqlcmd: Error:* ]]
echo "."
then
break
fi
done
It's not working as I figure out the string comparison stuff with shell/bash ... but was more making sure if I was on the right track etc.
You could try something like:
while true ; do
if Sqlcmd xxx xxx xxx ; then break ; fi
# or:
Sqlcmd xx xxx xxx && break
sleep 1
done
You can also add a counter:
for ((n=100;n>0;n--)) ; do
Sqlcmd xxx xxx xxx
if [[ $? == 0 ]] ; then
break
fi
sleep 1
done
[[ $n == 0 ]] && echo Timeout && exit 1
I'm showing two different ways of testing the return value here, but the first one is preferred (if cmd ; then ... ; fi).
$? is the return value from the last command, which is 0 when it completed successfully. If it returns 0 even in case of error (which can happen for malformed programs), you can test the output with grep:
Sqlcmd xxx xxx 2>&1 | grep <error pattern> > /dev/null
if [[ $? != 0 ]] ; then break ; fi
Here we test $? != 0 because grep will return 0 when the error pattern has been found.
If you want to get the output result into a variable, run the command with X=$(Sqlcmd xxx xxx). Then you can use bash string comparison:
X=$(Sqlcmd xxx xxx)
if [[ "$X" =~ .*error.* ]] ; then
<handle error here>
fi
Note bash can match regexp, which makes it really handy at checking error types.
You can also use a switch/case construct:
case "$X" in
*Error:*) echo " Error detected " ;;
*) break ;;
esac
(Note the double ;;)
I ended up learning all the clues from #matthieu's post. This is what I ended up doing:
for i in $(seq 1 30)
do
/opt/mssql-tools/bin/sqlcmd -U sa -P <snip> -S localhost -Q "USE Master" 2>&1
if [[ $? != 0 ]]
then
# Failed
echo "."
sleep 1s
else
# worked!
break
fi
done
breakdown for those learning (like me)
execute a sql query using the sqlcmd command. Any errors via stderr (that's the 2 in 2>&1) will be redirected to the console stdout (that's the $1). REF: 2>&1 shell idiom.
the result status code is sent to $? (REF: what is bash dollar questionmark ?)
if it failed(any value that is NOT a zero), then sleep 1 sec and we'll try. Only re-try 30 times, though.
if we worked (value is zero), then stop trying and go on....
So there we have it! shell/bash shell 101 stuff. good luck!
I established a connection with a BLE device using gatttool. First I connected to the device with sudo gatttool -t random -b FF:3C:8F:22:C9:C8 -I and connect. After that I read the value of specific characteristic with char-read-uuid 2d30c082-f39f-4ce6-923f-3484ea480596.
What I want to do is to automate the whole process and put the latter command (querying for value) in the loop, ideally saving each value (appending) to a text file. I tried something like
sudo gatttool -t random -b FF:3C:8F:22:C9:C8 -I <<EOF
connect
while[ 1 ]; do
char-read-uuid 2d30c082-f39f-4ce6-923f-3484ea480596 > output.txt
done
exit 1
EOF
but it does not help, since I am not even able to connect to the device (ideally there should be some delay between the first and the second command). Also after connecting, an interactive mode is enabled and the shell commands do not work there. I'd appreciate any clues on how to tackle this issue.
If gattool writes prompts to stdout (and doesn't suppress them given non-TTY file descriptors), consider something like:
#!/usr/bin/env bash
case $BASH_VERSION in ''|[123].*|4.0.*) echo "ERROR: bash 4.1 or newer required" >&2; exit 1;; esac
exec {output_fd}>output.txt
prompt_re='[>] '
capture_re='^handle:.*value:.*$'
wait_for_prompt() {
IFS= read -r line || return
while ! [[ $line =~ $prompt_re ]]; do
[[ $line =~ $capture_re ]] && printf '%s\n' "$line" >&$output_fd
IFS= read -r line || return
done
}
wait_for_prompt
echo connect
while wait_for_prompt; do
echo "char-read-uuid 2d30c082-f39f-4ce6-923f-3484ea480596"
done
...saved as yourscript, and invoked using socat as:
socat 'SYSTEM:sudo gatttool -t random -b FF:3C:8F:22:C9:C8 -I 2>&1' 'EXEC:./yourscript'
(assuming that sudo is configured to work without a TTY; otherwise, you might move it to be sudo socat).
Indeed, pexpect works fine here. You can find my solution below. The code reads the value of the specific UUID, which contains IMU readings (floats).
import pexpect
import struct
import time
import sys
IMU_MAC_ADDRESS = "FF:3C:8F:22:C9:C8"
UUID_DATA = "2d30c082-f39f-4ce6-923f-3484ea480596"
if __name__ == '__main__':
gatt = pexpect.spawn("gatttool -t random -b " + IMU_MAC_ADDRESS + " -I")
gatt.sendline("connect")
gatt.expect("Connection successful")
while(True):
gatt.sendline("char-read-uuid " + UUID_DATA)
gatt.expect("handle: 0x0011 value: ")
gatt.expect(" \r\n")
data = (gatt.before).decode('UTF-8').replace(" ", "").decode('hex')
print(struct.unpack('f', data)[0]
With the bash script below I am getting an error;
line 16: [: : integer expression expected but I can't figure out why? The error lies in if [ "$(log_date)" -le "$days_in_ms" ]; then Can anyone help and explain as BASH if statements confuse me
#!/bin/bash
region="eu-west-1"
retention_days="28"
days_in_ms="$(date +%s%3N --date=-"$retention_days"+days)"
log_date () {
aws logs describe-log-streams --region $region --log-group-name "$groups" | jq -r '.logStreams[].lastEventTimestamp' > /dev/null 2>&1
}
log_name () {
aws logs describe-log-streams --region $region --log-group-name "$groups" | jq -r '.logStreams[].logStreamName' > /dev/null 2>&1
}
mapfile -t logGroups < <(aws logs describe-log-groups --region $region | jq -r '.logGroups[].logGroupName') > /dev/null 2>&1
for groups in "${logGroups[#]}"; do
if [ "$(log_date)" -le "$days_in_ms" ]; then
log_name
fi
done
What's Happening
Note the details of the error message:
[: : integer expression expected
See the blank before the second :? That's where the value that [ is trying to operate on would be, if there were one. It's blank, meaning something [ was told would be a number is not there at all.
To provide an example of how this works:
$ [ foo -ge 1 ]
-bash: [: foo: integer expression expected
Note that the foo is given in the same position where you have a blank. Thus, a blank value is being parsed as a number.
Why It's Happening
Your log_date function does not return any output, because of its >/dev/null. Consequently, your code tries to parse the empty string it returns as a number, and (correctly) complains that it's getting an empty string in a position where an integer is expected.
In the future, run set -x to enable logging of each command before it's run, or invoke your script with bash -x yourscript when debugging, to identify these issues.
I am creating a simple bash script that would routinely check mdadm and report back to CloudWatch with numeric values:
#!/bin/bash
## CHECKING RAID STATUS AND SUBMITTING RESULTS TO CLOUDWATCH ##
## 0 = Good, 1 = Bad, 2 = Still bad, needs investigating ##
HECK_RAID=`mdadm -D /dev/md1 | grep "State : [a-zA-Z]"`
SEND_RESPONSE=`aws cloudwatch put-metric-data --metric-name RAID-STATUS --namespace MONGODB --value "$STATUS" --dimensions InstanceID="$INSTANCEID" --region us-east-1`
INSTANCEID=$(wget -q -O - http://169.254.169.254/latest/meta-data/instance-id)
if [[ $CHECK_RAID =~ [Cc]lean ]]; then
STATUS=0
$SEND_RESPONSE
elif [[ $CHECK_RAID =~ [Ff]ailed ]]; then
STATUS=1
$SEND_RESPONSE
else
STATUS=2
$SEND_RESPONSE
fi
Problem is, when I run the script to test, I get this:
Invalid value ('') for param element of list:MetricData of type list
I know STATUS might be a culprit but is there a better way of creating the conditions I have made and actually submitting the results back to CloudWatch?
You are setting the variable "HECK_RAID" instead of "CHECK_RAID".
I am currently learning bash programming and dont really understand why the passing argument for me is not working.
i have a script like this
#!/bin/bash
# the following environment variables must be set before running this script
# SIM_DIR name of directory containing armsim
# TEST_DIR name of the directory containing this script and the expected outputs
# LOG_DIR name of the directory that your output is written to by the run_test2 script
# ARMSIM_VERBOSE set to "-v" for verbose logging or leave unset
# First check the environment variables are set
giveup=0
if [[ ${#SIM_DIR} -eq 0 || ${#TEST_DIR} -eq 0 || ${#LOG_DIR} -eq 0 ]] ; then
echo One or more of the following environment variables must be set:
echo SIM_DIR, TEST_DIR, LOG_DIR
giveup=1
fi
# Now check the verbose flag
if [[ ${#ARMSIM_VERBOSE} != 0 && "x${ARMSIM_VERBOSE}" != "x-v" ]] ; then
echo ARMSIM_VERBOSE must be unset, empty or set to -v
giveup=1
fi
# Stop if environment is not set up
if [ ${giveup} -eq 1 ] ; then
exit 0
fi
cd ${TEST_DIR}
for i in test2-*.sh; do
echo "**** Running test ${i%.sh} *****"
./$i > ${LOG_DIR}/${i%.sh}.log
done
When I run the .sh file and pass in 3 example argument as below:-
$ ./run_test2 SIM_DIR TEST_DIR LOG_DIR
It still show: One or more of the following environment variables must be set:
SIM_DIR, TEST_DIR, LOG_DIR
Can anyone guide me on this? Thank you.
That's not how it's intended to work. The environment variables must be set beforehand either in the script or in the terminal like
export SIM_DIR=/home/someone/simulations
export TEST_DIR=/home/someone/tests
export LOG_DIR=/home/someone/logs
./run_test2
If you use these variables frequently, you might want to export them in ~/.bashrc. The syntax is identical to the exports in the above example.
Environment variables aren't really arguments in the sense I understand from your question/example. It sounds to me like you want to give arguments to a function/script, if you do that you can find your arguments in $1-9 (I think bash supports even more, unsure), the number of arguments are stored in $#
Example function that expects two arguments:
my_func() {
if [ $# -ne 2 ]; then
printf "You need to give 2 arguments\n"
return
fi
printf "Your first argument: %s\n" "$1"
printf "Your second argument: $s\n" "$2"
}
# Call the functionl like this
my_func arg1 arg2