Doc & Tips: Interacting with Unix

 

 

Starting from PsyScope X Build 57, we introduced the possibility to send and receive Unix commands from within a script. Furthermore, you can also control PsyScope X directly from the terminal.

Starting PsyScope X from the Terminal

This is easy and potentially very powerful. The only thing you must remember is that PsyScope, is a package, so you have to address the program inside the package. Thus, assuming that you are in Terminal, and that PsyScope X is in your Home directory,

Me:~ luca$ /Users/luca/PsyScope\ X\ B57.app

won't work. Instead,

Me:~ luca$ PsyScope\ X\ B57.app/Contents/MacOS/PsyScope\ X\ B57

will start PsyScope X. Now, the nice things are the parameters you can pass. The options are:

-o: Open script. (Requires a file name)
-f: Create a flag file when running a script.
-q: Quit on experiment end
-s: Save script on exit. Arg: [a|y|n] (a)sk (y)es (n)o
-fg: Execute in foreground
-r: Run script on open
-h: Print this help

Most options are self-explanatory. Let us look at an example. If you write

Me:~ luca$ PsyScope\ X\ B57.app/Contents/MacOS/PsyScope\ X\ B57 -o MyScript

the terminal will open the script after opening the program (remember that you can simply drag and drop on the terminal the file to get the path pasted in it. If you add an option '-r' and the script will automatically run.If you add the option -fg, the script will run in the foreground, so that you will just see the script running without even seeing the startup graphical interface or the PsyScope startup window. And if you add a -q....

Me:~ luca$ PsyScope\ X\ B57.app/Contents/MacOS/PsyScope\ X\ B57 -o /Users/luca/Desktop/Archive/onlymovie\ Script\ copy -r -fg -q

the script will run and the program will quit after finishing the execution of the script.

Perhaps you can now see the interest of this possibility. You can write shell files that chain several experiments together, potentially using the result of one experiment to modify the execution of the next one.

This is where the only strange option, -f, comes in. The option serves to create an empty file, which exists only while PsyScope X is running. You can then write your shell scripts by checking whether this file exists (instead of looking for the process number, which turns out to be more complex) in order to know that the program is running.

Thus you can know how to run your sequence of experiments. This shell script shows how you could run two experiments by dragging and dropping the icon of the program (without looking inside the program's package) and the two scripts onto the terminal. You can check the script to see how it uses the existence of the flag file to close and restart the program without your intervention. Be aware that

Communicating with Unix from PsyScope X

The command SysCmd, that can be used both as a condition and as action (both available from the graphical interface), is a powerful tool with which PsyScope X becomes fully able to exploit any Unix command. For this reason, we invite you to use the command with care, and only if you know what you are doing. Whatever damage this command does to your setup, it's entirely your responsibility.

SysCmd as an input device: Activating the module.

When you want to interact with an external Unix process and receive information from it, PsyScope X "sees" that process as an external device, as if it were a keyboard or a button box that sends you information from outside. Therefore, a new input device has been defined: the SysCmd input device. You have to activate it, by flagging the relevant box in the window that appears when you select the Experiment->Input Devices menu. If the box is not marked, PsyScope will ignore any command that requires a return from an Unix process.

SysCmd as an action: Starting a process.

When you add the action SysCmd to any event, you have three available options. The first one is obvious: RUN COMMAND. The third one too: the Data field allows you to specify the command you want to run.

The second option, Label, is slightly less intuitive. Remember that in PsyScope when an event ends, all actions associated to it trigger, and then the event is erased. However, if you start a process in UNIX, this process lives as long as it has to live -- regardless of where you are in the execution of your PsyScope X script. Thus, you have to identify the Unix commands with a label. By reference to that label, you will then be able to refer to that command even after the event that called it ended. In other words, Unix commands and PsyScope events can be run decoupled, and after the event that initiated it ended PsyScope can always "go back" and find the relevant command in a successive template, for example, by using its label.

Consider for a moment what this decoupling means for an experiment. One of the main limits of PsyScope is that every single event resides inside a template. When the template is over, the event is over. So if you need to activate actions that span across templates, you cannot do it. Now, by starting a Unix process, you can. For example, you can record a full experimental session in one single sound file by starting a recording process via Unix commands. You can decide that the recording ends, say, two templates after it started. Remember, however, that the more processes you start, the worse the performance may be. So use this option carefully.

To give an example, the action below will open a web page:

Actions[ SysCmd[ "RUN COMMAND" myCmd "open http://psy.cns.sissa.it" ]

It will be identified with the associated label myCmd and successive events can query the state of that process by using that labels. Labels must be unique to each call for a process. That is, you cannot "reuse" the same label once you have assigned it to one command.

Calling Applescript scripts via SysCmd.

It may be useful to know that SysCmd also offers a bridge for Applescript interactions. Os X contains the command osascript, which can run every Applescript or even single Applescript instruction. Thus, if the previous action opened our web page, then the following action will quit Safari when you press the key a

Conditions[ Key[ a ] ] => Actions[ SysCmd[ "RUN COMMAND" OtherCmd {osascript -e 'tell application "Safari.app" to quit'} ]]

you ask the Unix core to execute a line of Applescript commands via a SySCmd call to the Unix process OsaScript. Once executed, Safari will quit and you will find yourself back in PsyScope X (although you really never left it)

Using variables within command calls SysCmd.

Often Unix (or Applescript) commands have parameters that may change according to the conditions of an experiment. Although you cannot use variables directly within a SysCmd call, you can always use them indirectly by composing the sequence of characters for a SysCmd call separately, assigning this sequence to a variable of type string and then specify that variable name in place of the command declaration within the SysCmd call.

As an example, suppose that the name of the site you want to open must vary by list, at each trial. You can have a list containing the sites to open:

psy.cns.sissa.it
psy.cns.sissa.it/sympa/info/psyscope
psy.cns.sissa.it/OtherStuff/OtherStuff.html

and at each trial you can assign a different site by composing the command with a mix of "Vary By List..." and Strcat. For example, assuming that MyWebPage is a string variable,

Conditions[ Start[] ] => Actions[ Set[ MyWebPage strcat("'open http://",FactorAttrib("WebPages", "Page"), "'")]

will assign an entry of your list to the variable, preceded by the string open http:// (plus the single quotes around the whole string), hence creating the correct strings for the commands

open http://psy.cns.sissa.it
open http://psy.cns.sissa.it/sympa/info/psyscope
open http://psy.cns.sissa.it/OtherStuff/OtherStuff.html

in successive trials. Then you can use the name of that variable, preceded by the dollar sign, in a SysCmd call:

Conditions[ Key[ d ] ] => Actions[ SysCmd[ "RUN COMMAND" "$TheLabel" "open $MyWebPage" ]

As I wrote, also the label must vary for each given command, even if you repeat the exact same command; so you may want to use a variable for the label instead of a constant string (as in the example above), and change the value of that variable by list, or in any case, if you need to repeat the same command, make sure that the value of that variable changes before you call that command.

SysCmd and running processes: checking a process status.

It is very important to keep in mind that processes that you start via PsyScope are not PsyScope processes, but Unix processes. Hence, they continue their life independently of what a PsyScope script does. For example, if the script terminates its execution -- because it is finished, or because you ended it with CTL-Period -- the processes you initiated continue to run. You have to know that you have to end them.

Some processes will end because their natural life came to an end, that's the sad story of processes as well as of humans. In many occasions, when you run a script, you want to know that a process died. As it turns out, it is not easy to know it from within PsyScope X. A way to do it is to check what response code a process sent back when interrogated. For this purpose, we added another parameter to the SysCmd action: Get Return code. When you use it, you can assign the code to a variable of type string or number, whose value you can use to modify the behavior of your script. Thus

SysCmd[ "GET RETURN CODE" "$MyLabel" ReturnCode ]

will assign to ReturnCode one of the following values:

       0 = The process ended correctly
99999 = Unknown status
10000 = Error code: waiting for a child process to terminate
20000 = Error code: error in shell execution
30000 =
40000 =
  1000 = Error code: the process crashed and generated an error report

which basically means: if you get 0 everything went well, otherwise something is wrong.

SysCmd as a condition: Triggering actions at the end of a process.

SysCmd can also be a condition. In this case, the condition triggers when the process with the specified label is finished. Thus an event with duration

EventType: Text Duration: Key[ q ] SysCmd[ "$myVar" ]

will end either when you press q or when the process with label identified by the variable myVar ended. Likewise, a condition such as

Conditions[ SysCmd[ "$myVar" ] ] => Actions[ Beep[]]

will cause a beep when the process with label identified by the variable myVar ended.

Let us pause for a moment to comment the fact that in the examples just given the label identifying the command is passed as a variable. We already said that because Unix processes potentially last longer than events or templates, we need labels to identify them. One nice feature of the implementation of this part of the code is that the variable within the SysCmd commands is updated dynamically. Therefore, suppose you started several processes in previous templates. Now you want to check whether one of them ended. If you know which one, no problem. But if the process to check depends on other aspects of the execution of the experiment, then you can create a SysCmd condition that contains, instead of a constant label, a variable, e.g.

Conditions[ SysCmd[ "$myVar" ] ] =>

Other parts of the script may modify the value of that variable, even inside the same trial, and because the variable is updated dynamically, when that condition is evaluated, you will evaluate it with respect to the current value of that variable. As a result, the actual label identifying the process can change during the execution of the experiment and so you can specify at later moments which process really needs to be terminated in order for the condition to trigger. This feature may turn out to be useful in several cases.

Terminating a (group of) process(es) initiated with SysCmd

I repeat it again: processes that you start via PsyScope continue their life independently of what a PsyScope script does. Therefore, if they do not end by themselves because of natural death, you have to kill them. However, as it turns out, killing is a dirty business. Add to it that a process can initiate other processes as well, so that you may be left, not only with a "front" process running, but also with all its daughters running. Furthermore, in Os X (unlike Linux for example), when you kill a mother process, the daughters survive as orphans.

To ease this work, SysCmd provides a direct way to send signals to processes, as well as to assign groups to processes, so that a full group can be terminated or interrogated from within PsyScope.

You can find this facility directly in the graphical interface, with the SysCmd SIGNAL option. This option requires that you specify the label of the process, and the signal you want to send. Signals are all the signals that you could send via terminal by means of the kill command (identified by their numbers), plus some labels to simplify your life. The labels are:

HANGUP | INTERRUPT | QUIT | ABORT | ALARM | TERMINATE | KILL |

(case sensitive). Thus, for example, the following line:

Conditions[ End[] ] => Actions[ SysCmd[ SIGNAL MyProcess TERMINATE ]]

will terminate the process with label MyProcess. If you want to see what numerical valid codes you can send instead of these labels, you should check the values given by kill -l via Terminal, as well as the BSD UNIX signal documentation. However, I think for most users (me for example) the labels above will suffice.

Actually, there is only a slight extra difficulty that has to be grasped. Recall that one process may hide another and yet another... and on top of it, that the initiating process may die leaving its daughters alone in the wild. Indeed, this is what happens basically always with SysCmd, because SysCmd itself uses a process to start any other process. Therefore, if you want to get rid of a single process and its descendants, you have to organize them in groups and communicate with the full group.

To do that, SysCmd easily allows you to define a label for a group by appending an asterisk in front of the process label. Thus,

SysCmd[ "RUN COMMAND" *MyProcess "perl myscript.pl"] ]

will assign the label MyProcess to all the processes initiated with the Perl call to the myscript.pl Perl script, and

Conditions[ End[] ] => Actions[ SysCmd[ SIGNAL *MyProcess TERMINATE ]]

will terminate Perl, as well as all the processes initiated by Perl with that particular call. Again, because you can put variables instead of constants inside a SysCmd call, the following expression is well formed:

Conditions[ End[] ] => Actions[ SysCmd[ SIGNAL strcat("*", @TheVariable) TERMINATE ]]

where TheVariable is a variable of type string. Because an asterisk is prepended, the action will terminate all the processes identified by the label which is the current value of the variable.

You are not obliged to use these calls from within PsyScope. If you prefer, you can also rely on shell scripts that do the job for you, and you can call these shell scripts as commands within PsyScope via SysCmd. For example, a shell script may get the PID and may kill it when it runs. Luca Filippin prepared a beautiful example showing how to handle one such cases. In this script, PsyScope starts a recording by using an external Unix program, and stops recording by killing the process via a separate script.

However you do it, you have to remember that it is your responsibility to clean up after your dog.

Sending a signal to all processes by default

Sometimes everything seems to go well: you run your experiment, you terminate your script, and you quit PsyScope. However, processes initiated by SysCmd while PsyScope was running may still be running. Sometimes, this is just what you want. Sometimes, you want to avoid this outcome like death.

To simplify the behavior of a script, we introduced another experiment attribute, which can send a signal to all the non-orphan processes initiated via PsyScope. Thus

#> ExperimentDefinitions
....
....
SysCmdSignalOnEnd: "TERMINATE"

will act as a "Terminate all at once" command that will become effective when a PsyScope script ends regularly (mind you, it won't work if you interrupt it before its normal end). SysCmdSignalOnEnd can take all the values that SIGNAL can take.

Where does all this happen?

When you start your terminal, by default, you are in your home directory. We thought it may have been confusing to run commands from a script that did their job (if any) in a directory different from the one that calls them. So by default, the working directory in case you send Unix commands is the directory where the script is located.. If you want to change this default, a new predicate can be specified in the ExperimentDefinitions section of a script, SysCmdExecuteInScriptDir. The predicate is a binary operator that takes Yes/No as values. So if you want to re-establish the default path you use in your Bash setup, write

#> ExperimentDefinitions
....
....
SysCmdExecuteInScriptDir:NO

That's it for Unix commands.