Why does the script launched through Crontab behave differently? - bash

I have .sh script in UbuntuServer (VM):
response=$(curl -H 'Accept: application/vnd.twitchtv.v5+json' -H 'Authorization: OAuth 9f65dd6onr07vhpdqblbiix5rl0tch' -X GET 'https://api.twitch.tv/kraken/streams/127060528' | jq -r '.stream');
now=$(date +"%Y-%m-%d %T");
echo "$now" >> log.txt;
echo "$response" >> log.txt;
if [[ "$response" == "null" ]]
then
echo "ZERO"
else
streamlink -o "dump/stream_$now.mp3" twitch.tv/mahetirecords/clip/FreezingEncouragingCougarSuperVinlin audio
echo "STREAM"
fi
When I run the script through the bash, the THEN-way is triggered.
When I run the script through crontab, the ELSE-way is triggered.
WHY?
Crontab -e:
* * * * * /home/chesterlife/twitch-interceptor/script.sh
If the stream is offline, then "$response" return null. This is text-null because var=""; if [ "$var" == "$response" ]; then echo "true"; else echo "false"; fi return false
Any ideas?

By default crontab executes using sh shell. You can change it to bash using the information from the link below:
https://unix.stackexchange.com/questions/94456/how-to-change-cron-shell-sh-to-bash
Also read this Stack Overflow question about the difference between [ and [[ in Bash
While [ is POSIX, [[ is only supported by some shells including bash.

You're missing a shebang in the script beginning, eg:
#!/usr/bin/env bash
Also, you should add your current user's path (obtained via echo $PATH) to your script, so you're running in a sane environnment, identical to your user's.

Related

Best way for conditionally (and verbosely) run a command in bash

I have written a shell script (bash) which runs some commands. It has an option to not to run the commands but to echo them to the screen. By default, the output of these commands is redirected to /dev/null but there is another option to show the output on the screen.
I use a function to check for the value of these variables and run the commands or simulate them:
runmaybe() {
if [[ true = $dry_run ]]; then
echo "Simulating '$#'"
else
if [[ true = $verbose ]]; then
$#
else
$# > /dev/null
fi
fi
}
The function is working but I had some issues with complex commands such as:
runmaybe eval svn cp $url $root/tags/$ntag -m \"Tagging revision with $ntag\"
I had to add the eval to prevent wordsplitting so svn gets the right value for the -m option.
I have some other complex commands in that script such as:
runmaybe vzctl exec 1 "( cd /var/wwww/vhosts/myhost ; php cron.php )"
runmaybe ssh -t user#$host "vzctl exec $vmid \"( /usr/local/bin/myscript )\"" 2>/dev/null
runmaybe rsync --delete --exclude=\"**/.svn/\" --exclude=\"**/.git/\" --include=*.exe --numeric-ids -a $vOpt -H $LOCAL_VM$dir $host:$REMOTE_VM$dir
Although the script is working right now, I wonder if there is a better way of doing this task.
The problem is in unquoted expansion of $#. As a rule of a thumb, if you see $, you should put it inside ". Unquoted expansions undergo word splitting and filename expansions - to prevent them, quote the expansion.
I had to add the eval
eval is evil. Do not use it.
runmaybe() {
if [[ true = $dry_run ]]; then
echo "Simulating '$*'"
# or better quote with printf in some corner cases
printf "Simulating:"
printf " %q" "$#"
printf "\n"
elif [[ true = $verbose ]]; then
"$#"
else
"$#" > /dev/null
fi
}
runmaybe svn cp "$url" "$root/tags/$ntag" -m "Tagging revision with $ntag"
runmaybe rsync --delete --exclude="**/.svn/" --exclude="**/.git/" --include="*.exe" --numeric-ids -a "$vOpt" -H "$LOCAL_VM$dir" "$host:$REMOTE_VM$dir"

Bash script into crontab behaving differently

I'm having trouble in finding the reason why my BASH script behaves differently if run alone or inside cron. I have this snippet:
#!/bin/bash
RESPONSE=$(curl -fs -XPOST -H "Content-type: application/json" -d '{"id" : 4}' https://myserver.com)
echo $RESPONSE
if [ -z "$RESPONSE" ]; then
echo "empty response"
return 0
fi
COMMAND=$(echo $RESPONSE | python -c "import sys, json; print json.load(sys.stdin)['command']")
if [ -z "$COMMAND" ]; then
echo "empty command"
elif [ "$COMMAND" = "SYS_INFO" ];
then
#business logic
fi
that prints two different responses in the two environments:
$RESPONSE Running from console:
{"id":"1f78d8d0-e754-4a23-a2f0-448fbeb42995", "key":"\n4RHDFAnTull1Z+aHGbO1zXcAGghuaEUz0w8sT7dlpc80jG6ZaWnbDox4G0f8sKY\ng0WZ80zWf8ftNgX3nes9MWYEq00nM5jJWCSavmGSKCKjoGD2XqBod8W0Z5w/KAHTSitGVMFgMjda91+xozw8uMlzR/t3Y8FP2k/NHj\n"}
$RESPONSE Running from :
{"id":"1f78d8d0-e754-4a23-a2f0-448fbeb42995", "key":"
4RHDFAnTull1Z+aHGbO1zXcAGghuaEUz0w8sT7dlpc80jG6ZaWnbDox4G0f8sKYj
g0WZ80zWf8ftNgX3nes9MWYEq00nM5jJWCSavmGSKCKjoGD2XqBod8W0Z5w/KAHTSitGVMFgMjda91+xozw8uMlzR/t3Y8FP2k/NHj
"}
Please notice the \n that the server returns into key field that are present when running from console and NOT present (actually, they are encoded as newline) when running from crontab
What I've tried:
adding source ~/.bashrc as suggested here
changing value of PATH evaluating the differences of the two environments as suggested here
However, nothing seems to work.

Crontab will not execute .sh but crontab will execute a command

This issue is currently driving me nuts.
I setup a crontab with sudo crontab -e
The contents are 1 * * * * /home/bolte/bin/touchtest.sh
The contents of that file are:
#!/bin/bash
touch /home/bolte/bin/test.log
It creates the file. But the below script will not run.
#!/bin/bash
# CHANGE THESE
auth_email="11111111#live.co.uk"
auth_key="11111111111111111" # found in cloudflare
account settings
zone_name="11111.io"
record_name="11111.bolte.io"
# MAYBE CHANGE THESE
ip=$(curl -s http://ipv4.icanhazip.com)
ip_file="/home/bolte/ip.txt"
id_file="/home/bolte/cloudflare.ids"
log_file="/home/bolte/cloudflare.log"
# LOGGER
log() {
if [ "$1" ]; then
echo -e "[$(date)] - $1" >> $log_file
fi
}
# SCRIPT START
log "Check Initiated"
if [ -f $ip_file ]; then
old_ip=$(cat $ip_file)
if [ $ip == $old_ip ]; then
echo "IP has not changed."
exit 0
fi
fi
if [ -f $id_file ] && [ $(wc -l $id_file | cut -d " " -f 1) == 2 ]; then
zone_identifier=$(head -1 $id_file)
record_identifier=$(tail -1 $id_file)
else
zone_identifier=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$zone_name" -H "X-Auth-E$
record_identifier=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_record$
echo "$zone_identifier" > $id_file
echo "$record_identifier" >> $id_file
fi
update=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$zone_identifier/dns_records/$record_ident$
[ Read 55 lines (Warning: No write permission) ]
^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos ^Y Prev Page
^X Exit ^R Read File ^\ Replace ^U Uncut Text ^T To Linter ^_ Go To Line ^V Next Page
I've been trying to troubleshoot why this code will not run every minute, there doesn't seem to be any output in the same folder as the script, which is located at /home/bolte/cloudflare-update-record.sh
Ok so the answer to this was, I was editing crontab with sudo, and the files were located in my users home folder. This is why they weren't working. Resolved my own issue.
If you have this issue just use $ crontab -e rather than sudo crontab -e, and specify full paths for your file outputs, unless you are putting the proper path variables in your script.

Bash sub script redirects input to /dev/null mistakenly

I'm working on a script to automate the creation of a .gitconfig file.
This is my main script that calls a function which in turn execute another file.
dotfile.sh
COMMAND_NAME=$1
shift
ARG_NAME=$#
set +a
fail() {
echo "";
printf "\r[${RED}FAIL${RESET}] $1\n";
echo "";
exit 1;
}
set -a
sub_setup() {
info "This may overwrite existing files in your computer. Are you sure? (y/n)";
read -p "" -n 1;
echo "";
if [[ $REPLY =~ ^[Yy]$ ]]; then
for ARG in $ARG_NAME; do
local SCRIPT="~/dotfiles/setup/${ARG}.sh";
[ -f "$SCRIPT" ] && echo "Applying '$ARG'" && . "$SCRIPT" || fail "Unable to find script '$ARG'";
done;
fi;
}
case $COMMAND_NAME in
"" | "-h" | "--help")
sub_help;
;;
*)
CMD=${COMMAND_NAME/*-/}
sub_${CMD} $ARG_NAME 2> /dev/null;
if [ $? = 127 ]; then
fail "'$CMD' is not a known command or has errors.";
fi;
;;
esac;
git.sh
git_config() {
if [ ! -f "~/dotfiles/git/gitconfig_template" ]; then
fail "No gitconfig_template file found in ~/dotfiles/git/";
elif [ -f "~/dotfiles/.gitconfig" ]; then
fail ".gitconfig already exists. Delete the file and retry.";
else
echo "Setting up .gitconfig";
GIT_CREDENTIAL="cache"
[ "$(uname -s)" == "Darwin" ] && GIT_CREDENTIAL="osxkeychain";
user " - What is your GitHub author name?";
read -e GIT_AUTHORNAME;
user " - What is your GitHub author email?";
read -e GIT_AUTHOREMAIL;
user " - What is your GitHub username?";
read -e GIT_USERNAME;
if sed -e "s/AUTHORNAME/$GIT_AUTHORNAME/g" \
-e "s/AUTHOREMAIL/$GIT_AUTHOREMAIL/g" \
-e "s/USERNAME/$GIT_USERNAME/g" \
-e "s/GIT_CREDENTIAL_HELPER/$GIT_CREDENTIAL/g" \
"~/dotfiles/git/gitconfig_template" > "~/dotfiles/.gitconfig"; then
success ".gitconfig has been setup";
else
fail ".gitconfig has not been setup";
fi;
fi;
}
git_config
In the console
$ ./dotfile.sh --setup git
[ ?? ] This may overwrite existing files in your computer. Are you sure? (y/n)
y
Applying 'git'
Setting up .gitconfig
[ .. ] - What is your GitHub author name?
Then I cannot see what I'm typing...
At the bottom of dotfile.sh, I redirect any error that occurs during my function call to /dev/null. But I should normally see what I'm typing. If I remove 2> /dev/null from this line sub_${CMD} $ARG_NAME 2> /dev/null;, it works!! But I don't understand why.
I need this line to prevent my script to echo an error in case my command doesn't exists. I only want my own message.
e.g.
$ ./dotfile --blahblah
./dotfiles: line 153: sub_blahblah: command not found
[FAIL] 'blahblah' is not a known command or has errors
I really don't understand why the input in my sub script is redirected to /dev/null as I mentioned only stderr to be redirected to /dev/null.
Thanks
Do you need the -e option in your read statements?
I did a quick test in an interactive shell. The following command does not echo characters :
read -e TEST 2>/dev/null
The following does echo the characters
read TEST 2>/dev/null

Bash programming with filesystem functions

I have been busy this week trying to wrap my head around a little Bash program to migrate a CMS from one server to another. The reasopn for this is because I have more tha 40 of these to do, and need to get it done in a timely manner, thus the Bash idea.
Needless to say, I have run into a couple of problems so far, but one of them has halted my development completetly, directory checking.
No I have tried a couple of methods and none of them seem to work really. The catch is that I have to check the folder on a remote server via ssh. Here my example:
ExSshRsa=~/.ssh/id_rsa
ExSshPort=22
ExSshHost=localhost
ExRoot=/var/www/
echo -n "Verifying Root access $ExRoot..."
SSHRoot='ssh -i $ExSshRsa -p $ExSshPort $ExSshHost [ -d $ExRoot ] || exit 1 '
echo $SSHRoot
if [ "$SSHRoot" -eq 0 ]
then
echo "OK"
else
echo "FAIL"
fi
I get the Error: [: : integer expression expected
Does the [ or test not resturn a 0 which is numerical. ?
Passing strings as arguments to a remote host is not trivial; you need to use arrays. A test example:
declare -a cmd=(touch "file name with spaces")
printf -v escaped_cmd_str '%q ' "${cmd[#]}"
ssh localhost $escaped_cmd
ssh localhost ls # Should return "file name with spaces" on a separate line
So your case should be:
ExSshRsa=~/.ssh/id_rsa
ExSshPort=22
ExSshHost=localhost
ExRoot=/var/www/
echo -n "Verifying Root access $ExRoot..."
declare -a cmd=( '[' -d "$ExRoot" ']' ) # Need to quote "[" since it's a Bash-specific symbol
printf -v escaped_cmd_str '%q ' "${cmd[#]}"
if ssh -i "$ExSshRsa" -p "$ExSshPort" "$ExSshHost" $escaped_cmd
then
echo "OK"
else
echo "FAIL"
fi
This is a rare case where using unquoted variable expansion is perfectly fine.
change the shebang to #!/bin/bash -x and look at the output...
you are storing a string in variable SSHRoot using single quotes, meaning that no variables will be expanded, i.e. a $ is still a $. Use double quotes instead, i.e. "
to store the output from a command in bash, use
var=$(cmd)
the exist status of a command is stored in the variable $?. Do a check on that after the ssh-command
you are never executing the ssh-command in your code
Great link here for bash-programming
Try the following:
ExSshRsa=~/.ssh/id_rsa
ExSshPort=22
ExSshHost=localhost
ExRoot=/var/www/
echo -n "Verifying Root access $ExRoot..."
cmd="bash -c \"[ -d $ExRoot ] || exit 1\""
SSHRoot="ssh -i $ExSshRsa -p $ExSshPort $ExSshHost ${cmd}"
$SSHRoot
if [ $? -eq 0 ]
then
echo "OK"
else
echo "FAIL"
fi
The variables weren't being replaced in your SSHRoot variable as it's in single quotes. Also, you weren't passing an executable command, so that's why I use bash -c above. It will run the bash commands inside the quoted string.
$? stores the exit value of the last command, in this case the SSHRoot one.
#!/bin/bash
ExSshRsa=~/.ssh/id_rsa
ExSshPort=22
ExSshHost=localhost
ExBase='/tmp/'
ExRoot='one space/'
declare -a AExRoot
for argR in "${ExRoot[#]}"
do
ExRoot+=($(printf %q "$argR"))
done
clear
FRoot=( $ExBase${ExRoot[#]} )
echo -n "Verifying Root access $FRoot..."
SSHRootTest="bash -c \"[ -d $FRoot ] && echo 0 && exit 0 || echo 1 && exit 1\""
SSHRoot=$( ssh -i $ExSshRsa -p $ExSshPort $ExSshHost ${SSHRootTest})
if [ $? -eq 0 ]
then
echo -en "\e[1;32mOK\e[0;37;m..."
else
echo -en "\e[1;31mFAIL\e[0;37;m..."
fi
sleep 1
if [ -w $FRoot ]
then
echo -e "\e[1;32mwritable\e[0;37;m"
else
echo -e "\e[1;31mNOT writeable\e[0;37;m"
fi
echo -e "\e[0;m"
exit 0
So I have incorporated all of the suggestions so far and have one last problem, the FRoot is not getting populated by the complete array values. Other than that I think it now has the subjective approach as suggested #john-keyes, the proper expansion #frederik and the crazy space escapes #l0b0

Resources