您的方法存在几个问题:
您的 here 文档在本地进行插值。您正在运行的实际命令是:for w in var asd dsa 123 ; do echo >&2; echo "123"; done
您可以通过使用 set -x 来启用本地跟踪,并使用 cat 而不是登录 shell 运行 ssh:
ssh pi cat << EOF
for w in $(echo "/var/asd/dsa/123" | tr "/" " ") ; do echo $w >&2; echo "123"; done
EOF
+ ssh pi cat
++ echo /var/asd/dsa/123
++ tr / ' '
for w in var asd dsa 123 ; do echo >&2; echo "123"; done
如您所见,echo /var/asd/dsa/123 和 tr / ' ' 在本地执行,$w 被插入到空字符串中。
您可能希望在 'EOF' 周围使用单引号:
ssh pi cat << 'EOF'
for w in $(echo "/var/asd/dsa/123" | tr "/" " ") ; do echo $w >&2; echo "123"; done
EOF
+ ssh pi cat
for w in $(echo "/var/asd/dsa/123" | tr "/" " ") ; do echo $w >&2; echo "123"; done
现在,命令正确到达那里。
注意:如果您希望$(echo "/var/asd/dsa/123" | tr "/" " ") 在本地运行,那么您至少需要转义$w。
ssh pi << EOF
for w in $(echo "/var/asd/dsa/123" | tr "/" " ") ; do echo \$w >&2; echo "123"; done
EOF
缓冲会打乱顺序。这就是为什么您的空行(由echo >&2 打印的行)位于输出的末尾。您可以使用tee 来“强制”行缓冲:
ssh -T pi << 'EOF'
for w in $(echo "/var/asd/dsa/123" | tr "/" " ") ; do echo $w >&2; echo "123"; done
EOF
+ ssh -T pi
Linux mserv 3.10.25+ #622 PREEMPT Fri Jan 3 18:41:00 GMT 2014 armv6l
...
123
123
123
123
var
asd
dsa
123
对比:
ssh pi << 'EOF'
for w in $(echo "/var/asd/dsa/123" | tr "/" " ") ; do echo $w >&2; echo "123"; done 2>&1 | tee
EOF
+ ssh pi
Linux mserv 3.10.25+ #622 PREEMPT Fri Jan 3 18:41:00 GMT 2014 armv6l
...
var
123
asd
123
dsa
123
123
123
(在第一种情况下,首先打印 STDIN,然后是 STDERR,在第二种情况下,行交替)
管道命令到 ssh 并强制登录 shell
如果您通过将命令通过管道传输到 ssh 的标准输入来运行命令,它将运行一个登录 shell 并打印 MOTD(取决于服务器配置)并运行一个登录 shell(它有几个副作用)。
在我的示例中,Linux mserv 3.10.25+ #622 PREEMPT Fri Jan 3 18:41:00 GMT 2014 armv6l 是 MOTD 的一部分。但是,不需要管道,ssh 将通过用户 shell 运行最后一个参数,而不需要将其通过管道传输到 STDIN。这可能仅限于 MAX_ARG_STRLEN(约为 128K),但如果您有一个这么大的脚本,您可能应该先 scp-it 到主机。
$ ssh pi '
for w in $(echo "/var/asd/dsa/123" | tr "/" " ")
do
echo $w >&2
echo "123"
done 2>&1 | tee
'
var
123
asd
123
dsa
123
123
123
注意:这可能仅限于 MAX_ARG_STRLEN(约为 128K),但如果您有这样大小的脚本,您可能应该先 scp-it 到主机。
注意: pi 是我使用的测试服务器,请将其替换为您的适当连接信息。