使用Java存储过程执行Shell命令。

  • Create the Java Stored Procedure
  • Publish the Java call specification
  • Grant Privileges
  • Test It.
  • Known Issues.
仅当无法通过其他方式(例如使用计划程序的外部作业)使用所需的功能时,才应将此作为最后的选择。如下文所述,您需要非常小心文件系统的授权和授予谁对此功能的访问权。如果使用不当,则可能被恶意损坏服务器上的文件或导致重大安全漏洞。

1.Create the Java Stored Procedure

首先,我们需要创建Java类来执行shell命令。

CONN test/test

CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED "Host" AS
import java.io.*;
public class Host {
  public static void executeCommand(String command) {
    try {
      String[] finalCommand;
      if (isWindows()) {
        finalCommand = new String[4];
        // Use the appropriate path for your windows version.
        //finalCommand[0] = "C:\\winnt\\system32\\cmd.exe";    // Windows NT/2000
        finalCommand[0] = "C:\\windows\\system32\\cmd.exe";    // Windows XP/2003
        //finalCommand[0] = "C:\\windows\\syswow64\\cmd.exe";  // Windows 64-bit
        finalCommand[1] = "/y";
        finalCommand[2] = "/c";
        finalCommand[3] = command;
      }
      else {
        finalCommand = new String[3];
        finalCommand[0] = "/bin/sh";
        finalCommand[1] = "-c";
        finalCommand[2] = command;
      }
  
      final Process pr = Runtime.getRuntime().exec(finalCommand);
      pr.waitFor();

      new Thread(new Runnable(){
        public void run() {
          BufferedReader br_in = null;
          try {
            br_in = new BufferedReader(new InputStreamReader(pr.getInputStream()));
            String buff = null;
            while ((buff = br_in.readLine()) != null) {
              System.out.println("Process out :" + buff);
              try {Thread.sleep(100); } catch(Exception e) {}
            }
            br_in.close();
          }
          catch (IOException ioe) {
            System.out.println("Exception caught printing process output.");
            ioe.printStackTrace();
          }
          finally {
            try {
              br_in.close();
            } catch (Exception ex) {}
          }
        }
      }).start();
  
      new Thread(new Runnable(){
        public void run() {
          BufferedReader br_err = null;
          try {
            br_err = new BufferedReader(new InputStreamReader(pr.getErrorStream()));
            String buff = null;
            while ((buff = br_err.readLine()) != null) {
              System.out.println("Process err :" + buff);
              try {Thread.sleep(100); } catch(Exception e) {}
            }
            br_err.close();
          }
          catch (IOException ioe) {
            System.out.println("Exception caught printing process error.");
            ioe.printStackTrace();
          }
          finally {
            try {
              br_err.close();
            } catch (Exception ex) {}
          }
        }
      }).start();
    }
    catch (Exception ex) {
      System.out.println(ex.getLocalizedMessage());
    }
  }
  
  public static boolean isWindows() {
    if (System.getProperty("os.name").toLowerCase().indexOf("windows") != -1)
      return true;
    else
      return false;
  }

};
/

show errors java source "Host"

2.Publish the Java call specification

接下来,我们创建PL/SQL "wrapper" / PL/SQL procedure

CREATE OR REPLACE PROCEDURE host_command (p_command  IN  VARCHAR2)
AS LANGUAGE JAVA 
NAME 'Host.executeCommand (java.lang.String)';
/

3.Grant Privileges

在此示例中,我们授予对服务器上所有目录的访问权限。这样的操作很危险。您需要更具体地/非常谨慎地授予谁授予对此功能的访问权限。

必须使用SYS权限授予有关JServer的相关权限才能访问文件系统。在这里仅作为测试,才授予对Oracle所有者可访问的所有文件的访问权限,但实际上这是非常危险的事情。

CONN / AS SYSDBA

DECLARE
  l_schema VARCHAR2(30) := 'TEST'; -- Adjust as required.
BEGIN
  DBMS_JAVA.grant_permission(l_schema, 'java.io.FilePermission', '<<ALL FILES>>', 'read ,write, execute, delete');
  DBMS_JAVA.grant_permission(l_schema, 'SYS:java.lang.RuntimePermission', 'writeFileDescriptor', '');
  DBMS_JAVA.grant_permission(l_schema, 'SYS:java.lang.RuntimePermission', 'readFileDescriptor', '');
END;
/

被授权者需要重新登录,授权才会有效。另外,Oracle的所有者必须具有访问所引用文件系统的权限。

4.Test It

最后,我们用Linux的命令来测试一下。

CONN test/test

SET SERVEROUTPUT ON SIZE 1000000
CALL DBMS_JAVA.SET_OUTPUT(1000000);

BEGIN
  --host_command (p_command => 'move C:\test1.txt C:\test2.txt');  --windows系统
  host_command (p_command => '/bin/mv /home/oracle/test1.txt /home/oracle/test2.txt');  --Linux系统
END;
/

可以使用以下DBMS_OUTPUT.get_lines过程捕获host命令的输出。

CONN test/test

SET SERVEROUTPUT ON SIZE 1000000
CALL DBMS_JAVA.SET_OUTPUT(1000000);

DECLARE
  l_output DBMS_OUTPUT.chararr;
  l_lines  INTEGER := 1000;
BEGIN
  DBMS_OUTPUT.enable(1000000);
  DBMS_JAVA.set_output(1000000);

  host_command('dir C:\');
  --host_command('/bin/ls /home/oracle');

  DBMS_OUTPUT.get_lines(l_output, l_lines);

  FOR i IN 1 .. l_lines LOOP
    -- Do something with the line.
    -- Data in the collection - l_output(i)
    DBMS_OUTPUT.put_line(l_output(i));
  END LOOP;
END;
/


使用COM Automation可以达到相同的结果,但我认为这种方法更加整洁。


已知的问题

  • 根据环境的不同,即使手动调用destroy()方法,该进程在执行完命令后仍可能以僵尸的形式继续运行。如果发生这种情况,则仅在会话结束时清除该过程。在正常情况下,这并不代表问题,但是当作为作业的一部分被调用时,僵尸进程仅在作业队列协调器停止时才会结束。
  • 没有为操作系统标注运行任何配置文件,因此将不会设置任何环境变量。所以,您将需要使用任何可执行文件("ls"变为"/bin/ls")或脚本的完整路径。或者,将所有操作编写为脚本,然后在脚本内设置相关的环境变量。

有关更多信息,请参见:
从PL/SQL执行操作系统命令


转载自https://oracle-base.com/articles/8i/shell-commands-from-plsql