Executing a local script on a remote Machine - bash

I have a script on my local machine, but need to run it on a remote machine without copying it over there (IE, I can't sftp it over and just run it there)
I currently have the following functioning command
echo 'cd /place/to/execute' | cat - test.sh | ssh -T user#hostname
However, I also need to provide a commandline argument to test.sh.
I tried just adding it after the .sh, like I would for local execution, but that didn't work:
echo 'cd /place/to/execute' | cat - test.sh "arg" | ssh -T user#hostname
"cat: arg: No such file or directory" is the resulting error

You need to override the arguments:
echo 'set -- arg; cd /place/to/execute' | cat - test.sh | ssh -T user#hostname
The above will set the first argument to arg.
Generally:
set -- arg1 arg2 arg3
will overwrite the $1, $2, $3 in bash.
This will basically make the result of cat - test.sh a standalone script that doesn't need any arguments`.

Depends on the complexity of the script that you have. You might want to rewrite it to be able to use rpcsh functionality to remotely execute shell functions from your script.
Using https://gist.github.com/Shadowfen/2b510e51da6915adedfb saved into /usr/local/include/rpcsh.inc (for example) you could have a script
#!/bin/sh
source /usr/local/include/rpcsh.inc
MASTER_ARG=""
function ahelper() {
# used by doremotely just to show that we can
echo "master arg $1 was passed in"
}
function doremotely() {
# this executes on the remote host
ahelper $MASTER_ARG > ~/sample_rpcsh.txt
}
# main
MASTER_ARG="newvalue"
# send the function(s) and variable to the remote host and then execute it
rpcsh -u user -h host -f "ahelper doremotely" -v MASTER_ARG -r doremotely
This will give you a ~/sample_rpcsh.txt file on the remote host that contains
master arg newvalue was passed in
Copy of rpcsh.inc (in case link goes bad):
#!/bin/sh
# create an inclusion guard (to prevent multiple inclusion)
if [ ! -z "${RPCSH_GUARD+xxx}" ]; then
# already sourced
return 0
fi
RPCSH_GUARD=0
# rpcsh -- Runs a function on a remote host
# This function pushes out a given set of variables and functions to
# another host via ssh, then runs a given function with optional arguments.
# Usage:
# rpcsh -h remote_host -u remote_login -v "variable list" \
# -f "function list" -r mainfunc [-- param1 [param2]* ]
#
# The "function list" is a list of shell functions to push to the remote host
# (including the main function to run, and any functions that it calls).
#
# Use the "variable list" to send a group of variables to the remote host.
#
# Finally "mainfunc" is the name of the function (from "function list")
# to execute on the remote side. Any additional parameters specified (after
# the --)gets passed along to mainfunc.
#
# You may specify multiple -v "variable list" and -f "function list" options.
#
# Requires that you setup passwordless access to the remote system for the script
# that will be running this.
rpcsh() {
if ! args=("$(getopt -l "host:,user:,pushvars:,pushfuncs:,run:" -o "h:u:v:f:r:A" -- "$#")")
then
echo getopt failed
logger -t ngp "rpcsh: getopt failed"
exit 1
fi
sshvars=( -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null )
eval set -- "${args[#]}"
pushvars=""
pushfuncs=""
while [ -n "$1" ]
do
case $1 in
-h|--host) host=$2;
shift; shift;;
-u|--user) user=$2;
shift; shift;;
-v|--pushvars) pushvars="$pushvars $2";
shift; shift;;
-f|--pushfuncs) pushfuncs="$pushfuncs $2";
shift; shift;;
-r|--run) run=$2;
shift; shift;;
-A) sshvars=( "${sshvars[#]}" -A );
shift;;
-i) sshvars=( "${sshvars[#]}" -i $2 );
shift; shift;;
--) shift; break;;
esac
done
remote_args=( "$#" )
vars=$([ -z "$pushvars" ] || declare -p $pushvars 2>/dev/null)
ssh ${sshvars[#]} ${user}#${host} "
#set -x
$(declare -p remote_args )
$vars
$(declare -f $pushfuncs )
$run ${remote_args[#]}
"
}

Related

getopts to get multiple values for same argument

I am looking to get multiple values from same argument using getopts. I want to use this script to ssh and run commands on a list of hosts provided through a file.
Usage: .\ssh.sh -f file_of_hosts.txt -c "Command1" "command2"
Expected output:
ssh userid#server1:command1
ssh userid#server1:command2
ssh userid#server2:commnand1
ssh userid#server2:commnand2
Sample Code I used but failed to get expected results
id="rm08397"
while getopts ":i:d:s:f:" opt
do
case $opt in
f ) file=$OPTARG;;
c ) cmd=$OPTARG;;
esac
done
shift "$(($OPTIND -1))"
# serv=$(for host in `cat $file`
# do
# echo -e "$host#"
# done
# )
# for names in $serv
# do
# ssh $id#$serv:
for hosts in $file;do
for cmds in $cmd;do
o1=$id#$hosts $cmds
echo $o1
done
done
You can achieve the effect by repeating -c :
declare -a cmds
id="rm08397"
while getopts ":c:f:" opt
do
case $opt in
f ) file="$OPTARG";;
c ) cmds+=("$OPTARG");;
esac
done
shift $((OPTIND -1))
for host in $(<$file);do
for cmd in "${cmds[#]}";do
echo ssh "$id#$host" "$cmd"
done
done
# Usage: ./ssh.sh -f file_of_hosts.txt -c "Command1" -c "command2"

