Skip to content

Commit 43fb095

Browse files
committed
subprocess_run_octave: for GNU Octave only
1 parent 19f0d18 commit 43fb095

File tree

2 files changed

+165
-2
lines changed

2 files changed

+165
-2
lines changed

+stdlib/subprocess_run.m

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
%% SUBPROCESS_RUN run process with optional cwd, env. vars, stdin, timeout
1+
%% SUBPROCESS_RUN run process for Matlab only
2+
% with optional cwd, env. vars, stdin, timeout
23
%
34
% handles command lines with spaces
45
% input each segment of the command as an element in a string array
@@ -23,7 +24,7 @@
2324
%
2425
% NOTE: if cwd option used, any paths must be absolute or relative to cwd.
2526
% otherwise, they are relative to pwd.
26-
% SUBPROCESS_RUN run a program with arguments and options
27+
%
2728
% uses Matlab Java ProcessBuilder interface to run subprocess and use stdin/stdout pipes
2829
% https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/ProcessBuilder.html
2930

+stdlib/subprocess_run_octave.m

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
%% SUBPROCESS_RUN_OCTAVE run process for GNU Octave only
2+
% with optional cwd, env. vars, stdin, timeout
3+
%
4+
% handles command lines with spaces
5+
% input each segment of the command as an element in a string array
6+
% this is how python subprocess.run works
7+
%
8+
%%% Inputs
9+
% * cmd_array: cell of char to compose a command line
10+
% * env: environment variable struct to set
11+
% * cwd: working directory to use while running command
12+
% * stdin: string to pass to subprocess stdin pipe
13+
% * timeout: time to wait for process to complete before erroring (seconds)
14+
%%% Outputs
15+
% * status: 0 is generally success. -1 if timeout. Other codes as per the
16+
% program / command run
17+
% * stdout: stdout from process
18+
% * stderr: stderr from process
19+
%
20+
%% Example
21+
% subprocess_run({'mpiexec', '-help2'})
22+
%
23+
% NOTE: if cwd option used, any paths must be absolute or relative to cwd.
24+
% otherwise, they are relative to pwd.
25+
%
26+
% uses Java ProcessBuilder interface to run subprocess and use stdin/stdout pipes
27+
% https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/ProcessBuilder.html
28+
29+
function [status, stdout, stderr] = subprocess_run_octave(cmd, env, cwd, stdin, timeout)
30+
if ischar(cmd), cmd = {cmd}; end
31+
if nargin < 2 || isempty(env), env = struct(); end
32+
if nargin < 3, cwd = ''; end
33+
if nargin < 4, stdin = ''; end
34+
if nargin < 5, timeout = 0; end
35+
36+
%% process instantiation
37+
% https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/lang/ProcessBuilder.html#command(java.lang.String...)
38+
jcary = javaArray("java.lang.String", length(cmd));
39+
for i = 1:length(cmd)
40+
jcary(i) = javaObject("java.lang.String", cmd{i});
41+
end
42+
proc = javaObject("java.lang.ProcessBuilder", jcary);
43+
44+
if ~isempty(fieldnames(env))
45+
% https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/lang/ProcessBuilder.html#environment()
46+
jenv = proc.environment();
47+
fields = fieldnames(env);
48+
for i = 1:length(fields)
49+
jenv.put(fields{i}, env.(fields{i}));
50+
% jenv.put(fields{i}, javaObject("java.lang.String", env.(fields{i})));
51+
end
52+
end
53+
54+
if stdlib.len(cwd) > 0
55+
% https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/lang/ProcessBuilder.html#directory(java.io.File)
56+
o = javaObject("java.io.File", cwd);
57+
proc.directory(o);
58+
end
59+
60+
%% start process
61+
% https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/lang/ProcessBuilder.html#start()
62+
h = proc.start();
63+
64+
%% stdin pipe
65+
if stdlib.len(stdin) > 0
66+
os = javaObject("java.io.OutputStream", h.getOutputStream());
67+
writer = javaObject("java.io.BufferedWriter", os);
68+
writer.write(stdin);
69+
writer.flush()
70+
writer.close()
71+
end
72+
73+
%% wait for process to complete
74+
% https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/lang/Process.html#waitFor()
75+
76+
tmsg = '';
77+
if timeout > 0
78+
% returns true if process completed successfully
79+
% returns false if process did not complete within timeout
80+
sec = javaMethod("valueOf", "java.util.concurrent.TimeUnit", "SECONDS");
81+
b = h.waitFor(timeout, sec);
82+
if b
83+
status = 0;
84+
else
85+
tmsg = 'Subprocess timeout';
86+
status = -1;
87+
end
88+
else
89+
% returns 0 if process completed successfully
90+
status = h.waitFor();
91+
end
92+
93+
%% read stdout, stderr pipes
94+
stdout = read_stream(h.getInputStream());
95+
stderr = read_stream(h.getErrorStream());
96+
97+
%% close process
98+
h.destroy();
99+
100+
stderr = strcat(tmsg, stderr);
101+
102+
if nargout < 2 && stdlib.len(stdout) > 0
103+
disp(stdout)
104+
end
105+
if nargout < 3 && stdlib.len(stderr) > 0
106+
warning(stderr)
107+
end
108+
109+
end % function subprocess_run
110+
111+
112+
function msg = read_stream(stream)
113+
114+
% https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/io/BufferedReader.html
115+
reader = javaObject("java.io.BufferedReader", javaObject("java.io.InputStreamReader", stream));
116+
117+
line = reader.readLine();
118+
msg = '';
119+
while ~isempty(line)
120+
msg = strcat(msg, line, newline);
121+
122+
line = reader.readLine();
123+
end
124+
125+
if stdlib.len(msg) > 0 && msg(end) == newline
126+
msg(end) = [];
127+
end
128+
129+
reader.close();
130+
131+
end
132+
133+
%!test
134+
%! if ispc, c = "dir"; else, c = "ls"; end
135+
%! [r, m, e] = subprocess_run_octave(c);
136+
%! assert(r == 0)
137+
%! assert(length(m) > 0)
138+
%! assert(length(e) == 0)
139+
%! [r, mc, e] = subprocess_run_octave(c, [], '/');
140+
%! assert(r == 0)
141+
%! assert(!strcmp(m, mc))
142+
%!testif 0
143+
%! names = {'TEST1', 'TEST2'};
144+
%! vals = {'test123', 'test321'};
145+
%! env = struct(names{1}, vals{1}, names{2}, vals{2});
146+
%! for i = 1:length(names)
147+
%! if ispc
148+
%! c = {"cmd", "/c", "echo", strcat('%', names{i}, '%')};
149+
%! else
150+
%! c = {"echo", strcat('$', names{i})};
151+
%! end
152+
%! [r, m, e] = subprocess_run_octave(c, env);
153+
%! assert(r == 0)
154+
%! assert(strcmp(m, vals{i}), '%s != %s', m, vals{i})
155+
%! assert(length(e) == 0)
156+
%! end
157+
%!test
158+
%! if ispc, c = {'powershell', '-command', 'Start-Sleep -s 3'}; else, c = {'sleep', '3'}; end
159+
%! [r, m, e] = subprocess_run_octave(c, [], [], [], 1);
160+
%! assert(r == -1)
161+
%! assert(length(m) == 0)
162+
%! assert(strncmp(e, 'Subprocess timeout', 17))

0 commit comments

Comments
 (0)