Redirect standard output OCaml - ocaml

How can you redirect the standard output in OCaml ?
I tried Format.set_formatter_out_channel but it doesn't seem to work. When I use printf afterwards, the text is still printed on the screen, and the file I created remains empty.

The reason your experiment failed is that Printf.printf doesn't use the output channel of the Format module. The Format module is for pretty-printing, a fairly elaborate task. The Printf.printf function writes formatted data to the standard output (a C-style printf).
Do you really want to redirect standard output, or do you just want to write to a specific channel? To write to a channel oc you can just use
Printf.fprintf oc ...
rather than
Printf.printf ...
Doing redirection is a different thing. You can do it with Unix.dup2. Here's an example session that shows how to do it:
$ cat redirected
cat: redirected: No such file or directory
$ cat redir.ml
let main () =
let newstdout = open_out "redirected" in
Unix.dup2 (Unix.descr_of_out_channel newstdout) Unix.stdout;
Printf.printf "line of text\n";
Printf.printf "second line of text\n"
let () = main ()
$ ocamlopt -o redir unix.cmxa redir.ml
$ ./redir
$ cat redirected
line of text
second line of text
Since this is changing low-level file descriptors behind the back of the OCaml I/O system, I'd be a little careful. As a quick hack it's fantastic--I've done it many times.
Update
Here's a version of the above code that redirects standard output temporarily, then puts it back where it was before.
$ cat redirected
cat: redirected: No such file or directory
$
$ cat redir.ml
let main () =
let oldstdout = Unix.dup Unix.stdout in
let newstdout = open_out "redirected" in
Unix.dup2 (Unix.descr_of_out_channel newstdout) Unix.stdout;
Printf.printf "line of text\n";
Printf.printf "second line of text\n";
flush stdout;
Unix.dup2 oldstdout Unix.stdout;
Printf.printf "third line of text\n";
Printf.printf "fourth line of text\n"
let () = main ()
$
$ ocamlopt -o redir unix.cmxa redir.ml
$ ./redir
third line of text
fourth line of text
$
$ cat redirected
line of text
second line of text

Related

QThread doesn't provide output from called shellscript

I have a function that executes system calls and writes the output into a QTextEdit.
string SystemCallFactory::runSysCallWithoutButton(unique_ptr<SystemCall> sysCall)
{
cout << "Running " << sysCall->getCommand() << endl;
textEdit->setText("");
textEdit->repaint();
QProcess process;
process.start(QString::fromStdString(sysCall->getCommand()));
process.waitForFinished();
QString output(process.readAllStandardOutput());
textEdit->append(output);
textEdit->repaint();
return output.toStdString();
}
The problem is that this works when getCommand() is a real command, e.g. ls /etc but does not work correctly when it refers to a shellscript. In my calls, the call that does not work looks like
/home/turtle10000/tilematching.sh /some/folder /some/specific.file
The script gets executed and does what it's supposed to do, but readAllStandardOutput() returns an empty string. When I run it in a terminal, it shows the expected output.
This is the shellscript:
#!/bin/sh -v
WORKSPACE=$1
SVPFILE=$2
cd $WORKSPACE
ls -1 *.all > datalist.0.mb-1
mbset -I datalist.0.mb-1 -PSVPMODE:1 -PSVPFILE:$SVPFILE
mbprocess -I datalist.0.mb-1
ls -1 *p.mb58 > datalist.1.mb-1
mbset -I datalist.1.mb-1 -PSONAROFFSETX:-0.079 -PSONAROFFSETY:0.196 -PSONAROFFSETZ:0.048 -PVRUOFFSETX:-0.4473 -PVRUOFFSETY:0.000 -PVRUOFFSETZ:-0.3395 -PROLLBIAS:0.1 -PPITCHBIAS:1.32
mbprocess -I datalist.1.mb-1
ls -1 *pp.mb58 > datalist.2.mb-1
mblist -I datalist.2.mb-1 -MA -O^X^Y-z -JU > output.xyz
#remove broken lines (error in input files)
sed -ie '/ /d' output.xyz
Edit: as Botje guessed, some of output appears in stderr instead of stdout.
As Botje suggested, I checked if the output went to stderr and it did. For whatever reason, the output of mbset and mbprocess, tools from the mb-system package, goes to stderr.
In this case, I changed the line
QString output(process.readAllStandardOutput());
to
QString output(process.readAllStandardError());
Another option would probably be to channel the stderr output to stdout in the script.

Global CLI flag in OCaml Core.Command