Bash using optional parameters

I am trying to create a function that will take in $PASS, $USER, and $COMMAND as inputs the $USER and $PASS are optional, meaning it will use default username and passwords if non was supplied as parameters. Here is my function
function exec_ssh_command() {
local PASS=${1:-${ROOT_PW}}; # use argument supplied or default root pw
local USER=${2:-${ROOT_USER}};
shift 2
local COMMAND=$#
echo "Executing command: ${COMMAND}..."
sshpass -p ${PASS} ssh ${USER}#${ADDRESS} ${COMMAND}
}
If this is run without $1 and $2 arguments it breaks the function, so the output would be something like sshpass -p ls -ltr ssh {USER}#{ADDRESS} ls -ltr if my $COMMAND is ls -ltr
How can I get around this?
You can use getopts to parse positional parameters as below:
exec_ssh_command() {
local OPTIND=0
local OPTARG OPTION
local pass=${DEFAULT_PASS} user=${DEFAULT_USER}
while getopts "u:p:" OPTION; do
case "$OPTION" in
u) user=$OPTARG;;
p) pass=$OPTARG;;
esac
done
sshpass -p "$pass" ssh "${user}#${ADDRESS}" "${#:OPTIND}"
}
Sample usages:
exec_ssh_command -u my_user -p my_password ls -ltr
exec_ssh_command -p my_password ls -ltr
exec_ssh_command -p my_password ls -ltr
exec_ssh_command ls -ltr
Explanation:
See help getopts on a bash prompt for the complete info. (Any explanation I would have added here would have been just a snippet from the same output.)
If you don't provide at least two arguments, the shift 2 can fail because there are not enough arguments to shift. Try testing them before:
if [[ -n "$1" ]] ; then
PASS="$1"
shift
else
PASS="$ROOT_PW"
fi
if [[ -n "$1" ]] ; then
USER="$1"
shift
else
USER="$ROOT_USER"
fi
COMMAND=$#
...
If you want to make the convention that the function is to be called: exec_ssh_command [[user] password] command, you could implement it like:
exec_ssh_command() {
local pass user
case $# in
2) pass=${ROOT_PW}; user="$1"; shift 1;;
1) pass=${ROOT_PW}; user=${ROOT_USER};;
*) pass="$1"; user="$2"; shift 2;;
esac
sshpass -p "$pass" ssh "${user}#${ADDRESS?}" "$#"
}
or (basically the same thing, just a stylistic difference):
exec_ssh_command() {
local pass user
case $# in
2) user="$1"; shift 1;;
1) ;;
*) pass="$1"; user="$2"; shift 2;;
esac
sshpass -p "${pass:-$ROOT_PW}" ssh "${user:-$ROOT_USER}#${ADDRESS?}" "$#"
}

Bash: How to check if dynamic env variable is set?

