如何正确编写用于SSH远程执行命令的Shell函数?
我明白你想做的事——写一个通用函数,把它当前缀用,后面跟的命令就能直接在远程服务器上执行对吧?先说说你原来写法的问题:你用< ${cmdArgs[@]}是想把命令内容传给远程的bash -s,但这个语法是重定向文件用的,不是传递参数内容,所以才会报“ambiguous redirect”错误。
你后来改的写法虽然能跑,但用$*有隐患——$*会把所有参数拼成一个字符串,要是参数里有空格、引号或者特殊字符,远程shell解析的时候很容易出错。比如如果${webserverUser}是带空格的名字(比如"web admin"),$*会把它拆成两个参数,远程的sudo会把admin当成额外参数,直接报错。
给你几个更靠谱的写法,按场景选:
最简单直接的写法
如果远程服务器的默认shell是bash(或者你不需要特殊的bash特性),直接把参数原封不动传给ssh就行,这是最稳妥的方式:
function NC_SSH_CMD () { ssh root@myserver.intern "$@" }
用"$@"的好处是,它会保留每个参数的边界——你调用函数时传的每个空格、引号、特殊字符,都会原原本本传递到远程服务器。比如你原来的调用:
NC_SSH_CMD sudo -u "${webserverUser}" php ${nextcloudFileDir}/occ maintenance:mode --on
每个参数(包括带变量的部分)都会被正确解析,完全不会乱。
需要用bash -s的场景
如果你必须用bash -s(比如远程默认shell不是bash,或者需要用到bash的特定语法),可以这么写:
function NC_SSH_CMD () { ssh root@myserver.intern bash -s -- "$@" }
这里的--是告诉bash:“后面的都是命令参数,别把它们当成我的选项”,避免命令里有-开头的参数被bash误判。同样用"$@"保证参数的完整性。
另一种标准输入传递的写法
如果你的命令需要从标准输入读取内容,也可以用管道把参数传过去,但要注意用null字符分隔避免空格问题:
function NC_SSH_CMD () { printf '%s\0' "$@" | ssh root@myserver.intern bash -s -- }
不过这种场景比较少见,一般前面两种写法就够了。
总结一下:优先用ssh ... "$@"的写法,既简单又能避免绝大多数参数解析问题,比你用$*的写法可靠多了。
备注:内容来源于stack exchange,提问作者Bud Spencer

