bash adds escape chars when passing a string with quotes - string

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).

Related

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

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).

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

Passing a quoted string to ssh

So, i want to run this command in terminal
pssh -h hosts -i "echo "DenyUsers $1" >> /etc/ssh/sshd_config && service sshd config"
as you can see, " before echo words will be broken and it will be ended by " before DenyUsers $1 command.
I have changed " before echo and after config words and it doesn't still work like what i want.
I am newcomer in this scripting and i don't know what keywords should i put into the search engine :-)
Doing this in a manner that is safe even if you don't trust your input is a bit more involved.
Use printf %q to generate an eval-safe version of your data:
#!/usr/bin/env bash
# ^^^^- Requires an extension not available in /bin/sh
# printf %q is also available on ksh, but there you would write:
# echo_str=$(printf 'DenyUsers %q' "$1")
# cmd=$(printf '%q ' printf '%s\n' "$echo_str")
# as the ksh version doesn't have -v, but optimizes away the subshell instead.
printf -v echo_str 'DenyUsers %q' "$1"
printf -v cmd '%q ' printf '%s\n' "$echo_str"
pssh -h hosts -i "$cmd >> /etc/ssh/sshd_config && service sshd config"
Note that printf is used instead of echo for greater predictability; see the APPLICATION USAGE section of the POSIX specification for echo.
Did you try
pssh -h hosts -i "echo \"DenyUsers $1\" >> /etc/ssh/sshd_config && service sshd config"
or
pssh -h hosts -i 'echo "DenyUsers $1" >> /etc/ssh/sshd_config && service sshd config'
If the source of $1 can be trusted, then you can simply escape the inner double quotes with \:
pssh -h hosts -i "echo \"DenyUsers $1\" >> /etc/ssh/sshd_config && service sshd config"
The drawback to the approach above is what happens if the $1 expands to something malicious, for example, to $(rm -fr *). Then, /etc/ssh/sshd_config will end up containing:
echo "DenyUsers $(rm -fr *)"
which will run rm -fr * when executed.
For this reason, consider this answer for a safer solution based on printf %q.

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"

shell save MySQL query result to a file

I have a script which runs a MySQL query, something like this:
#!/bin/sh
user="root"
pwd="test"
database="mydb"
command="long...
long... query in
multiple lines"
mysql -u $user -p$pwd << EOF
use $database;
$command
EOF
This query does a backup from a table to another. Is it possible to save the query result in a file without using mysql INTO OUTFILE? I only want to know if the query failed or succeeded.
If it succeeded something like 1 row(s) affected or if it failed Error Code: 1064. You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ...
Update
Solution 1: () act as a chain of commands, so wrapping them in a variable gets the result of those commands. Then simply output the result of that variable in a file.
output=$( mysql -u $user -p$pwd << EOF
use $database;
$command
EOF
)
echo "$output" >> /events/mysql.log
Solution 2: use tee as system command to send the result of the commands into a file, but this needs to be done from the crontab, like this:
*/1 * * * * root sh /events/mysql.sh |tee -a /events/mysql.log
http://forums.mysql.com/read.php?10,391070,391983#msg-391983
My working solution:
user="root"
pwd="root12345"
database="mydb"
command="long ...long query"
mysql -u $user -p$pwd << EOF >> /events/mysql.log 2>&1
use $database;
$command;
EOF
This should work:
output=$( mysql -u $user -p$pwd << EOF
use $database;
$command
EOF
)
echo "$output" >> outfile
The correct way to handle error-messages is to through stderr. Use 2>&1 to catch the error.
So, add this to the end of your script:
>> install.log 2>&1
you can also do it like this:
#!/bin/sh
user="root"
pwd="test"
database="mydb"
command="long...
long... query in
multiple lines"
mysql -u$user -p$pwd -D$database -e "$command" > file
It's easier to use the MySQL tee command to send output.
To get the logging process started, just use the tee command,
tee /tmp/my.out;
#!/bin/sh
user="root"
pwd="test"
database="mydb"
$pathToFile = "/tmp/my.out"
command="long...
long... query in
multiple lines"
mysql -u $user -p$pwd << EOF
tee $pathToFile
use $database;
$command
EOF
EDIT
Since tee is reportedly not working inside script, you could also log output using Tee directly when running the script.
mysql_bash.sh > >(tee -a script.log)

Resources