Skip to content

Commit dd77ac1

Browse files
committed
java based subprocess_run from matlab-subprocess-stdin
testSys: update env test for new subprocess_run
1 parent 5cdb414 commit dd77ac1

File tree

3 files changed

+71
-52
lines changed

3 files changed

+71
-52
lines changed

+stdlib/+sys/subprocess_run.m

Lines changed: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,73 @@
1-
function [status, msg] = subprocess_run(cmd_array, opt)
2-
1+
function [status, stdout, stderr] = subprocess_run(cmd, opt)
2+
% SUBPROCESS_RUN run a program with arguments and options
3+
% uses Matlab Java ProcessBuilder interface to run subprocess and use stdin/stdout pipes
34
arguments
4-
cmd_array (1,:) string
5+
cmd (1,:) string
56
opt.env struct {mustBeScalarOrEmpty} = struct.empty
67
opt.cwd string {mustBeScalarOrEmpty} = string.empty
8+
opt.stdin string {mustBeScalarOrEmpty} = string.empty
79
end
810

9-
exe = space_quote(cmd_array(1));
11+
%% process instantiation
12+
proc = java.lang.ProcessBuilder("");
1013

11-
if length(cmd_array) > 1
12-
cmd = append(exe, " ", join(cmd_array(2:end), " "));
13-
else
14-
cmd = exe;
14+
if ~isempty(opt.env)
15+
% requires Parallel Computing Toolbox
16+
env = proc.environment();
17+
fields = fieldnames(opt.env);
18+
for i = 1:length(fields)
19+
env.put(fields{i}, opt.env.(fields{i}));
20+
end
1521
end
1622

1723
if ~isempty(opt.cwd)
1824
mustBeFolder(opt.cwd)
19-
cwd = stdlib.fileio.absolute_path(opt.cwd);
20-
oldcwd = pwd;
21-
cd(cwd)
25+
proc.directory(java.io.File(opt.cwd));
2226
end
2327

24-
old = isMATLABReleaseOlderThan('R2022b');
25-
if old && ~isempty(opt.env)
26-
warning("Matlab >= R2022b required for 'env' option of subprocess_run()")
28+
proc.command(cmd);
29+
%% start process
30+
h = proc.start();
31+
32+
%% stdin pipe
33+
if ~isempty(opt.stdin)
34+
writer = java.io.BufferedWriter(java.io.OutputStreamWriter(h.getOutputStream()));
35+
writer.write(opt.stdin);
36+
writer.flush()
37+
writer.close()
2738
end
2839

29-
if isempty(opt.env) || old
30-
[status, msg] = system(cmd);
31-
else
32-
envCell = namedargs2cell(opt.env);
33-
% https://www.mathworks.com/help/matlab/ref/system.html
34-
[status, msg] = system(cmd, envCell{:});
40+
%% wait for process to complete
41+
% https://docs.oracle.com/javase/9/docs/api/java/lang/Process.html#waitFor--
42+
status = h.waitFor();
43+
44+
%% read stdout, stderr pipes
45+
stdout = read_stream(h.getInputStream());
46+
stderr = read_stream(h.getErrorStream());
47+
48+
%% close process
49+
h.destroy()
50+
51+
if nargout < 2 && strlength(stdout) > 0
52+
disp(stdout)
3553
end
36-
if ~isempty(opt.cwd)
37-
cd(oldcwd)
54+
if nargout < 3 && strlength(stderr) > 0
55+
warning(stderr)
3856
end
3957

40-
end
58+
end % function subprocess_run
4159

4260

43-
function q = space_quote(p)
44-
arguments
45-
p (1,1) string
46-
end
61+
function msg = read_stream(stream)
4762

48-
if ~contains(p, " ")
49-
q = p;
50-
return
63+
reader = java.io.BufferedReader(java.io.InputStreamReader(stream));
64+
line = reader.readLine();
65+
msg = "";
66+
while ~isempty(line)
67+
msg = append(msg, string(line), newline);
68+
line = reader.readLine();
5169
end
52-
53-
q = append('"', p, '"');
70+
msg = strip(msg);
71+
reader.close()
5472