I'm writing a script where I want to dynamically create an environment variable name, and check if it has been set.
#!/bin/bash
#######################################
# Builds options string
# Globals:
# JVM_OPTS_DIR
# Arguments:
# 1. Options env variable name
# 2. File containing options defaults
# Returns:
# Options
#######################################
function buildOpts() {
declare -n opts=$1
declare -n excludeOpts="EXCLUDE_$1"
local -r optsFile=$2
local x=
if [ -z ${excludeOpts+x} ]; then
while read -r o; do
if [ -n "${o// }" ]; then
x+=" $o"
fi
done <"$JVM_OPTS_DIR/$optsFile"
fi
if [ -n "$opts" ]; then
for o in $opts; do
x+=" $o"
done
fi
printf '%s' "$x"
}
# https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
# Standard options
stdOpts=$(buildOpts STD_OPTS std.opts)
...
javaCmd="java $stdOpts $nonStdOpts $advRtOpts $advCompOpts $advServOpts $advGcOpts -jar $APP_DIR/app.jar"
printf '%s' "$javaCmd"
# eval $javaCmd "$#"
The above script serves as a Docker entrypoint
docker build -t jdk . && docker run --rm -it jdk -e APP_NAME=test -e APP_LOG_DIR=test -e APP_DIR=test -e STD_OPTS='a b' -e EXCLUDE_STD_OPTS=true
However, I don't see a and b included in the javaCmd, neither do I see the EXCLUDE working. Basically, none of the if conditions in function buildOpts are working.
I'm a backend programmer, and not a Bash wizard. Help.
You need to put the envs flags before the image name.
Syntax of docker run is:
docker run [OPTIONS] IMAGE[:TAG|#DIGEST] [COMMAND] [ARG...]
Anything after the image name will be treated as [COMMAND][ARG...]

How can I export function in a function in bash?

I am learning bash. And I would like to make a function which wrap another function in a temporal script file and execute it with sudo -u command in sub-shell.
The problem I encountered is the generated script cannot find the wrapped function although it is exported in the wrap function.
I append test cords below. Someone who finds problems, please let me know. Thank you very much.
main.sh
source "./display.sh"
source "./sudo_wrap.sh"
display_func "load success"
sudo_wrap_func '' 'display_func' '3' '4 5'
output, display.sh, sudo_wrap.sh and generated temporal file are appended below,
output
display_func : load success
export -f display_func
30481: line 5: display_func: command not found
display.sh
function display_func() {
echo "display_func : $#"
}
sudo_wrap.sh
function sudo_wrap_func() {
local sudo_user="${1:-root}"
local function_name="${2:?'function_name is null string.'}"
shift 2
local func_augs=( "$#" )
local script
# *** script : header ***
script="#!/bin/bash\n"
script="${script}\n"
# *** script : making augments for function ***
script="${script}augs=("
for aug in "${func_augs[#]}"
do
if [[ "${aug}" =~ [[:blank:]] ]]; then
script=" ${script} \"${aug}\""
else
script=" ${script} ${aug}"
fi
done
script="${script})\n"
local tmp_script_file="${RANDOM}"
echo -e "${script}" >> "${tmp_script_file}"
# *** script : calling function with augments ***
echo -e "${function_name} \"\${augs[#]}\"\n" >> "${tmp_script_file}"
echo "export -f "${function_name}"" >&2
export -f "${function_name}"
sudo -u"${sudo_user}" bash "${tmp_script_file}"
rm "${tmp_script_file}"
}
temporally generated file (in this case, file name is 30481)
#!/bin/bash
augs=( 3 "4 5")
display_func "${augs[#]}"
As I said in a comment, the basic problem is that sudo cleans its environment (including both variables and functions) before running the command (/script) as another user. This can be overridden with sudo -E, but only if it's explicitly allowed in /etc/sudoers.
But the problem is not insoluble; you just have to include the definition of the function in the script, so it gets recreated in that environment. bash even has a convenient command, declare -f display_func, that prints the function definition in the appropriate form (and declare -p variable does the same for variables). So you can use those to add the appropriate definitions to the script.
Here's a script I wrote to do this. I made a few other changes vs. your script: I take -u username to specify a different user to run as (so you don't have to pass '' as the first argument if you don't want to specify a different user). I also added -f functionname and -v variablename to "export" additional function and variable definitions into the script (in case the main function depends on them). I also create the temp script file in /tmp, and change ownership if necessary so it'll be readable by the other user.
#!/bin/bash
me="$(basename "$0")"
usage() {
echo "Usage: $me [-u user] [-f otherfunction] [-v variablename] function [args...]" >&2
}
tmp_script_file=$(mktemp "/tmp/${me}.XXXXXXXXXXXX") || {
echo "Error creating temporary script file" >&2
exit 1
}
echo "#!/bin/bash" > "$tmp_script_file" # Not actually needed, since we'll run it with "bash"
# Parse the command options; "-u" gets stored for later, but "-f" and "-v" write
# the relevant declarations to the script file as we go.
sudo_user=""
while getopts u:f:v: OPT; do
case "$OPT" in
u)
sudo_user="$OPTARG" ;;
f)
declare -f "$OPTARG" >>"$tmp_script_file" || {
echo "Error saving definition of function $OPTARG" >&2
exit 1
} ;;
v)
declare -p "$OPTARG" >>"$tmp_script_file" || {
echo "Error saving definition of variable $OPTARG" >&2
exit 1
} ;;
?) usage; exit 1 ;;
esac
done
shift $(($OPTIND-1))
if (( $# == 0 )); then # No actual command specified
usage
exit 1
fi
# Write the main function itself into the script
declare -f "$1" >>"$tmp_script_file" || {
echo "Error saving definition of function $1" >&2
exit 1
}
# Then the command to run it, with arguments quoted/escaped as
# necessary.
printf "%q " "$#" >>"$tmp_script_file"
# the printf above won't write a newline, so add it by hand
echo >>"$tmp_script_file"
# If the script will run as someone other than root, change ownership of the
# script so the target user can read it
if [[ -n "$sudo_user" ]]; then
sudo chown "$sudo_user" "$tmp_script_file"
fi
# Now launch the script, suitably sudo'ed
sudo ${sudo_user:+ -u "$sudo_user"} bash "$tmp_script_file"
# Clean up
sudo rm "$tmp_script_file"
Here's an example of using it:
$ foo() { echo "foo_variable is '$foo_variable'"; }
$ bar() { echo "Running the function bar as $(whoami)"; echo "Arguments: $*"; foo; }
$ export -f foo bar # need to export these so the script can see them
$ export foo_variable='Whee!!!' # ditto
$ # Run the function directly first, so see what it does
$ bar 1 2 3
Running the function bar as gordon
Arguments: 1 2 3
foo_variable is 'Whee!!!'
$ # Now run it as another user with the wrapper script
$ ./sudo_wrap.sh -f foo -v foo_variable -u deenovo bar 1 2 3
Running the function bar as deenovo
Arguments: 1 2 3
foo_variable is 'Whee!!!'
Note that you could remove the need to export the functions and variables by either running the script with source or making it a function, but doing that would require changes to how $me is defined, the usage function, replacing all those exits with returns, and maybe some other things I haven't thought of.

Check if bash command has specified modificator

During the configuration of Symfony 2 project it is required to set appropriate privilages to the cache and log directories.
Documentation says to do it in two ways. One of them is calling setfacl command with -m modificator. However not every version contains this modificator. Is it possible to check if this command or any other command allows to set some modificator ?
For example with following pseudocode:
if [ checkmods --command=setfacl --modificator=-m ]
setfacl -m ....
else
chmod ...
You can parse the usage information by running setfacl --help and check if contains the modificator. For example:
if setfacl --help | grep -q -- -m,
then
echo "setfacl -m supported"
else
echo "setfacl -m not supported"
fi
If you want to do it for any command which has the --help option, take a look at the _parse_help function available in your bash-completion file.
http://anonscm.debian.org/gitweb/?p=bash-completion/bash-completion.git;a=blob;f=bash_completion
# Parse GNU style help output of the given command.
# #param $1 command; if "-", read from stdin and ignore rest of args
# #param $2 command options (default: --help)
#
_parse_help()
{
eval local cmd=$( quote "$1" )
local line
{ case $cmd in
-) cat ;;
*) LC_ALL=C "$( dequote "$cmd" )" ${2:---help} 2>&1 ;;
esac } \
| while read -r line; do
[[ $line == *([ $'\t'])-* ]] || continue
# transform "-f FOO, --foo=FOO" to "-f , --foo=FOO" etc
while [[ $line =~ \
((^|[^-])-[A-Za-z0-9?][[:space:]]+)\[?[A-Z0-9]+\]? ]]; do
line=${line/"${BASH_REMATCH[0]}"/"${BASH_REMATCH[1]}"}
done
__parse_options "${line// or /, }"
done
}

Resources