I am currently creating a CLI application in OCaml and using Core.Command, the CLI parser included in core (v0.10), to parse the command line.
I want to have a global flag that can be used for any subcommand (like the --paginate or --git-dir flags in git for example).
For instance, I want a -debug flag so that the two following commands are valid
my-cli -debug hello world
my-cli -debug goodbye world
However, I could not find a way to do this with the Core.Command API.
Here is a simplified version what I currently have.
open Core
let initialize_logger debug =
Logs.set_reporter (Logs_fmt.reporter ());
let log_level = if debug then Logs.Debug else Logs.Info in
Logs.set_level (Some log_level)
let some_func_with_logging () =
Logs.debug (fun m -> m "the flag debug was passed!")
let hello name =
some_func_with_logging ();
Printf.printf "Hello %s!\n" name
let goodbye name =
some_func_with_logging ();
Printf.printf "Goodbye %s!\n" name
let hello_command =
let open Command.Let_syntax in
Command.basic
~summary:"says hello"
[%map_open
let name = anon ("name" %: string)
and debug = flag "debug" no_arg ~doc:"debug" in
fun () ->
initialize_logger debug;
hello name
]
let goodbye_command =
let open Command.Let_syntax in
Command.basic
~summary:"says goodbye"
[%map_open
let name = anon ("name" %: string)
and debug = flag "debug" no_arg ~doc:"debug" in
fun () ->
initialize_logger debug;
goodbye name
]
let main_command =
Command.group ~summary:"a cool CLI tool"
[ ("hello", hello_command);
("goodbye", goodbye_command);
]
let () = Command.run main_command
There are two main issues here:
the debug flag as well as the call to initialize_logger is duplicated in every subcommand
the debug flag needs to be passed after the subcommand when invoking the command: my-cli hello world -debug instead of my-cli -debug hello world
Is there a clean way to handle this with Core.Command API?

Python process pipes with subprocess.Popen

Here is a test file:
gunzip -c file_1.gz
Line 1
Line 2
Line 3
I am executing bash commands this way:
cmd = "gunzip -c file_1.gz | grep 3"
subprocess.call(cmd, shell=True))
Line 3
I need to run this command on several files in parallel, then join the processes. SO it seems I have to use subprocess.Popen().communicate(). However Popen won't recognize the pipe correctly and will feed it to the first command, gunzip in my case:
subprocess.Popen(cmd.split()).communicate())
gunzip: can't stat: | (|.gz): No such file or directory
gunzip: can't stat: grep (grep.gz): No such file or directory
gunzip: can't stat: 8 (8.gz): No such file or directory
I would like to keep the whole command and to avoid separating it this way:
gunzip = subprocess.Popen('gunzip -c file_1.gz'.split(), stdout=subprocess.PIPE)
grep = subprocess.Popen('grep 3'.split(), stdin=gunzip.stdout, stdout=subprocess.PIPE)
gunzip.stdout.close()
output = grep.communicate()[0]
gunzip.wait()
Is there a way to not separate the commands and process the pipe correctly?
To run the grep 3 command you need the output from the previous command, so there is no way to run this successfully in a single command with subprocess.Popen.
If you always want to run grep 3 for all the files, you could just join the results of all the gunzip -c file_x.gz and then run the grep command only once on the entire list.
subprocess.Popen('gunzip -c file_1.gz'.split(), stdout=subprocess.PIPE)
subprocess.Popen('gunzip -c file_2.gz'.split(), stdout=subprocess.PIPE)
...
grep = subprocess.Popen('grep 3'.split(), stdin=all_gunzip_stdout, stdout=subprocess.PIPE)

How are command line argument are treated in toplevel?

I have a program which takes command line argument. The same of source file is encode.ml. I want to load this file in the toplevel.
Is there way to load the source file in the toplevel where we can pass it a command line arguments?
Thanks.
Yes, invoke your toplevel with ocaml encode.ml arg1 arg2 etc. The following program demonstrates it:
$ cat args.ml
let () =
Array.iteri (Printf.printf "%d -> %s\n") Sys.argv
$ ocaml args.ml -h --help -help
0 -> args.ml
1 -> -h
2 -> --help
3 -> -help

Find and return a specific area of a text

Hello everyone and happy new year! I have a text from which I like to return a specific area from a specific string using a python script!
This is the text I have
#!/bin/sh
case "$1" in
start)
ulimit -s 1024
/usr/bin/oscam --config-dir /etc/tuxbox/config/oscam --daemon --pidfile /tmp/oscam.pid --restart 2 --utf8
;;
stop)
kill `cat /tmp/oscam.pid` 2> /dev/null
;;
restart|reload)
$0 stop
sleep 1
$0 start
;;
version)
echo "svn8631"
;;
info)
echo "oscam svn8631"
;;
*)
echo "Usage: $0 start|stop|restart"
exit 1
;;
esac
exit 0
from the text above I need to create a python command that always returns only what is written where the oscam svn8631 is written between the quotes! So far the only thing I managed to do is return another area of this text using this code
try:
f = open("/etc/init.d/softcam", "r")
content = f.read()
f.close()
except:
content = ""
contentInfo = content.split("\n")
if (content != ""):
for line in contentInfo:
if line.__contains__("usr/bin/"):
idex = line.index("n/")
line = line[(idex + 2):]
return line
This of course returns what's after the usr/bin text, and I need another area which comes after the word info). Can anyone help me please? I don't know how to make my script read this specific area I need! Thanks in advance!
Using the content variable that you have created, the
"oscam svn8631" text can be extracted with:
for chunk in content.split(";;"):
if chunk.strip().startswith('info)'):
return chunk.split('"')[1]
The natural way to break up a bash case statement is by splitting it on ";;". Then, we identify the info section (chunk.strip().startswith('info)')) and select the first quoted string in that section (chunk.split('"')[1]).