5573
end

+stdlib/+test/TestSys.m

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,25 @@ function test_simple_run(tc)
1919

2020
function test_env_run(tc)
2121

22-
tc.assumeFalse(isMATLABReleaseOlderThan('R2022b'), "system(..., EnvName=, EnvVal=) requires Matlab R2022b+")
23-
2422
names = ["TEST1", "TEST2"];
2523
vals = ["test123", "test321"];
2624

2725
env = struct(names(1), vals(1), names(2), vals(2));
2826

29-
% NOTE: test function cannot get specific variables without invoking a
30-
% subshell, as echo is evaluated in current shell before &&
31-
% Thus we just print the entire environment
32-
if ispc
33-
c = "set";
34-
else
35-
c = "env";
27+
for i = 1:length(names)
28+
if ispc
29+
% in ComSpec, echo is a special shell cmd like "dir" -- also doesn't
30+
% work in python subprocess.run
31+
cmd = ["pwsh", "-c" "(Get-ChildItem Env:" + names(i) + ").Value"];
32+
else
33+
cmd = ["sh", "-c", "echo $" + names(i)];
34+
end
35+
36+
[ret, out] = stdlib.subprocess_run(cmd, env=env);
37+
tc.verifyEqual(ret, 0)
38+
tc.verifyTrue(contains(out, vals(i)))
3639
end
3740

38-
[ret, msg] = stdlib.subprocess_run(c, env=env);
39-
tc.verifyEqual(ret, 0)
40-
tc.verifyTrue(contains(msg, names(1) + "=" + vals(1)))
41-
tc.verifyTrue(contains(msg, names(2) + "=" + vals(2)))
42-
4341
end
4442

4543
function test_find_fortran(tc)

+stdlib/subprocess_run.m

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
function [status, msg] = subprocess_run(cmd_array, opt)
1+
function [status, stdout, stderr] = subprocess_run(cmd_array, opt)
22
%% subprocess_run(cmd_array, opt)
33
% handle command lines with spaces
44
% input each segment of the command as an element in a string array
@@ -7,14 +7,16 @@
77
% * cmd_array: vector of string to compose a command line
88
% * opt.env: environment variable struct to set
99
% * opt.cwd: working directory to use while running command
10+
% * opt.stdin: string to pass to subprocess stdin pipe
1011
%%% Outputs
1112
% * status: 0 is success
12-
% * msg: stderr + stdout from process
13+
% * stdout: stdout from process
14+
% * stderr: stderr from process
1315
%
1416
%% Example
1517
% subprocess_run(["mpiexec", "-help2"])
16-
% subprocess_run(["ls", "-l"])
17-
% subprocess_run(["dir", "/Q", "/L"])
18+
% subprocess_run(["sh", "-c", "ls", "-l"])
19+
% subprocess_run(["cmd", "/c", "dir", "/Q", "/L"])
1820
%
1921
% NOTE: if cwd option used, any paths must be absolute or relative to cwd.
2022
% otherwise, they are relative to pwd.
@@ -23,9 +25,10 @@
2325
cmd_array (1,:) string
2426
opt.env struct {mustBeScalarOrEmpty} = struct.empty
2527
opt.cwd string {mustBeScalarOrEmpty} = string.empty
28+
opt.stdin string {mustBeScalarOrEmpty} = string.empty
2629
end
2730

28-
[status, msg] = stdlib.sys.subprocess_run(cmd_array, ...
29-
'env', opt.env, 'cwd', opt.cwd);
31+
[status, stdout, stderr] = stdlib.sys.subprocess_run(cmd_array, ...
32+
'env', opt.env, 'cwd', opt.cwd, "stdin", opt.stdin);
3033

3134
end

0 commit comments

Comments
 (0)