为什么 csh 脚本通过 SQLPLUS 只执行部分 .sql 脚本?
结论
先检查 script1.sql 和 script3.sql 是否读取了标准输入,重点看 PAUSE、ACCEPT、未赋值的替换变量和分页暂停设置。
这些操作可能把后面的 @script2.sql ...、@script4.sql ... 当作交互输入读走,导致 SQL*Plus 不再执行这两行脚本调用。这能解释“去掉前面的脚本后,脚本 2、4 又能运行”的现象。
缺少各个 .sql 文件的内容,还无法确认根因,可以先从这里排查。现有信息也不足以判断问题是否出在 Solaris 11.4 或 csh 上。
为什么单独执行正常,连续执行却出问题?
六次 @ 调用共用一个 SQLPlus 进程。正常情况下,SQLPlus 按顺序执行,每个脚本结束后再处理下一条命令。
here-document 中的内容也是 SQL*Plus 的标准输入:
sqlplus -s /nolog << EOF
...
EOF
如果 script1.sql 中有这样的语句:
PAUSE Press Return to continue
在标准输入重定向的情况下,暂停操作可能把后面这一行读作响应:
@/path/to/script/script2.sql input1 input2
这一行被读走后,SQL*Plus 接下来读到的就是 script3.sql 的调用。如果 script3.sql 中也有类似操作,script4.sql 就可能同样被跳过。
单独运行时,用户会手动按回车或输入参数,因此看不出这个问题。单独执行成功,只能说明脚本在当时的输入和会话状态下可以运行。
如何定位
1. 检查前一个脚本及其嵌套调用
检查 script1.sql、script3.sql,以及它们通过 @、@@ 或 START 调用的其他文件,重点查找:
PAUSE。SET PAUSE ON,查询结果分页时可能等待输入。ACCEPT。- 尚未定义的
&name、&&name或位置参数。例如,脚本用了&3,调用时却只传入两个参数。 - 通过
HOST启动且会读取标准输入的外部程序。
SET PAUSE OFF 只关闭分页暂停,不能取消脚本中显式写出的 PAUSE 命令。
2. 增加执行标记,保留完整日志
排查时先去掉 -s,设置 SET ECHO ON、SET TERMOUT ON,并在每次调用前后加入标记:
SET ECHO ON
SET TERMOUT ON
PROMPT BEGIN script1
@/path/to/script/script1.sql input1 input2
PROMPT END script1
PROMPT BEGIN script2
@/path/to/script/script2.sql input1 input2
PROMPT END script2
在 csh 中运行,并将标准输出和标准错误合并保存到日志:
csh -f run.csh >& run.log
如果脚本中确实有读取标准输入的命令,新加的 PROMPT 行也可能被读走,让原本跳过的脚本恢复执行。增加日志后,运行结果发生变化也是排查线索,不能据此认为问题已经修复。
SET ECHO ON 有助于显示通过 @ 执行的脚本内容,但子脚本可能再次关闭它。必要时,在可疑位置临时恢复这些设置。
3. 区分“没有执行”和“没有显示输出”
如果日志确认已经进入脚本,却没有生成预期文件,再检查:
- 前一个脚本是否执行了
SET TERMOUT OFF。 SPOOL的文件名、路径和关闭位置是否正确。- 多个脚本是否写入同一个文件,导致输出被后续脚本覆盖。
- 是否出现
ORA-、PLS-或SP2-错误。
还要检查 EXIT、QUIT 和 WHENEVER ... EXIT,它们会影响整个 SQL*Plus 进程。不过,提前退出通常会使后面的脚本全部停止,与当前“只少了 2、4”的现象不完全一致。
如何修复
批处理脚本应避免等待人工输入:
- 删除或绕过批处理路径中的
PAUSE,并关闭分页暂停。 - 将通过
ACCEPT获取的值改为显式传入的参数。 - 补齐所有替换变量,避免 SQL*Plus 在运行时询问变量值。
- 检查子脚本修改的 SQL*Plus 设置,避免影响后续调用。
例如,脚本可以直接使用调用时传入的两个参数:
-- script1.sql
SELECT '&1' AS input1, '&2' AS input2
FROM dual;
对应调用为:
@/path/to/script/script1.sql input1 input2
如果脚本依赖 &1、&2,加入 SET DEFINE OFF 会关闭替换功能,使这些参数失效,因此不能直接用它消除输入提示。
csh 调用示例
清理交互命令后,可以保留原来的 here-document 结构,加入执行标记和错误处理:
#!/bin/csh -f
set pw = "replace_with_password"
sqlplus -L /nolog << EOF
whenever oserror exit failure rollback
whenever sqlerror exit failure rollback
connect your_username/${pw}
set echo on
set termout on
set pause off
prompt BEGIN script1
@/path/to/script/script1.sql input1 input2
prompt END script1
prompt BEGIN script2
@/path/to/script/script2.sql input1 input2
prompt END script2
prompt BEGIN script3
@/path/to/script/script3.sql input1 input2
prompt END script3
prompt BEGIN script4
@/path/to/script/script4.sql input1 input2
prompt END script4
prompt BEGIN script5
@/path/to/script/script5.sql input1 input2
prompt END script5
prompt BEGIN script6
@/path/to/script/script6.sql input1 input2
prompt END script6
exit success
EOF
set sqlplus_status = $status
exit $sqlplus_status
在没有错误、没有交互读取且子脚本正常返回的情况下,这段代码会依次执行六个脚本。遇到上述错误处理配置所指定的错误时,SQL*Plus 会停止执行,并把失败状态返回给 csh。
使用时还需注意:
WHENEVER SQLERROR不能捕获所有 SQL*Plus 命令错误,仍要检查日志中的SP2-信息。EXIT SUCCESS默认提交未提交的事务;如果业务要求正常结束时回滚,应明确使用EXIT SUCCESS ROLLBACK。- 子脚本中的
EXIT会结束整个 SQL*Plus 进程。可复用的子脚本通常应运行到文件末尾后返回。 - 在调用之间插入空行可能暂时满足一次输入等待,但交互读取仍然存在,不建议将其作为修复方法。
备注:内容仅供参考。
