SSH remote execution - How to declare a variable inside EOF block (Bash script) - linux

I have the following code in a bash script:
remote_home=/home/folder
dump_file=$remote_home/my_database_`date +%F_%X`.sql
aws_pem=$HOME/my_key.pem
aws_host=user#host
local_folder=$HOME/db_bk
pwd_stg=xxxxxxxxxxxxxxxx
pwd_prod=xxxxxxxxxxxxxxx
ssh -i $aws_pem $aws_host << EOF
mysqldump --column-statistics=0 --result-file=$dump_file -u user -p$pwd_prod -h $db_to_bk my_database
mysql -u user -p$pwd_prod -h $db_to_bk -N -e 'SHOW TABLES from my_database' > $remote_home/test.txt
sh -c 'cat test.txt | while read i ; do mysql -u user -p$pwd_prod -h $db_to_bk -D my_database --tee=$remote_home/rows.txt -e "SELECT COUNT(*) as $i FROM $i" ; done'
EOF
My loop while is not working because "i" variable is becoming empty. May anyone give me a hand, please? I would like to understand how to handle data in such cases.

The local shell is "expanding" all of the $variable references in the here-document, but AIUI you want $i to be passed through to the remote shell and expanded there. To do this, escape (with a backslash) the $ characters you don't want the local shell to expand. I think it'll look like this:
ssh -i $aws_pem $aws_host << EOF
mysqldump --column-statistics=0 --result-file=$dump_file -u user -p$pwd_prod -h $db_to_bk my_database
mysql -u user -p$pwd_prod -h $db_to_bk -N -e 'SHOW TABLES from my_database' > $remote_home/test.txt
sh -c 'cat test.txt | while read i ; do mysql -u user -p$pwd_prod -h $db_to_bk -D my_database --tee=$remote_home/rows.txt -e "SELECT COUNT(*) as \$i FROM \$i" ; done'
EOF
You can test this by replacing the ssh -i $aws_pem $aws_host command with just cat, so it prints the here-document as it'll be passed to the ssh command (i.e. after the local shell has done its parsing and expansions, but before the remote shell has done its). You should see most of the variables replaced by their values (because those have to happen locally, where those variables are defined) but $i passed literally so the remote shell can expand it.
BTW, you should double-quote almost all of your variable references (e.g. ssh -i "$aws_pem" "$aws_host") to prevent weird parsing problems; shellcheck.net will point this out for the local commands (along with some other potential problems), but you should fix it for the remote commands as well (except $i, since that's already double-quoted as part of the SELECT command).

Related

Sql in bash script (postgres)

i have a command in bash script for rename base.
It's work, example
psql -U $User -t -A -q -c 'ALTER DATABASE "Old_Name" RENAME TO "New_Name"'
But if i do this -
O_Name='Old_Name'
N_Name='New_Name'
psql -U $User -t -A -q -c 'ALTER DATABASE "$O_Name" RENAME TO "$N_Name"'
It's not work, i think sql get $O_Name not Old_Name.
How to pass the value of a variable bash to sql?
Single quotes don't allow for environment variable expansion. Use double quotes instead (and escape the nested quotes). Like,
psql -U $User -t -A -q -c "ALTER DATABASE \"$O_Name\" RENAME TO \"$N_Name\""

BCP Command on Shell (.sh file)

I have a .sh script that do this:
bcp "EXEC SPName" queryout "test.csv" -k -w -t"," -S "$server" -U "$user" -P "$pass"
The variables $server, $user and $pass are being read from a external config file.
The problem is that the variables don't work and give me always connection timeout. For example if I use the same command but with the variables hard coded works fine:
bcp "EXEC SPName" queryout "test.csv" -k -w -t"," -S "TEST" -U "admin" -P "admin"
How I can make the command dynamic?
I found the problem, I was reading the variables from a external json file created in Windows and the file contained "\r" at the end and then the command could not execute.
How I solved:
sed -i 's/\r//g' YourFile.json

Open new gnome-terminal from scripts and input vars from present script.

#!/bin/bash
Dpath=/home/$USER/Docker/
IP=`sed -n 1p /home/medma/.medmadoc`
DockerMachine=`sed -n 2p /home/$USER/.medmadoc`
DockerPort=`sed -n 5p /home/$USER/.medmadoc`
DockerUser=`sed -n 3p /home/$USER/.medmadoc`
DockerPass=`sed -n 4p /home/$USER/.medmadoc`
if [ ! -d $Dpath ] ; then
mkdir -p $Dpath
else
stat=`wget -O ".dockerid" http://$IP/DOCKER-STAT.txt`
for ids in `cat .dockerid`
do
if [ "$ids" == "$DockerMachine" ] ; then
gnome-terminal -x sh -c 'sshfs -p$DockerPort $DockerUser#$IP:/var/www/html $Dpath ; bash '
nautilus $Dpath
zenity --info --text "Mounted $DockerMachine"
exit
else
:
fi
done
zenity --info --text "No Such ID:$DockerMachine"
fi
gnome-terminal -x sh -c 'sshfs -p$DockerPort $DockerUser#$IP:/var/www/html $Dpath ; bash '
this command opens up a new terminal but the problem is that it does not load vars like $DockerPort $DockerUser $IP $Dpath from this script.
How do I input the values in these vars from this script to the newly opened terminal ?
Thanks !
As indicated before, you could try to use double quotes instead of single quotes around the sshfs invocation.
Single quotes in Bash are used to delimit verbatim text, in which variables are not expanded. Double quotes, in contrast, allow for variables expansion and command substitution ($(...)) to take place.
If you do use double quotes, beware of unintended side-effects (your username may contain a space, a dollar, a semicolon, or any other shell-special character). A cleaner approach would be to export the variables to the environment before calling gnome-terminal (and not forgetting to add double quotes around your variables inside the single-quotes), so that your code looks like :
export Docker{Port,User} IP Dpath
gnome-terminal -x sh -c 'sshfs -p"$DockerPort" "$DockerUser#$IP":/var/www/html "$Dpath" ; bash'
You may not want to pollute the environment with variables that will only be used once. If that is the case, instead of exporting them, you can use Bash's declare -p feature to serialize variables before loading them into a new environment (in my opinion, this is the cleanest approach). Here is what it looks like :
set_vars="$(declare -p Docker{Port,User} IP Dpath)"
gnome-terminal -x bash -c "$set_vars;"'sshfs ....'
Using this latest method, the variables are only visible to the shell process that runs the sshfs command, not gnome-terminal itself nor any sub-process run thereafter.
PS: you could read all your variables at once from the ~/.medmadoc file by using the following code instead of repeated sed invocations :
for var in IP Docker{Machine,User,Pass,Port}; do
read $var
done < ~/.medmadoc
This code makes use of the read builtin, that reads a line of input into a variable (in its simplest form).
PPS: That stat variable probably won't contain any useful information, since the output of wget was redirected by the -O flag. Perhaps you meant to store the result code of wget into stat, in which case what you meant was :
wget -O .dockerid ...
stat=$?

Why bash script breaks if it meets space in this example?

I need to execute following command on multiple servers:
mysql -h 127.0.0.1 -uroot -psecret mydatabase -e 'SELECT 1;'
So, i have test1.sh script, which echo-es dynamic string:
#!/bin/bash
echo -n "mysql -h 127.0.0.1 -uroot -psecret mydatabase -e 'SELECT 1'"
And test2.sh script, who executes the given string:
#!/bin/bash
CMD=`./test1.sh`
$CMD
If i execute `./test2.sh, i will see help output, command will be not executed.
If i remove spaces in mysql query SELECT 1 or the whole -e param, and then execute ./test2.sh script, everything works.
Why this is happening? Can you please describe this magic?
My bash version is 4.2.46.
As long as you control and trust command line coming from test1.sh, you can use dreaded eval in test2.sh like this:
#!/bin/bash
cmd="$(./test1.sh)"
eval "$cmd"
Why and when should eval use be avoided in shell scripts?
Can you try test1.sh script as like this
#!/bin/bash
echo -e "mysql -h 127.0.0.1 -uroot -psecret mydatabase -e"
test2.sh
#!/bin/bash
CMD=$(./test1.sh)
${CMD} "SELECT 1"

bash adds escape chars when passing a string with quotes

I'm trying to run a msyql command which includes a quoted string, for eg:
mysql -h host -u=user -p=pass -e "show tables" database.
Here I'm having difficulty to pass "show tables" with quotes to a fuction that executes the command.
run_cmd() # Run a command
{
local output=$1
local timeout=$2
shift 2
( $* > $output 2>&1 ) &
# ....
}
# Query mysql
query_mysql(){
local mysql_cmd=( "mysql -h $host --user=$user --password=$pass -e "show tables" $db")
run_cmd $output $timeout "${mysql_cmd[#]}"
}
query_mysql
I tried many combinations, however the command get executed either without quotes or with multiple single/double/escape chars. In all cases the final command becomes invalid due to missing/additional quotes/chars.
few of my attempts & the final command:
"show tables"
mysql -h localhost --user=root --password=foo -e show tables db1
'show tables'
mysql -h localhost --user=root --password=foo -e ''\''show' 'tables'\''' db1
\"show tables\"
mysql -h localhost --user=root --password=foo -e '"show' 'tables"' db1
Any suggestions to pass the quoted string as is?
Thanks in advance!
Don't quote unrelated words inside the array assignment. So use
local mysql_cmd=( mysql -h "$host" --user="$user" --password="$pass" -e "show tables" "$db")
instead of
local mysql_cmd=( "mysql -h $host --user=$user --password=$pass -e "show tables" $db")
See the difference between yours
$ mysql_cmd=( "mysql -h $host --user=$user --password=$pass -e "show tables" $db")
$ printf %q\\n "${mysql_cmd[#]}"
mysql\ -h\ host\ --user=user\ --password=pass\ -e\ show
tables\ db
and mine
$ mysql_cmd=( mysql -h "$host" --user="$user" --password="$pass" -e "show tables" "$db")
$ printf %q\\n "${mysql_cmd[#]}"
mysql
-h
host
--user=user
--password=pass
-e
show\ tables
db
Also don't use unquoted $* when you execute the command. You probably want to use "$#" instead. So
( "$#" > $output 2>&1 ) &
which doesn't actually need the sub-shell () and can probably just be
"$#" > $output 2>&1 &
See http://mywiki.wooledge.org/BashFAQ/050 for more details about why this array command usage needs to work this way (and why you were having such trouble with your attempts).

Resources