为何bash脚本中连续两个制表符加EOF会列出当前目录文件?
这个问题本质是bash的**变量展开与文件名通配(globbing)**规则在搞鬼,结合ssh远程命令的特殊执行逻辑,就会出现你看到的现象,我来拆解一下:
1. 核心原因:本地bash提前展开了here doc内容
当你在双引号包裹的ssh命令里使用<<EOF的here doc时,本地的bash会先对here doc里的所有内容进行预处理——包括变量替换(比如${tmpdb})、文件名通配(比如*、?这类符号)。
如果你的here doc内容里不小心出现了通配符(哪怕是连续制表符附近的内容被误解析),bash就会把它展开成当前目录的文件列表,然后再把展开后的内容传给远程的ssh和mysql,这就导致了“列出当前目录文件”的奇怪现象。
2. 你的两种写法的区别与注意点
写法一:标准双引号包裹的here doc
clear-mysql(){ mysqlword="xxxxx" port="22" ip="11.22.33.44" tmpdb="xxxx" ssh -p $port root@$ip "mysql -u root -p$mysqlword <<EOF use ${tmpdb}; UPDATE xx SET yy = replace(post_content, 'domain', '"http://$ip/wp"'); EOF" }
这种写法里,本地bash会提前展开${tmpdb}和$ip,这在你需要把本地变量传给远程时是有用的,但要绝对避免here doc里出现通配符(比如*),否则会被本地展开成文件列表。另外注意:root@{$ip}里的大括号是多余的,直接写root@$ip就可以。
写法二:用<<-支持缩进的here doc
<<-是bash对here doc的扩展,它的作用是允许结束分隔符(比如EOF)前面有制表符(注意只能是制表符,不能是空格),这样你可以缩进here doc的内容,让脚本代码更整洁:
clear-mysql(){ mysqlword="xxxxx" port="22" ip="11.22.33.44" tmpdb="xxxx" ssh -p $port root@$ip "mysql -u root -p$mysqlword <<-EOF use ${tmpdb}; UPDATE xx SET yy = replace(post_content, 'domain', '"http://$ip/wp"'); EOF" }
这里的结束EOF前面的制表符会被bash忽略,不会影响here doc的结束判断。但要注意,同样存在本地bash提前展开内容的问题,如果要避免本地展开,应该把here doc的分隔符用单引号引起来,比如<<-'EOF',这样本地bash会原封不动把here doc内容传给远程:
clear-mysql(){ mysqlword="xxxxx" port="22" ip="11.22.33.44" tmpdb="xxxx" ssh -p $port root@$ip "mysql -u root -p$mysqlword <<-'EOF' use ${tmpdb}; UPDATE xx SET yy = replace(post_content, 'domain', 'http://$ip/wp'); EOF" }
不过这种写法下,${tmpdb}和$ip会被远程的bash展开,所以如果这些变量是本地定义的,就不适合用这种方式。
3. 如何避免“列出当前目录文件”的问题
- 如果需要本地变量传递给远程:保持双引号包裹ssh命令,但确保here doc里没有任何通配符(比如
*、?、[]等)。 - 如果不需要本地展开,希望原封不动传递内容:把here doc的分隔符用单引号引起来(
<<'EOF'),或者把整个ssh命令用单引号包裹。 - 用
<<-时,确保结束分隔符前面只有制表符,不要混有空格,同时注意展开规则。
内容的提问来源于stack exchange,提问作者user7988893

