You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Expect脚本exec执行mailx命令失败,为何需用sh -c?

问题描述

编写了一段Expect脚本,执行exec命令时出现异常——用puts输出命令内容显示正常,但exec执行时报错。脚本内容如下:

#!/usr/bin/expect -f

set recipients {
    "abc@abc.com"
    "xyz@abc.com"
}
set body "Success!"
set subject "File Upload Status"
set email_sender "EmailSender <Email.Sender@abc.com>"
set smtp_server "smtp://webmail.abc.com"
set recipient_list [join $recipients ","]
set cmd "echo \"$body\" | mailx -s \"$subject\" -r \"$email_sender\" -S smtp=$smtp_server \"$recipient_list\""
puts $cmd

# Execute the mailx command
exec $cmd

执行时的错误信息:

> ./test.expect
echo "Success!" | mailx -s "File Upload Status" -r "EmailSender <Email.Sender@abc.com>" -S smtp=smtp://webmail.abc.com "abc@abc.com,xyz@abc.com"
couldn't execute "echo "Success!" | mailx -s "File Upload Status" -r "EmailSender <Email.Sender@abc.com>" -S smtp=smtp:/webmail.abc.com "abc@abc.com,xyz@abc.com"": no such file or directory
    while executing
"exec $cmd"
    (file "./test.expect" line 16)

尝试给exec后的$cmd加单双引号都没用,而且错误信息里smtp地址少了一个/(但puts输出正常)。改成exec sh -c $cmd后脚本能正常运行,想问:哪里操作错了?为什么必须显式调用sh -c?有没有办法避免?

问题分析与解决

1. 核心错误原因

Expect的exec和shell的命令执行逻辑完全不一样:

  • shell会把整条命令拆解成主命令+参数+管道/重定向这些元语法来处理;
  • 但Expect的exec是直接把传入的字符串当成单个可执行文件的路径去查找,它根本不认识管道|、引号这些shell语法,也不会自动拆分参数。

你把带管道的整串命令传给exec,它就会把整个字符串当作一个文件名去找,自然找不到,所以报“no such file or directory”。至于错误信息里smtp地址少一个/,是Expect解析字符串时的转义问题:$cmd里smtp=$smtp_server展开后是smtp=smtp://webmail.abc.com,exec处理时会把//当成转义,最终变成smtp:/webmail.abc.com,但puts是直接输出原始字符串,所以显示正常。

2. 为什么sh -c $cmd能正常运行

sh -c是让系统shell来接管命令解析:

  • sh是系统里存在的可执行文件,-c是它的参数,后面的$cmd是shell要执行的命令字符串;
  • 此时shell会处理管道、引号、参数拆分这些逻辑,所以命令能正常执行。

3. 不用sh -c的替代方法

如果不想依赖shell,可以用Expect原生语法拆分命令,避开shell的元语法:

方法一:用管道直接传内容给mailx

# 把recipients列表直接展开成mailx的参数,不用逗号拼接
set mail_args [list -s $subject -r $email_sender -S smtp=$smtp_server {*}$recipients]
# 创建管道连接mailx,直接写入body内容
set pipe [open "|mailx {*}$mail_args" w]
puts $pipe $body
close $pipe

这里用{*}展开recipients列表,让每个邮箱作为单独的参数传给mailx,比用逗号拼接更规范;同时用open "|命令"创建管道,直接把body内容写入管道,不需要用echo。

方法二:用Expect的原生管道语法

# 直接在exec里写管道,两边命令分开写,不用拼成字符串
exec echo $body | mailx -s $subject -r $email_sender -S smtp=$smtp_server {*}$recipients

Expect的exec本身支持管道,但必须把管道两边的命令直接写在exec里,而不是拼成一个字符串。{*}用来展开recipients列表,确保每个邮箱都是独立参数。

内容的提问来源于stack exchange,提问作者user22157547

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 15:05:03