Kinx Library - Process
Kray-G
Posted on July 12, 2020
The script language Kinx is published with the concept of Looks like JavaScript, Feels like Ruby, Stable like AC/DC(?).
I did introduce the Kinx
at this post before, but I feel documents are too few for a lot of people to know how to use it. So I decided I should post some information about Kinx. Especially for undocumented things on README.md, QuickReference.md, or Kinx Specification.
Of course, although I don't know who need it, I also decided I don't care about it. I will be glad if there is one person who is waiting for this.
Anyway, it is Process
this time. It is because this will be useful in practice but it is not documented yet.
- Reference
- First motivation ... The post of introduction
- Kinx, I wanted a scripting language with C style syntax.
- Repository ... https://github.com/Kray-G/kinx
- I am waiting for pull requests.
- First motivation ... The post of introduction
Child Process in Kinx
Process class
using Process
The Process library is not built-in, so it is explicitly loaded using the using
directive.
using Process;
Exec
Create a Process object with new Process(command, opts)
. The argument is a command name and an array of arguments, or a command string. In the case of an array, it is like passing arguments separately, and in the case of a command line string, it is analyzed internally and automatically decomposed into an array format.
- Array: for example,
["ls", "-1"]
. - String: for example,
"ls -1"
.
The created process object has the following methods.
Method | Overview |
---|---|
run() |
Starts the process. |
launch() |
Starts the process and detaches it. |
std() |
Returns the options passed as arguments. { in: opts.in, out: opts.out, err: opts.err }
|
Not done yet only with created a new object. It is started when run()
or launch()
is called. run()
returns an object of the ProcessController
class to control the child process.
Run
By doing run()
, an instance of ProcessController
class is returned.
var p = new Process(["cmd","arg1"]).run();
Launch
launch()
returns nothing, which means it returns null. This is the method of not taking care of the child after starting.
new Process(["cmd","arg1"]).launch();
ProcessController
The ProcessController
class instance returned by run()
has the following methods.
Method | Outline |
---|---|
isAlive() |
true if the process is alive, false if it has already exited, or false after being detach
|
wait() |
Waits for the end of the process, and then returns the exit code of the process. After detach , 0 is returned. |
detach() |
Detaches the process. |
detach()
is to detach the process after starting it. On Linux, the operation is a little different from when detached with launch()
, but what to be done is the same. On Windows, the internal operation is absolutely the same.
On Linux, it is detached by the so-called double-fork method in order to detach it at process startup, but this can only be used at process startup. It is practically impossible to detach it after the process is started, and the child will survive as a zombie unless it is properly wait
or waitpid
in the parent process.
So, right after doing detach()
, Kinx starts a thread just for waitpid
and takes care of the child until death.
By the way, double-fork on Linux is...
- When the parent process dies, the child process will be connected to the init process and the init process will do
wait
for that child.
By using the functionality of the init above, you can fork again from the process that was once forked, then by quickly terminating the first forked process and let init manage the grandchild process.
The top parent process must not forget the waitpid
for the child that first forked. Only the grandchild is the target what you let the init process take care of. Of course Kinx is properly doing it, so you don't have to care about those.
Wait
An example of waiting for the end and acquiring the end code is as follows.
var p = new Process(["cmd", "arg1"]).run();
var status = p.wait();
If you are doing detach
, you cannot get it, then 0 is returned.
Detach
This is the detach
but I have already described above. The process can also be detached by detach
after starting the process. If detached successfully, there becomes nothing every relationship between the process and the child. You don't have to do wait
and wait for the end. Or rather, you can't do it even if you want to care.
var p = new Process(["cmd", "arg1"]).run();
p.detach();
Pipe
This section it is about the pipe everybody wants to use. The main purpose of making Process
is a pipe. The most desired function is that you can freely connect standard input/output with child processes by a pipe to exchange information.
Specify the pipe with opts
of new Process(cmd, opts)
. The following three types of parameters are available.
Parameter | Outline |
---|---|
in |
Specify standard input. Can specify a pipe object, character string, or $stdin
|
out |
Specify standard output. Can specify a pipe object, string, $stdout , or $stderr
|
err |
Specifies the standard error output. Can specify a pipe object, string, $stdout , or $stderr
|
- Pipe object ... An object for using a pipe. Details will be described later.
- Character string ... Input source, output destination file as file name.
-
$stdin
,$stdout
,$stderr
... Bind the input source and output destination of the child process to the standard input/output of this proocess.
Pipe object
Create a pipe object by new Pipe()
, and it returns an array of two objects, [Read, Write]
. The pipe object has the following methods for each read and write.
Normally, specify the Write
pipe as the out
or err
of the child process, and read from the Read
pipe.
Read Pipe
Do not close the pipe before doing run()
because it is set after doing run()
.
Method | Outline |
---|---|
peek() |
Returns 0 if there is no data in the pipe, and a number greater than 0 if there. -1 is an error. |
read() |
Gets all pipe data as a string. If there is no data, it returns an empty string. |
close() |
Closes the pipe. |
Write Pipe
Do not close the pipe before doing run()
because it is set after doing run()
.
Method | Outline |
---|---|
write(data) |
Writes data to the pipe. When not all can be written, the number of written bytes will be returned. |
close() |
Closes the pipe. |
Example
The general form is as follows.
using Process;
var [r1, w1] = new Pipe();
var p1 = new Process([ "ls", "-1" ], { out: w1 }).run();
w1.close(); // You can close it as it is no longer used
while (p1.isAlive() || r1.peek() > 0) {
var buf = r1.read();
if (buf.length() < 0) {
System.println("Error...");
return -1;
} else if (buf.length() > 0) {
System.print(buf);
} else {
// System.println("no input...");
}
}
System.println("");
When using Write Pipe on the parent process side, it looks like this.
using Process;
// stdin read from pipe and output to standard output
[r1, w1] = new Pipe();
var p1 = new Process("cat", { in: r1, out: $stdout }).run();
r1.close(); // You can close it as it is no longer used
// send to stdin of p1
var nwrite = w1.write("Message\n");
w1.close(); // Pipe close, transmission end
p1.wait();
By the way, this way you can control the standard output and standard error output.
new Process("cmd", { out: $stdout, err: $stdout }); // merge standard error output to standard output
new Process("cmd", { out: $stderr, err: $stderr }); // join standard output to standard error output
new Process("cmd", { out: $stderr, err: $stdout }); // swap
Pipeline
Since connecting pipes is a rather troublesome work (in other words, which one is read
...?), I also defined a Process.pipeline
that does all at once. Put a callback function at the end and use it as follows.
var r = Process.pipeline(cmd1, cmd2, cmd3/* , ... */) { &(i, o, pipeline):
// i ... write pipe to stdin for first command
// o ... read pipe from stdout of last command
// pipeline ... pipeline object
// pipeline.input ............. same as i above
// pipeline.output ............ same as o above
// pipeline.peek() ............ same as pipeline.output.peek()
// pipeline.read() ............ same as pipeline.output.read()
// same as pipeline.write() ... pipeline.input.write()
// pipeline.isAlive() ......... true if any process in the pipeline is alive
// pipeline.wait() ............ waits for all processes in the pipeline to complete,
// return the exit code as an array
// The return value of the callback becomes the return value of Process.pipeline() as it is.
return pipeline.wait();
};
The last block is a callback function. This can be included in the argument as the last parameter as follows. But I added this separated syntax by a recent requests.
// Kinx has prepared following syntax
// if you use a lambda in function call.
// This means a function object including lambda
// can be passed by arguments.
// A function object can be set not only to
// the last argument but also to any place in arguments.
var r = Process.pipeline(cmd1, cmd2, cmd3/* , ... */, &(i, o, pipeline) => {
...
});
// or
var r = Process.pipeline(cmd1, cmd2, cmd3/* , ... */, function(i, o, pipeline) {
...
});
// Recently, Kinx supports a block as a function object
// if the last argument is a function.
// This block syntax can be put outside
// function call argument list.
var r = Process.pipeline(cmd1, cmd2, cmd3/* , ... */) { (i, o, pipeline):
...
};
It can be used even without calling back.
var pipeline = Process.pipeline(cmd1, cmd2, cmd3 /* , ... */);
// pipeline ... pipeline object
// omitted below.
Conclusion
It is a good to use the child process by the script as a unified manner because the way of dealing with it is different between Windows and Linux. However, the commands themselves are different. I'm a Windows user, but I use UnxUtils to allow some Unix commands to be available at the command prompt as well. (I don't like Cygwin because it changes the environment...)
So, see you next time, if someone waits for me.
Posted on July 12, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.