PAYATHON 2026

为什么 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”的现象不完全一致。

如何修复

批处理脚本应避免等待人工输入:

  1. 删除或绕过批处理路径中的 PAUSE,并关闭分页暂停。
  2. 将通过 ACCEPT 获取的值改为显式传入的参数。
  3. 补齐所有替换变量,避免 SQL*Plus 在运行时询问变量值。
  4. 检查子脚本修改的 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 进程。可复用的子脚本通常应运行到文件末尾后返回。
  • 在调用之间插入空行可能暂时满足一次输入等待,但交互读取仍然存在,不建议将其作为修复方法。

备注:内容仅供参考。