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

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"

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

Bash script runs one command before previous. I want them one after the other

So part of my script is as follows:
ssh user#$remoteServer "
cd ~/a/b/c/;
echo -e 'blah blah'
sleep 1 # Added this just to make sure it waits.
foo=`grep something xyz.log |sed 's/something//g' |sed 's/something-else//g'`
echo $foo > ~/xyz.list
exit "
In my output I see:
grep: xyz.log: No such file or directory
blah blah
Whereas when I ssh to the server, xyz.log does exist within ~/a/b/c/
Why is the grep statement getting executed before the echo statement?
Can someone please help?
The problem here is that your command in backticks is being run locally, not on the remote end of the SSH connection. Thus, it runs before you've even connected to the remote system at all! (This is true for all expansions that run in double-quotes, so the $foo in echo $foo as well).
Use a quoted heredoc to protect your code against local evaluation:
ssh user#$remoteServer bash -s <<'EOF'
cd ~/a/b/c/;
echo -e 'blah blah'
sleep 1 # Added this just to make sure it waits.
foo=`grep something xyz.log |sed 's/something//g' |sed 's/something-else//g'`
echo $foo > ~/xyz.list
exit
EOF
If you want to pass through a variable from the local side, the easy way is with positional parameters:
printf -v varsStr '%q ' "$varOne" "$varTwo"
ssh "user#$remoteServer" "bash -s $varsStr" <<'EOF'
varOne=$1; varTwo=$2 # set as remote variables
echo "Remote value of varOne is $varOne"
echo "Remote value of varTwo is $varTwo"
EOF
[command server] ------> [remote server]
The better way is to create shell script in the "remote server" , and run the command in the "command server" such as :
ssh ${remoteserver} "/bin/bash /foo/foo.sh"
It will solve many problem , the aim is to make things simple but not complex .

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)

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