Running a shell script on remote servers and passing a command line argument to it - shell

I want to run top command through a script on remote servers.
I also want to add a filter that will allow only integers to pass as a command line argument to the script which will run on the remote servers
this is the command which i'm using:-
ssh -oConnectTimeout=5 -oBatchMode=yes -l group servername 'bash -s' < /some/path/top_command.sh
Now when i'm not passing any argument to the script, it works fine and displays top 20 lines of the top command.
it is also filtering out the garbage values like any character (non integer)
But the issue is with the negative integers
ssh -oConnectTimeout=5 -oBatchMode=yes -l group servername 'bash -s' < /some/path/top_command.sh -7
Now i'm getting an error:-
Usage: bash [GNU long option] [option] ...
bash [GNU long option] [option] script-file ...
GNU long options:
--debug
--debugger
--dump-po-strings
--dump-strings
--help
--init-file
--login
--noediting
--noprofile
--norc
--posix
--protected
--rcfile
--restricted
--verbose
--version
--wordexp
Shell options:
-irsD or -c command or -O shopt_option (invocation only)
-abefhkmnptuvxBCHP or -o option
But when i try running the command like without using the top_command.sh script:-
ssh -oConnectTimeout=5 -oBatchMode=yes -l group servername 'top -b -n 1 | head -n -2'
I'm getting the top command's output for negative head values
Now i'm confused, what am i doing wrong?
Btw Content of top_command.sh
1 #!/bin/bash
2 if [[ $1 == "" ]]; then
3 echo -e "No Argument passed:- Showing default top 20 lines\n"
4 command=$(top -b -n 1 | head -n 20 2>&1)
5 echo "$command"
6 else
7 re='^[-0-9]+$'
8 if [[ $1 =~ $re ]]; then
9 command=$(top -b -n 1 | head -n $1 2>&1)
10 echo "$command"
11 else
12 echo "Argument passed is not an integer"
13 fi
14 fi

You can do it like
ssh -oConnectTimeout=5 -oBatchMode=yes -l group servername bash -s -- -7 < /some/path/top_command.sh
-- is a common option-argument separator that is helpful when passing arguments starting with - to a command. Commands like mv and rm also recognizes it. Everything that follows -- is no longer tested as being an option or not and is already just considered as a normal argument. To rm and mv it's helpful if the file starts with -.

Related

grep command inside EOF doesn't seems to be executing on remote hosts [UNIX BASH]

