Call remote bash function via ansible - linux

Is it possible to call a remote function defined in bash (for example added in one of the scripts that are stored under /etc/profile.d) via ansible ad-hoc command (using shell , command modules ?)
For example I have the following function that allows to see the status of apt history:
function apt-history(){
case "$1" in
install)
cat /var/log/dpkg.log | grep 'install '
;;
upgrade|remove)
cat /var/log/dpkg.log | grep $1
;;
rollback)
cat /var/log/dpkg.log | grep upgrade | \
grep "$2" -A10000000 | \
grep "$3" -B10000000 | \
awk '{print $4"="$5}'
;;
*)
cat /var/log/dpkg.log
;;
esac
}
Is it possible to make a call to this function directly via function name from one of the ansible existing modules via ad-hoc command ? I know it would be possible to create a new script and call it directly remotely, but this is not what I want to achieve here. Any suggestions appreciated.

You have to instantiate bash on the remote side, using the command or shell module like this :
ansible localhost -m command -a 'bash -lc apt-history'
This is a common trick if you need environment variables to be set-up.

Related

How to handle quotes, backtick special characters for running linux bash shell command in remote server

Someone, please help me in correcting below command I wasted more than a day fixing below but failed, please help, I will be using below in ansible shell module.
ssh -o ConnectTimeout=5 splunk#10.145.32.172 '
sdline="`
grep -n TA-aws-hf-{{client_code}}-{{env_name}} /opt/splunk/etc/system/local/serverclass.conf
| awk -F \":\" \'{print $1}\'
`
&& sed -ie \"$sdline,`
echo $sdline + 3
| bc
`d\" /opt/splunk/etc/system/local/serverclass.conf
"
> ^C
Even tried below way:
ssh -o ConnectTimeout=5 splunk#10.145.32.172 exec sdline=`grep -n TA-aws-hf-{{client_code}}-{{env_name}} /opt/splunk/etc/system/local/serverclass.conf|awk -F ":" '{print $1}'` && sed -ie "$sdline,`echo $sdline + 3|bc` d" /opt/splunk/etc/system/local/serverclass.conf
grep: /opt/splunk/etc/system/local/serverclass.conf: No such file or directory
bash: line 0: exec: sdline=: not found
Context: It seems this question originated as an XY Problem. OP appears to want to remove the 3 lines including and after the string "TA-aws-hf-{{client_code}}-{{env_name}}".
Backticks are deprecated; use $(modern $(command) substitution) when necessary. It is not necessary in this case.
If your remote server has GNU sed:
ssh splunk#10.145.32.172 'sed -i "/TA-aws-hf-{{client_code}}-{{env_name}}/,+2d" /opt/splunk/etc/system/local/serverclass.conf'
If that gives you sed: -e expression #1, char 19: unexpected ',':
ssh splunk#10.145.32.172 '
cd /opt/splunk/etc/system/local
awk "/TA-aws-hf-{{client_code}}-{{/ {i=-3} i++>0" \
serverclass.conf > temp && mv $_ serverclass.conf
'
Your remote command is quite complicated.
I suggest the following:
Use ssh to gain interactive shell in 10.145.32.172
Create a script on 10.145.32.172 that do the work, with everything hard coded.
Refactor command line parameters to your script.
Call your script remotely from your local machine.
This strategy simplify the script and its maintenance. Allowing you to send only the important parameters.
If you have to deploy the script on many remote machines. Use shared storage resources, like NFS. Optionally copy the script using scp prior to running it.

Allow sh to be run from anywhere

I have been monitoring the performance of my Linux server with ioping (had some performance degradation last year). For this purpose I created a simple script:
echo $(date) | tee -a ../sb-output.log | tee -a ../iotest.txt
./ioping -c 10 . 2>&1 | tee -a ../sb-output.log | grep "requests completed in\|ioping" | grep -v "ioping statistics" | sed "s/^/IOPing I\/O\: /" | tee -a ../iotest.txt
./ioping -RD . 2>&1 | tee -a ../sb-output.log | grep "requests completed in\|ioping" | grep -v "ioping statistics" | sed "s/^/IOPing seek rate\: /" | tee -a ../iotest.txt
etc
The script calls ioping in the folder /home/bench/ioping-0.6. Then it saves the output in readable form in /home/bench/iotest.txt. It also adds the date so I can compare points in time.
Unfortunately I am no experienced programmer and this version of the script only works if you first enter the right directory (/home/bench/ioping-0.6).
I would like to call this script from anywhere. For example by calling
sh /home/bench/ioping.sh
Googling this and reading about path variables was a bit over my head. I kept up ending up with different version of
line 3: ./ioping: No such file or directory
Any thoughts on how to upgrade my scripts so that it works anywhere?
The trick is the shell's $0 variable. This is set to the path of the script.
#!/bin/sh
set -x
cd $(dirname $0)
pwd
cd ${0%/*}
pwd
If dirname isn't available for some reason, like some limited busybox distributions, you can try using shell parameter expansion tricks like the second one in my example.
Isn't it obvious? ioping is not in . so you can't use ./ioping.
Easiest solution is to set PATH to include the directory where ioping is. perhaps more robust - figure out the path to $0 and use that path as the location for ioping (assing your script sits next to ioping).
If iopinf itself depend on being ruin in a certain directory, you might have to make your script cd to the ioping directory before running.

Find the current shell of the user using a shell script [duplicate]

How can I determine the current shell I am working on?
Would the output of the ps command alone be sufficient?
How can this be done in different flavors of Unix?
There are three approaches to finding the name of the current shell's executable:
Please note that all three approaches can be fooled if the executable of the shell is /bin/sh, but it's really a renamed bash, for example (which frequently happens).
Thus your second question of whether ps output will do is answered with "not always".
echo $0 - will print the program name... which in the case of the shell is the actual shell.
ps -ef | grep $$ | grep -v grep - this will look for the current process ID in the list of running processes. Since the current process is the shell, it will be included.
This is not 100% reliable, as you might have other processes whose ps listing includes the same number as shell's process ID, especially if that ID is a small number (for example, if the shell's PID is "5", you may find processes called "java5" or "perl5" in the same grep output!). This is the second problem with the "ps" approach, on top of not being able to rely on the shell name.
echo $SHELL - The path to the current shell is stored as the SHELL variable for any shell. The caveat for this one is that if you launch a shell explicitly as a subprocess (for example, it's not your login shell), you will get your login shell's value instead. If that's a possibility, use the ps or $0 approach.
If, however, the executable doesn't match your actual shell (e.g. /bin/sh is actually bash or ksh), you need heuristics. Here are some environmental variables specific to various shells:
$version is set on tcsh
$BASH is set on bash
$shell (lowercase) is set to actual shell name in csh or tcsh
$ZSH_NAME is set on zsh
ksh has $PS3 and $PS4 set, whereas the normal Bourne shell (sh) only has $PS1 and $PS2 set. This generally seems like the hardest to distinguish - the only difference in the entire set of environment variables between sh and ksh we have installed on Solaris boxen is $ERRNO, $FCEDIT, $LINENO, $PPID, $PS3, $PS4, $RANDOM, $SECONDS, and $TMOUT.
ps -p $$
should work anywhere that the solutions involving ps -ef and grep do (on any Unix variant which supports POSIX options for ps) and will not suffer from the false positives introduced by grepping for a sequence of digits which may appear elsewhere.
Try
ps -p $$ -oargs=
or
ps -p $$ -ocomm=
If you just want to ensure the user is invoking a script with Bash:
if [ -z "$BASH" ]; then echo "Please run this script $0 with bash"; exit; fi
or ref
if [ -z "$BASH" ]; then exec bash $0 ; exit; fi
You can try:
ps | grep `echo $$` | awk '{ print $4 }'
Or:
echo $SHELL
$SHELL need not always show the current shell. It only reflects the default shell to be invoked.
To test the above, say bash is the default shell, try echo $SHELL, and then in the same terminal, get into some other shell (KornShell (ksh) for example) and try $SHELL. You will see the result as bash in both cases.
To get the name of the current shell, Use cat /proc/$$/cmdline. And the path to the shell executable by readlink /proc/$$/exe.
There are many ways to find out the shell and its corresponding version. Here are few which worked for me.
Straightforward
$> echo $0 (Gives you the program name. In my case the output was -bash.)
$> $SHELL (This takes you into the shell and in the prompt you get the shell name and version. In my case bash3.2$.)
$> echo $SHELL (This will give you executable path. In my case /bin/bash.)
$> $SHELL --version (This will give complete info about the shell software with license type)
Hackish approach
$> ******* (Type a set of random characters and in the output you will get the shell name. In my case -bash: chapter2-a-sample-isomorphic-app: command not found)
ps is the most reliable method. The SHELL environment variable is not guaranteed to be set and even if it is, it can be easily spoofed.
I have a simple trick to find the current shell. Just type a random string (which is not a command). It will fail and return a "not found" error, but at start of the line it will say which shell it is:
ksh: aaaaa: not found [No such file or directory]
bash: aaaaa: command not found
I have tried many different approaches and the best one for me is:
ps -p $$
It also works under Cygwin and cannot produce false positives as PID grepping. With some cleaning, it outputs just an executable name (under Cygwin with path):
ps -p $$ | tail -1 | awk '{print $NF}'
You can create a function so you don't have to memorize it:
# Print currently active shell
shell () {
ps -p $$ | tail -1 | awk '{print $NF}'
}
...and then just execute shell.
It was tested under Debian and Cygwin.
The following will always give the actual shell used - it gets the name of the actual executable and not the shell name (i.e. ksh93 instead of ksh, etc.). For /bin/sh, it will show the actual shell used, i.e. dash.
ls -l /proc/$$/exe | sed 's%.*/%%'
I know that there are many who say the ls output should never be processed, but what is the probability you'll have a shell you are using that is named with special characters or placed in a directory named with special characters? If this is still the case, there are plenty of other examples of doing it differently.
As pointed out by Toby Speight, this would be a more proper and cleaner way of achieving the same:
basename $(readlink /proc/$$/exe)
My variant on printing the parent process:
ps -p $$ | awk '$1 == PP {print $4}' PP=$$
Don't run unnecessary applications when AWK can do it for you.
Provided that your /bin/sh supports the POSIX standard and your system has the lsof command installed - a possible alternative to lsof could in this case be pid2path - you can also use (or adapt) the following script that prints full paths:
#!/bin/sh
# cat /usr/local/bin/cursh
set -eu
pid="$$"
set -- sh bash zsh ksh ash dash csh tcsh pdksh mksh fish psh rc scsh bournesh wish Wish login
unset echo env sed ps lsof awk getconf
# getconf _POSIX_VERSION # reliable test for availability of POSIX system?
PATH="`PATH=/usr/bin:/bin:/usr/sbin:/sbin getconf PATH`"
[ $? -ne 0 ] && { echo "'getconf PATH' failed"; exit 1; }
export PATH
cmd="lsof"
env -i PATH="${PATH}" type "$cmd" 1>/dev/null 2>&1 || { echo "$cmd not found"; exit 1; }
awkstr="`echo "$#" | sed 's/\([^ ]\{1,\}\)/|\/\1/g; s/ /$/g' | sed 's/^|//; s/$/$/'`"
ppid="`env -i PATH="${PATH}" ps -p $pid -o ppid=`"
[ "${ppid}"X = ""X ] && { echo "no ppid found"; exit 1; }
lsofstr="`lsof -p $ppid`" ||
{ printf "%s\n" "lsof failed" "try: sudo lsof -p \`ps -p \$\$ -o ppid=\`"; exit 1; }
printf "%s\n" "${lsofstr}" |
LC_ALL=C awk -v var="${awkstr}" '$NF ~ var {print $NF}'
My solution:
ps -o command | grep -v -e "\<ps\>" -e grep -e tail | tail -1
This should be portable across different platforms and shells. It uses ps like other solutions, but it doesn't rely on sed or awk and filters out junk from piping and ps itself so that the shell should always be the last entry. This way we don't need to rely on non-portable PID variables or picking out the right lines and columns.
I've tested on Debian and macOS with Bash, Z shell (zsh), and fish (which doesn't work with most of these solutions without changing the expression specifically for fish, because it uses a different PID variable).
If you just want to check that you are running (a particular version of) Bash, the best way to do so is to use the $BASH_VERSINFO array variable. As a (read-only) array variable it cannot be set in the environment,
so you can be sure it is coming (if at all) from the current shell.
However, since Bash has a different behavior when invoked as sh, you do also need to check the $BASH environment variable ends with /bash.
In a script I wrote that uses function names with - (not underscore), and depends on associative arrays (added in Bash 4), I have the following sanity check (with helpful user error message):
case `eval 'echo $BASH#${BASH_VERSINFO[0]}' 2>/dev/null` in
*/bash#[456789])
# Claims bash version 4+, check for func-names and associative arrays
if ! eval "declare -A _ARRAY && func-name() { :; }" 2>/dev/null; then
echo >&2 "bash $BASH_VERSION is not supported (not really bash?)"
exit 1
fi
;;
*/bash#[123])
echo >&2 "bash $BASH_VERSION is not supported (version 4+ required)"
exit 1
;;
*)
echo >&2 "This script requires BASH (version 4+) - not regular sh"
echo >&2 "Re-run as \"bash $CMD\" for proper operation"
exit 1
;;
esac
You could omit the somewhat paranoid functional check for features in the first case, and just assume that future Bash versions would be compatible.
None of the answers worked with fish shell (it doesn't have the variables $$ or $0).
This works for me (tested on sh, bash, fish, ksh, csh, true, tcsh, and zsh; openSUSE 13.2):
ps | tail -n 4 | sed -E '2,$d;s/.* (.*)/\1/'
This command outputs a string like bash. Here I'm only using ps, tail, and sed (without GNU extesions; try to add --posix to check it). They are all standard POSIX commands. I'm sure tail can be removed, but my sed fu is not strong enough to do this.
It seems to me, that this solution is not very portable as it doesn't work on OS X. :(
echo $$ # Gives the Parent Process ID
ps -ef | grep $$ | awk '{print $8}' # Use the PID to see what the process is.
From How do you know what your current shell is?.
This is not a very clean solution, but it does what you want.
# MUST BE SOURCED..
getshell() {
local shell="`ps -p $$ | tail -1 | awk '{print $4}'`"
shells_array=(
# It is important that the shells are listed in descending order of their name length.
pdksh
bash dash mksh
zsh ksh
sh
)
local suited=false
for i in ${shells_array[*]}; do
if ! [ -z `printf $shell | grep $i` ] && ! $suited; then
shell=$i
suited=true
fi
done
echo $shell
}
getshell
Now you can use $(getshell) --version.
This works, though, only on KornShell-like shells (ksh).
Do the following to know whether your shell is using Dash/Bash.
ls –la /bin/sh:
if the result is /bin/sh -> /bin/bash ==> Then your shell is using Bash.
if the result is /bin/sh ->/bin/dash ==> Then your shell is using Dash.
If you want to change from Bash to Dash or vice-versa, use the below code:
ln -s /bin/bash /bin/sh (change shell to Bash)
Note: If the above command results in a error saying, /bin/sh already exists, remove the /bin/sh and try again.
I like Nahuel Fouilleul's solution particularly, but I had to run the following variant of it on Ubuntu 18.04 (Bionic Beaver) with the built-in Bash shell:
bash -c 'shellPID=$$; ps -ocomm= -q $shellPID'
Without the temporary variable shellPID, e.g. the following:
bash -c 'ps -ocomm= -q $$'
Would just output ps for me. Maybe you aren't all using non-interactive mode, and that makes a difference.
Get it with the $SHELL environment variable. A simple sed could remove the path:
echo $SHELL | sed -E 's/^.*\/([aA-zZ]+$)/\1/g'
Output:
bash
It was tested on macOS, Ubuntu, and CentOS.
On Mac OS X (and FreeBSD):
ps -p $$ -axco command | sed -n '$p'
Grepping PID from the output of "ps" is not needed, because you can read the respective command line for any PID from the /proc directory structure:
echo $(cat /proc/$$/cmdline)
However, that might not be any better than just simply:
echo $0
About running an actually different shell than the name indicates, one idea is to request the version from the shell using the name you got previously:
<some_shell> --version
sh seems to fail with exit code 2 while others give something useful (but I am not able to verify all since I don't have them):
$ sh --version
sh: 0: Illegal option --
echo $?
2
One way is:
ps -p $$ -o exe=
which is IMO better than using -o args or -o comm as suggested in another answer (these may use, e.g., some symbolic link like when /bin/sh points to some specific shell as Dash or Bash).
The above returns the path of the executable, but beware that due to /usr-merge, one might need to check for multiple paths (e.g., /bin/bash and /usr/bin/bash).
Also note that the above is not fully POSIX-compatible (POSIX ps doesn't have exe).
Kindly use the below command:
ps -p $$ | tail -1 | awk '{print $4}'
This one works well on Red Hat Linux (RHEL), macOS, BSD and some AIXes:
ps -T $$ | awk 'NR==2{print $NF}'
alternatively, the following one should also work if pstree is available,
pstree | egrep $$ | awk 'NR==2{print $NF}'
You can use echo $SHELL|sed "s/\/bin\///g"
And I came up with this:
sed 's/.*SHELL=//; s/[[:upper:]].*//' /proc/$$/environ

Execute bash on logon and add alias

When I login to a Linux server per Putty, I want to execute the bash (because the default shell is another) and after that adding an alias.
I tried several combinations of putting exec bash in the .profile and adding alias foo='echo foo' into .bash_profile. But I didn't find out the correct combination. Either the alias wasn't set, or the bash wasn't executed.
So, the question is, in which of these files:
.profile
.bashrc
.bash_profile
do I have to put these commands:
exec bash
alias foo='echo foo'
to run the bash shell and have access to my alias every time I login to the server?
edit: We're using all the same user to login. But I want to execute the bash and adding the alias only for my remote machine. I do already have a suitable if statement for that. I only have to know, where to put these commands!
edit2:
What I have so far in my .profile:
if [ $(who -m | awk '{print $NF}' | grep "myHostName" | wc -l) -eq 1 ]
then
exec bash
alias foo='echo foo'
fi
This will execute the bash for only my user. But the alias will be ignored, since I'm starting a new shell and the alias will be probably set in the old shell...
Going to go out on a limb and guess you want to do this because your default shell isn't bash. Don't. Just change your default shell
> chsh -s /bin/bash
Then put
alias foo='echo foo'
In either ~/.bashrc or ~/.bash_profile
If multiple users are using the same account, you can try to do the following. While logged in, run
> who -a | grep $(ps -p $PPID -o ppid=) | awk '{print $NF}'
This may be system dependent, but on a couple I tried it on, this will get location you're logged in from. Once you have that output, do the following
if [[ $(who -a | grep $(ps -p $PPID -o ppid=) | awk '{print $NF}') == output ]]; then
alias foo='echo foo'
done
If you're ssh-ing from multiple computers, then I don't think there is any way to do what you want. Simplest would be to make your own file in the home directory, and then source it manually each time you log in.
e.g.
> touch myfile.txt
> echo "alias foo='echo foo'" >> myfile.txt
> source myfile.txt
> foo
foo
So you would just have to run source myfile.txt each time you log in or just have putty source it by default.
Okay, finally I figured it out by myself with the great help of BroSlow.
I wrote the following to my .profile:
if [ $(who -m | awk '{print $NF}' | grep "myHostName" | wc -l) -eq 1 ]
then
exec bash
fi
and the other part to the .bash_profile:
if [ $(who -m | awk '{print $NF}' | grep "myHostName" | wc -l) -eq 1 ]
then
alias foo='echo foo'
fi
This solved my problem!
On logon, the .profile will be sourced automatically and will execute the bash.
After that the .bash_profile will be sourced due to the fact, that the bash shell will source it's own profile.
However: thanks a lot for the support!
To set up alias in startup change your .bash_profile
Add alias to bash profile:
$ cd
$ sudo nano .bash_profile
$ alias ALIAS_NAME='COMMAND'
Update bash profile
$ source ~/.bash_profile

Is it possible to run a script which loops through hosts with rsh/ssh

I have tried to run following csh script which ssh to all computers on our network with the purpose of yum installing software passed as arguments. However, the script fails to continue once I have rsh to another host. Is there a way around this problem?
if ($1 == "")then
echo -n "Please enter a package to install\n"
set package=$<
else set package = $#argv
endif
set numlines = `cat $NM_HOME/sh_local/nc_network2.txt | grep -v "^#" | fgrep "%" | wc -l`
while ($numlines>0)
set line = `cat $NM_HOME/sh_local/nc_network2.txt | grep -v "^#" | fgrep "%" | tail -$numlines | head -1`
set host2 = `echo $line | cut -f 1 -d %`
set where = `echo $line | cut -f 2 -d %`
if ($host2 == $this_machine) then
echo "This is $host2....skipping rsh to this machine"
echo ""
goto yum
endif
echo ""
echo "logging into $host2 $where"
echo ""
sleep 1
rsh $host2
yum:
echo ""
echo "Preparing to install $package on $host2"
sudo yum -y install $package
if ($host2 == $this_machine) then
goto decrement
else
logout
goto decrement
endif
decrement:
# numlines--
end
ssh/rsh-ing to another host does not magically continue the execution of your script on that host. Executing ssh hostname in a script has the exact same effect as executing ssh hostname in your shell — it connects to that machine, runs an interactive shell there, and leaves you in that shell. Your script execution will only continue after you close the ssh/rsh connection yourself.
In order to perform some action on that host, you need to provide an explicit command you want to run on that host, like this:
rsh $host2 sudo yum -y install $package
Note that this is only a naive example for illustration purposes; it likely won't be enough to fix the entire logic of your script, but should point you in the right direction.
YES. My answer is provided below only by reading your "Question" and not what you have put up in the code that you posted.
I have been doing this for many years as below.
create a list of hosts with onehost on each line.
enable SSH key based authentication upfront. This can be done manually but I had done it in the past using perl and expect.
run a for loop as below to perform action onall the servers:
for i in cat hosts_list; do ssh $i dmidecode | grep -m 1 'Serial Number:'; done
Above oneliner will give me the serial numbers of all the servers wic are mentioned in the host_list. This a shot example of pulling serial number. however, you can write a full fledged bash script following the exact same logic.
for more advanced requirement, consider using perl and the ssh modules that are available on CPAN

Resources