Shellscript throws errors but somehow is still working - shell

I have this shellscrip to deploy code from travis on another machine.
#!/bin/sh
codecov
function sshDeploy {
printf -v __ %q "$1"
ssh -oStrictHostKeyChecking=no deployuser#178.62.252.23 "cd api; git pull origin master; git checkout $__;./sbt clean; ./sbt stage; ./neeedo restart; exit $?"
}
sshDeploy $TRAVIS_COMMIT
exit $?
This script runs without any errors on my macbook. However when I run it on travis(unfortunately I can't tell you which unix they run on their buildagents because I dont have direct access to them) I observe strange behaviour.
Errors are thrown but the script is somehow still being executed.
./after-success.sh: 3: ./after-success.sh: function: not found
./after-success.sh: 4: printf: Illegal option -v
Warning: Permanently added '178.62.252.23' (ECDSA) to the list of known hosts.
From https://github.com/HTW-Projekt-2014-Commercetools/api
* branch master -> FETCH_HEAD
... Git Log ...
Stopping Neeedo-API: ..done.
Starting Neeedo-API: ....done.
./after-success.sh: 6: ./after-success.sh: Syntax error: "}" unexpected
Now I have 2 problems/question:
Why are these errors thrown and how can I make this shellscript as plattform independent as possible? I dont know which buildagent is running my script so it should run on the common unix systems.
How can I make sure that the script returns an errorcode different than 0 if something fails to make sure that the travis build fails. At the moment its still returning a green build and also the app is started somehow as you can see in the logs...
(3.) Is there a way to format the commands that are sent through ssh in a better way than in a long string?

Change it to a bash script.
First line: #!/bin/bash.
sh
$ printf -v ___ %q "$1"
sh: 1: printf: Illegal option -v
bash
$ printf -v ___ %q "$1"
<no error>

The errors are thrown because the function keyword and the -v argument to printf aren't being understood by /bin/sh on the remote host.
Changing the shebang line to #!/bin/bash will fix those errors.
The reason the script still "works" even with the above errors (quotes because it isn't really working I don't think) is because you got lucky. /bin/sh didn't understand the function keyword so it ignored that line entirely which then meant that it likely ran the ssh command directly (not through the function call) and didn't use $__ correctly (because of the printf -v error) so didn't run the correct git checkout command.
I would have expected to see an error about not knowing what the sshDeploy command was but maybe it didn't do that or maybe it got hidden by something else.
That all being said there's no real reason for that function at all.
The exit status from a script is the exit status of the last command that ran so there's no need to end with exit $?.
With that and without the function your script just becomes.
codecov
printf -v __ %q "$TRAVIS_COMMIT"
ssh -oStrictHostKeyChecking=no deployuser#178.62.252.23 "cd api; git pull origin master; git checkout $__;./sbt clean; ./sbt stage; ./neeedo restart"
And you can even avoid the printf -v most likely since git refs are unlikely (if not incapable) of containing any shell metacharacters that would need escaping when expanded in a shell string like that (I believe at least). Though this is certainly safer if you don't mind the bash requirement.

Related

How to execute subcommand in makefile

Here is my makefile (or at least the relevant part):
build-frontend:
cd frontend; printf '{"tag":"%s"}\n' $(git describe --tags) > VERSION.json
other-commands
When I execute this command from shell it works fine but when I do make build-frontend,
it shows me that make executes following command:
cd frontend; printf '{"tag":"%s"}\n' > VERSION.json
It looks like the subcommand is executed before cd frontend,
but even then it seems weird since in that case it should return version of the deployment script and it returns nothing.
What am I doing wrong?
Keep in mind that $() is how make does variable subsitution. You need to escape the $, like this.
build-frontend:
cd frontend; printf '{"tag":"%s"}\n' $$(git describe --tags) > VERSION.json
other-commands

Syntax error near unexpected token `;' when running a command in Bash

In Bash in the terminal, it is impossible to run a command that ends with and & (being sent to the background) followed by another command (obviously with a ; between them). Why is that? Why can't you run anything with &; or $ ; in it?
I'm trying to recreate a 502 error and was trying to DoS a specific page in a testing server. I was trying to run this:
while true; do curl -s https://some.site.com/someImage.jpg > /dev/null &; echo blah ; done
as a "one-liner" in the terminal. However, I got this error:
-bash: syntax error near unexpected token `;'
However the commands work individually, and when I run the curl command not in the background it works as a loop as well. It also works when it write a one line script, "/tmp/curlBack.sh" that includes only
curl -s https://some.site.com/someImage.jpg > /dev/null &
And then run
while true; do bash /tmp/curlBack.sh ; echo blah ; done
Why am I getting this error and how do I fix it?
The problem is with the semi-colon after the ampersand (&):
An ampersand does the same thing as a semicolon or newline in that it indicates the end of a command, but it causes Bash to execute the command asynchronously.
BashSheet
Basically, it's as if you were to put double semi-colons back to back, that would cause a syntax error too.
To fix this problem, you simply need to remove the semi-colon, I tested it that way and it seems to be working:
while true; do curl -s https://some.site.com/someImage.jpg > /dev/null & echo blah ; done

Save all 'ssh' commands to a file

I log into a lot of servers via ssh. Typically I just use the easy bash history search to scroll through my history of 'ssh' commands and find the one I want. However, eventually my .bash_history reaches its limit and I start losing entries, despite increasing the limits, etc..
I would rather just adjust my $PROMPT_COMMAND so that after every command, it checks to see if I ran a command with ssh, and if so, appends that command to a file somewhere.
I saw some relevant questions asked here and here but I am struggling to understand how to use these in a function that I can source from my .bashrc and add to my $PROMPT_COMMAND that will check if the last command entered started with ssh and copy it to a file.
For example, this does not work:
$ PROMPT_COMMAND="echo; foo; "
$ foo () { echo "command was: $BASH_COMMAND" ; }
$ ssh cn-0030
...(Ctrl-D)...
$ logout
Connection to cn-0030 closed.
command was: echo "command was: $BASH_COMMAND"
This also does not work:
$ foo () { echo !! | grep ssh --color ; }
Because !! gets expanded into the actual last command run immediately and then saved into foo, instead of being evaluated when foo is evaluated

A command line that works in a prompt and not in a script

Here is the exact command
bcftools norm -f /path/hg19/ucsc.hg19.fasta -c s ./user1.vcf -o ../fixed/user2.vcf
When I run it in the shell directly it works fine.
When i put it into a bash script it fails
The error message comes from bcftools itself
[main] Unrecognized command.
Script is encoded in ascii:
#!/bin/bash
bcftools norm -f /path/hg19/ucsc.hg19.fasta -c s ./user1.vcf -o ../fixed/user2.vcf
So bcftools accept argument when received directly from prompt but not within the script. It is like spaces from prompt and from script are not interpreted the same way
Is that the full error message? Usually after 'Unrecognised command' it gives which command is unrecognised. Looking at the C source:
fprintf(stderr, "[E::%s] unrecognized command '%s'\n", __func__, argv[1]);
So there should be something inside single quotes - the argv[1] in the code.
The most common reason in web chatter for this message is that certain commands are not available in early versions of bcftools. So, do you have more than one version of bcftools installed?
Comment (above) by the OP confirms that an alias referred to an earlier version.

bash script + rsync: bash won't sync to host?

I've only been writing actual .sh scripts since sometime this morning, and I'm a bit stuck. I'm trying to write a script to check to see if a process is running, and to start it if it isn't. (I plan to run this script once every 10 to 15 minutes with cron.)
Here's what I have so far:
#!/bin/bash
APPCHK=$(ps aux | grep -c "/usr/bin/rsync -rvz -e ssh /home/e-smith/files/ibays/drive-i/files/Warehouse\ Pics/organized_pics imgserv#192.168.0.140:~/webapps/pavlick_container/public/images
")
RUNSYNC=$(rsync -rvz -e ssh /home/e-smith/files/ibays/drive-i/files/Warehouse\ Pics/organized_pics imgserv#192.168.0.140:~/webapps/pavlick_container/public/images)
if [ $APPCHK < '2' ];
then
$RUNSYNC
fi
exit
Here's the error that I'm getting:
$ ./image_sync.sh
rsync: mkdir "/home/i/webapps/pavlick_container/public/images" failed: No such file or directory (2)
rsync error: error in file IO (code 11) at main.c(595) [Receiver=3.0.7]
rsync: connection unexpectedly closed (9 bytes received so far) [sender]
rsync error: error in rsync protocol data stream (code 12) at io.c(601) [sender=3.0.7]
./image_sync.sh: line 8: 2: No such file or directory
TRTWF is that
rsync -rvz -e ssh /home/e-smith/files/ibays/drive-i/files/Warehouse\ Pics/organized_pics imgserv#192.168.0.140:~/webapps/pavlick_container/public/images
runs just fine from a terminal window.
What am I doing wrong?
Your grep call is wrong on two counts. The pattern shouldn't include a newline. To look for an exact string, use grep -F 'substring' or grep -xF 'exact whole line'.
Finding if a process is running with ps | grep is highly brittle. On most unices (at least Solaris, Linux and *BSD), use pgrep: pgrep -f 'PATTERN' returns true if there's a running process whose command line matches PATTERN.
Every program returns a status code, either 0 to indicate success or a number between 1 and 255 to indicate failure. In the shell, any command is a valid boolean expression; the status code 0 is treated as true and anything else as false.
$(…) means run the command inside the parentheses and capture its output. So rsync is executed as soon as the shell hits the definition of the RUNSYNC variable. To store a block of shell code, use a function (example below, although you don't actually need a function here, you could just write the code directly).
Your test [ $APPCHK < 2 ] should be [ $APPCHK -lt 2 ]: < means input redirection. (In bash, you can also write [[ foo < bar ]], but that's string comparison, not numeric comparison.)
~/ at the beginning of the remote rsync path is optional. Also, -e ssh is the default unless your version of rsync is really old.
exit at the end of the script is useless, the script will exit anyway.
Here's a script taking the above into account:
#!/bin/bash
run_rsync () {
rsync -rvz '/home/e-smith/files/ibays/drive-i/files/Warehouse Pics/organized_pics' \
imgserv#192.168.0.140:webapps/pavlick_container/public/images
}
process_pattern='/usr/bin/rsync -rvz /home/e-smith/files/ibays/drive-i/files/Warehouse Pics/organized_pics imgserv#192\.168\.0\.140:webapps/pavlick_container/public/images'
if pgrep -xF "$process_pattern"; then
run_rsync
fi
Looks like with your rsync command that some directory along this path is wrong: ~/webapps/pavlick_container/public/images
Have you checked on the server 192.168.0.140 in imgserv's home directory to see if "pavlick_container/public" exists? That's my guess.
You have a number of problems. First you are running the commands instead of putting the commands in variables. There is also a much easier way.
RUNSYNC="rsync -rvz -e ssh /home/e-smith/files/ibays/drive-i/files/Warehouse\ Pics/organized_pics imgserv#192.168.0.140:~/webapps/pavlick_container/public/images"
if ! pgrep -f "rsync.*organized_pics"; then $RUNSYNC; fi
First of all, the way of checking if the program is running is mostly wrong. This may or may not work. You should rely on some special file you create when your script starts, that it is deleted when your script ends. This will tell you if the script is running, just checking if this file exists.
Then, try to either put a \ before the ~ or to remove the ~/ completely. If cron is run as other user, the tilde will be substituted in the client for the user directory. It works for the command line because maybe the home directory of your user in both machines match, but not in the user the cron is running. A guess at this point, but again, try to remove the ~/ and see if it works.
If your real code is missing a closing dlb-quote on the grep target, you're going to get weird results from the get-go.
Also, ps aux will not list a complete command line result like you show (at least on all the the pss I have used).
You need to make it ps auxwww. Often you will see people add | grep -v grep | (you'll see why at some point). This can be reduced to changing your static search target slightly like "/usr/bin/rsync" to "/usr/bin/[r]sync ".
Other users are also helping with their comments. Using a flag file as #DiegoSevilla mentions is marginally deprecated. use a mkdir /tmp/MyWatcher_flagDir for your flag. Directory creation is an atomic activity (where as file creations are not), and this will eliminate any errors you might encounter from having 2 copies of you monitor try to make a flag file at the same time. Only one process will succeed in making or removing a flag dir.
I hope this helps.

Resources