Shell脚本中连接数据库查询数据报错 "No such file or directory"以及函数传参数组
- 前言
- [问题1: 执行mysql命令,报错"No such file or directory"](#问题1: 执行mysql命令,报错"No such file or directory")
- [问题2: mysql的静默输出](#问题2: mysql的静默输出)
- [问题3 函数的参数是数组,调用问题](#问题3 函数的参数是数组,调用问题)
- 总结
前言
业务要求,编写一个Shell脚本,目的是定时从 MySQL 数据库中拉取业务数据,填入 CSV 报表中,并每日凌晨定时推送到远程平台。
在脚本调试过程中遇到了几个问题(Bash语法坑),好久不写Shell了生疏了......
问题1: 执行mysql命令,报错"No such file or directory"
报错
脚本中定义了一个数组用来存放 MySQL 的连接命令和参数,但在执行时报错:
sh
# 第一种
MYSQL_CONN='/path/to/mysql -uuser -ppassword -S /tmp/mysql.sock dbname'
# ... 省略中间逻辑 ...
"${MYSQL_CONN}" -e "${sql}"
# 第二种
MYSQL_CONN=(/path/to/mysql -uuser -ppassword -S /tmp/mysql.sock dbname)
# ... 省略中间逻辑 ...
"${MYSQL_CONN[*]}" -e "${sql}"
原因分析
第一种因为我的password里特殊字符如@$符号,如mysql -uuser -p"passw@rd",shell 分词混乱会把密码分割开导致命令执行失败;
从网上查询的第二种方法,我明明 ls 看了,/path/to/mysql 这个文件是绝对存在的,为什么 Shell 还是说找不到文件?
问题的根源在于 ${MYSQL_CONN[*]} 中的这个 *。
在 Bash 中,"${arr[*]}" 会把数组中的所有元素合并成一个单一的字符串,中间默认用空格连接。Shell 在执行时,会试图去文件系统中寻找一个文件名叫 "mysql -uuser -ppassword..." 的文件。这当然找不到!文件名里怎么可能有 -u 这种参数呢?
解决方案
将 * 改为 @。"${arr[@]}" 会将数组元素展开为独立的参数,保留参数之间的分隔。这样 Shell 就会正确理解:第一个元素是命令,后面的元素是参数。
sh
"${MYSQL_CONN[@]}" -e "${sql}"
问题2: mysql的静默输出
问题
发MySQL 默认返回的结果带漂亮的表格边框,首先想到使用grep、awk等工具去处理
sh
+------------------+
| count(username) |
+------------------+
| 2 |
+------------------+
解决方案
不需要用 grep 或 awk 去切分字符串,直接利用 MySQL 自带的参数:
-N:不显示列头(Header)。
-s:静默模式(Silent),去掉边框线条。
问题3 函数的参数是数组,调用问题
问题
sh
fun_c(){
local para=$1
}
arr=(a,b)
fun_c $arr
在调用函数的时候,发现只有a的处理逻辑,b一直未处理。
原因分析
调用时:$arr 默认只取数组的第一个元素。
接收时:local para=$1 只读取了第一个参数。
解决方案
必须显式地展开数组,并在函数内部用 $@ 重新接收。
sh
fun_c(){
# 将传入的所有参数重新组装成数组
local para=("$@")
for i in ${para[@]}
}
arr=(a,b)
# 调用时:使用 [@] 并加引号
fun_c "${arr[@]}"
总结
原来一直以为${arr[@]}与${arr[*]}是一样的含义,这次发现完全不同,数组展开永远优先使用 "${arr[@]}"。
此外,函数传参传数组要 "${arr[@]}",要传数组所有元素;函数传数组要用 "$@" 重新接收所有元素,然后再处理数组。