Here is the chunk of code for reference:-
Output:
I have checked the variable values using echo and those looks fine.
But what I want do achieve is searching logs on remote hosts using grep which does not give any output.
for dir in ${log_path}
do
for host in ${Host}
do
if [[ "${userinputserverhost}" == "${host}" ]]
then
ssh -q -T username#userinputserverhost "bash -s" <<-'EOF' 2>&1 | tee -a ${LogFile}
echo -e "Fetching details: \n"
`\$(grep -A 5 -s "\${ID}" "\${dir}"/archive/*.log)`
EOF
fi
break
done
done
First, remove all the crap around the grep.
Second, you're overquoting your vars.
Third, skip the "bash -s" if you can.
ssh -q -T username#userinputserverhost <<-'EOF' 2>&1 | tee -a ${LogFile}
echo -e "Fetching details: \n"
grep -A 5 -s "${ID}" "${dir}"/archive/*.log
EOF
Fourth, I don't see where $ID is set...so if that's being loaded on the remote system by the login or something, then that one would need the dollar sign backslashed.
Finally, be aware that here-docs are great, but sometimes here-strings are simpler if you can spare the quotes.
$: ssh 2>&1 dudeling#sandbox-server '
> date
> whoami
> ' | tee -a foo.txt
Fri Apr 30 09:23:09 EDT 2021
dudeling
$: cat foo.txt
Fri Apr 30 09:23:09 EDT 2021
dudeling
That one is more a matter of taste. Even better, if you can, write your remote-script to a local file & use that. And of course, you can always add set -vx into the script to see what gets remotely executed.
cat >tmpScript <<-'EOF'
echo -e "Fetching details: \n"
set -vx
grep -A 5 -s "${ID}" "${dir}"/archive/*.log
EOF
ssh <tmpScript 2>&1 -q -T username#userinputserverhost | tee -a ${LogFile}
Now you have an exact copy of what was issued for debugging.
Thanks Paul for spending time and coming up with suggestions/solutions.
I have managed to get it working couple of days back. Would have felt happy to say that your solution worked 100% but even satisfied that I got it sorted on my own as it helped me learn some new stuff.
FYI - grep -A 5 -s "${ID}" "${dir}"/archive/*.log - this will work but only by using shell built-in 'declare -p' to declare the variables within EOF. Also, I read somewhere and it is recommended to use EOF unqouted as it caters variable expansion to remote hosts without any trouble.
Below piece of code is working for me in bash:
ssh -q -T username#userinputserverhost <<-EOF 2>&1 | tee -a ${LogFile}
echo -e "Fetching details: \n"
$(declare -p ID)
$(declare -p dir)
grep -A 5 -s "${ID}" "${dir}"/archive/*.log
EOF

ssh bash -c exit status does not propagate [duplicate]

This question already has an answer here:
How to have simple and double quotes in a scripted ssh command
(1 answer)
Closed 4 years ago.
According to man ssh and this previous answer, ssh should propagate the exit status of whatever process it ran on the remote server. I seem to have found a mystifying exception!
$ ssh myserver exit 34 ; echo $?
34
Good...
$ ssh myserver 'exit 34' ; echo $?
34
Good...
$ ssh myserver bash -c 'exit 34' ; echo $?
0
What?!?
$ ssh myserver
ubuntu#myserver $ bash -c 'exit 34' ; echo $?
34
So the problem does not appear to be either ssh or bash -c in isolation, but their combination does not behave as I would expect.
I'm designing a script to be run on a remote machine that needs to take an argument list that's computed on the client side. For the sake of argument, let's say it fails if any of the arguments is not a file on the remote server:
ssh myserver bash -c '
for arg ; do
if [[ ! -f "$arg" ]] ; then
exit 1
fi
done
' arg1 arg2 ...
How can I run something like this and effectively inspect its return status? The test above seems to suggest I cannot.
The problem is that the quoting is being lost. ssh simply concatenates the arguments, it doesn't requote them, so the command you're actually executing on the server is:
bash -c exit 34
The -c option only takes one argument, not all the remaining arguments, so it's just executing exit; 34 is being ignored.
You can see a similar effect if you do:
ssh myserver bash -c 'echo foo'
It will just echo a blank line, not foo.
You can fix it by giving a single argument to ssh:
ssh myserver "bash -c 'exit 34'"
or by doubling the quotes:
ssh myserver bash -c "'exit 34'"
Insofar as your question is how to run a command remotely while passing it on ssh's command line without it getting in a mangle that triggers the bug in question, printf '%q ' can be used to ask the shell to perform quoting on your behalf, to build a string which can then be passed to ssh:
printf -v cmd_str '%q ' bash -c '
for arg ; do
if [[ ! -f "$arg" ]] ; then
exit 1
fi
done
' arg1 arg2 ...
ssh "$host" "$cmd_str"
However, this is only guaranteed to work correctly if the default shell for the remote user is also bash (or, if you used ksh's printf %q locally, if the remote shell is ksh). It's much safer to pass your script text out-of-band, as on stdin:
printf -v arg_str '%q ' arg1 arg2 ...
ssh "$host" "bash -s $arg_str" <<'EOF'
for arg; do
if [[ ! -f "$arg" ]]; then
exit 1
fi
done
EOF
...wherein we still depend on printf %q to generate correct output, but only for the arguments, not for the script itself.
Try wrapping in quotes:
╰─➤ ssh server "bash -c 'exit 34' "; echo $?
34

How to run for loop inside heredoc while accessing remote machine

Here is my script in which I use local variable inside a remote machine using heredoc. But the loop under the heredoc takes the first variable value only. The loop runs fine inside the heredoc but with the same values.
#!/bin/bash
prod_web=($(cat /tmp/webip.txt));
new_prod_app_private_ip=($(cat /tmp/ip.txt));
no_n=($(cat /tmp/serial.txt));
ssh -t -o StrictHostKeyChecking=no ubuntu#${prod_web[0]} -p 2345 -v << EOF
set -xv
for (( x = 0; x < '${#no_n[#]}'; x++ ))
do
sudo su
echo '${no_n[x]}'
echo '${new_prod_app_private_ip[x]}'
curl -fIkSs https://'${new_prod_app_private_ip[x]}':9002 | head -n 1
done
EOF
So, my ip.txt file contains values like:
10.0.1.0
10.0.2.0
10.0.3.0
My serial.txt file:
9
10
11
So, my loop runs for only the first IP (present in /tmp/ip.txt) in the remote machine, three times. I want to run it for all the three IPs. My remote ip is present in the file /tmp/webip.txt.
Got stuck for a long time, any help is appreciated. Is there any other solution that I can go with?
There are 2 environments. On your local machine and on the remote machine. You need to think how to transfer data/variables/state/objects/handles between these machines.
If you set something on your local machine (ie. prod_web=($(cat /tmp/webip.txt));) and then just ssh to remote host (ie. ssh user#host 'echo "${prod_web[#]}"'), the variable will not be visible/exported to the remote machine. You can:
scp the files {ip,serial}.txt and execute the whole script on the remote machine, then cleanup , ie. remove the {ip,serial}.txt files from the remote machine
pass the files {ip,serial}.txt somehow merged/joined/pasted to the stdin of the ssh and then read up stdin on the remove machine
create all the commands to run on your local machine and then pass pre-prepared commands to remote machine, like ssh .... "$(for ...; do; echo curl ...; done)"
I would go with the second option, as I like passing everything using pipes and don't like to cleanup after me - removing temporary files in case of error can be a mess.
My script would probably look like this:
#!/bin/bash
set -euo pipefail
read -r host _ <webip.txt
paste serial.txt ip.txt | ssh -t -o StrictHostKeyChecking=no -p 2345 -v ubuntu#"$host" '#!/bin/bash
set -euo pipefail
while read -r no_n ip; do
for ((i = 0; i < no_n; ++i)); do
printf "%s\n" "$no_n"
printf "%s\n" "$ip"
curl -fIkSs https://"$ip":9002 | head -n 1
done
done
'
As the remote script would become larger and less qouting friendly, I would save it into another remote_scripts.sh and execute ssh ... -m remote_scripts.sh.
I don't get what you are trying to do with that sudo su, which 100% does not do what you want.
If the no_n magic number is the number of times to execute that curl and you have xargs and you don't really care about errors, you can just do a magic and confusing oneliner:
#!/bin/bash
set -euo pipefail
read -r host _ <webip.txt
paste serial.txt ip.txt | ssh -t -o StrictHostKeyChecking=no -p 2345 -v ubuntu#"$host" 'xargs -n2 -- sh -c "seq 0 \"\$1\" | xargs -n1 -- sh -c \"curl -fIkSs https://\\\"\\\$1\\\":9002 | head -n 1\" -- \"\$2\"" --'
Preparing all the command to run maybe actually more readable and may save some nasty qouting to resolve. But this really depends on how big serial.txt and ip.txt are and how big are the commands to be executed on the remote machine, as you want to minimize the number of bytes transferred between machines.
Here the commands to run are constructed on local machine (ie. "$(...)" is passed to ssh) and executed on remote machine:
# semi-readable script, not as fast and no xargs
ssh -t -o StrictHostKeyChecking=no -p 2345 -v ubuntu#"$host" "$(paste serial.txt ip.txt | while read -r serial ip; do
seq 0 "$serial" | while read -r _; do
echo "curl -fIkSs \"https://$ip:9002\" | head -n 1"
done
done)"
HERE-doc does not expand shell commands, so:
$ cat <<EOF
> echo 1
> EOF
echo 1
but you can use command substitution $( ... ):
$ cat <<EOF
> $(echo 1)
> EOF
1

Obscure bash syntax error

I am unable to find my syntax problem here:
» ssh bootstrap01 bash -c 'for master in master01 master02 master03 ; do ssh root#$master -i .ssh/master hostname ; done'
bash: -c: line 0: syntax error near unexpected token `do'
bash: -c: line 0: `bash -c for master in master01 master02 master03 ; do ssh root#$master -i .ssh/master hostname ; done'
EDIT
To verify that my in-line script works:
$ for master in localhost localhost localhost ; do ssh $master hostname ; done
myhost.mydomain.net
myhost.mydomain.net
myhost.mydomain.net
It's actually a problem with the way that SSH passes the command to the remote side. Compare these examples:
$ ssh localhost bash -x -c 'echo 1; echo 2; echo 3'
+ echo
2
3
$ ssh localhost bash -x -c "'echo 1; echo 2; echo 3'"
1
2
3
+ echo 1
+ echo 2
+ echo 3
The key to understanding the problem is that SSH reconstructs a command line from its arguments and it does it badly. It just pastes the arguments back together using spaces, as can be seen if we run the two commands above with the -v option:
debug1: Sending command: bash -x -c echo 1; echo 2; echo 3
debug1: Sending command: bash -x -c 'echo 1; echo 2; echo 3'
respectively.
Obviously, the first of those is run (in the remote shell) as
bash -x -c "echo" 1
echo 2
echo 3
and that's what we see above.
In short, you need to provide quotes for the remote shell.
In your case, you'll probably be able to just omit the bash -c, as there's nothing in your command that a standard shell won't like:
ssh bootstrap01 'for master in master01 master02 master03 ; do ssh root#$master -i .ssh/master hostname ; done'

grep command exit code for unmatched patterns

i have written a shell scripts which runs crontab - l command
To make it more easy to use i have also given the user an ability to pass a command line argument to the script which will act like a pattern input for the grep command, so that the user can filter out all the stuffs which he/she doesn't need to see.
here's the script:-
1 #!/bin/bash
2 if [[ $1 == "" ]]; then
3 echo -e "No Argument passed:- Showing default crontab\n"
4 command=$(crontab -l 2>&1)
5 echo "$command"
6 else
7 rc=$?
8 command=$(crontab -l | grep -- "$1" 2>&1)
9 echo "$command"
10 if [[ $rc != 0 ]] ; then
11 echo -e "grep command on crontab -l was not successful"
12 fi
13 fi
this is how i run it
$ ./DisplayCrontab.sh
Now if i don't pass any command line argument it'll show me the complete crontab
If i pass any garbage pattern which doesn't exists in the crontab it'll show me the following message :-
grep command on crontab -l was not successful
But even if i pass a pattern which does exist in a couple of lines in crontab, i'm getting this kind of output:-
#matching lines
#matching lines
#matching lines
grep command on crontab -l was not successful
Why am i getting grep command not successful at the bottom?, how can i get rid of it?
Is there anything wrong with the script?
You're capturing the exit code before the execution, should be:
command=$(crontab -l | grep -- "$1" 2>&1)
rc=$?
To test this code use numeric operators:
[[ $rc -ne 0 ]]
Grep man:
Normally, the exit status is 0 if selected lines are found and
1 otherwise. But the exit status is 2 if an error occurred

Resources