5 min readByEugeny KhanchinEugeny Khanchin
Suggest Edit

Running External Tools via SKILL

When working with a programming language, we should stick to it as much as possible for consistency, efficiency, integration, error handling, scalability, and more.

However, no language can do everything, including SKILL. For tasks like file management (copy, permissions) or working with Excel files, using terminal commands to call external tools is invaluable.

In this guide, we'll explore various ways to use terminal commands via SKILL to run external tasks.

1. Straightforward system() Function

The system() function in SKILL is used to execute external commands or programs from within a SKILL script. This function allows you to interact with the operating system and run shell commands directly, which can be useful for automating tasks, integrating external tools, or manipulating files outside the Virtuoso environment.

Let’s see how to copy a file:

lisp
1/* 2Run external tools. 3A practical example of how to run external scripts and tools using two 4main approaches - system() and ipcBeginProcess() functions. 5 6Usage example: 7 ; Copy file with system() 8 filePath = "./myfile" 9 destinationPath = "./new_file" 10 copyFile(filePath destinationPath) 11 12 ; Run script in shell with system() 13 scriptFile = "/tmp/myScript.sh" 14 runScriptInShell(scriptFile) 15 16 ; Run command and read its output with ipcBeginProcess() 17 command = "find . -name '*.txt'" 18 output = readCommandOutput(command) 19 printf("%s\n" output) 20 21 ; Run command and use handlers to read its output on-the-fly with ipcBeginProcess() 22 command = "find . -name '*.txt'" 23 runCommandWithHandlers(command) 24 25 26Author: Eugeny Khanchin 27Source: AnalogHub.ie 28*/ 29 30 31procedure( copyFile(filePath destinationPath) 32 let( (command exitCode) 33 sprintf(command "cp %s %s" filePath destinationPath) 34 35 exitCode = system(command) 36 if( exitCode == 0 37 then 38 printf("Succeeded to copy file\n") 39 else 40 printf("Failed to copy file\n") 41 );if 42 );let 43);procedure 44 45 46procedure( runScriptInShell(scriptFile) 47 let( (command exitCode) 48 sprintf(command "sh %s" scriptFile) 49 50 exitCode = system(command) 51 if( exitCode == 0 52 then 53 printf("Succeeded to run the script\n") 54 else 55 printf("Failed to run the script\n") 56 );if 57 );let 58);procedure 59 60 61procedure( readCommandOutput(command) 62 let( (process output nextLine) 63 process = ipcBeginProcess(command) 64 ipcWait(process) 65 66 output = "" 67 while( nextLine = ipcReadProcess(process) 68 output = strcat(output nextLine) 69 );while 70 71 output 72 );let 73);procedure 74 75 76procedure( runCommandWithHandlers(command) 77 ipcBeginProcess( 78 command 79 "" ; Local 80 'readData 81 'readError 82 'exitHandler 83 ) 84);procedure 85 86 87procedure( readData(childId data) 88 ; Do data handling stuff here 89 printf("Process: %d\nOutput:\n%s\n" childId data) 90);procedure 91 92 93procedure( readError(childId data) 94 ; Do error handling stuff here 95 printf("Process: %d\nOutput:\n%s\n" childId data) 96);procedure 97 98 99procedure( exitHandler(childId exitStatus) 100 ; Do post processing stuff here 101 printf("Process %d finished with code %d\n" childId exitStatus) 102);procedure

Here, we use Linux’s ”cp” command. You can also run an external script/tool (Bash, Perl, Python) by providing its full path:

lisp
1let( (scriptFile command exitCode) 2scriptFile = "/tmp/myScript.sh" 3sprintf(command "sh %s" scriptFile) 4 5exitCode = system(command) 6if( exitCode == 0 7then 8printf("Succeeded to run the script\n") 9else 10printf("Failed to run the script\n") 11);if 12);let

This function is best for short tasks, as it freezes the Virtuoso session until the command is finished. Note that you can't read output when using this function.

2. Reading Output with ipcBeginProcess()

The ipcBeginProcess function in SKILL is part of the Inter-Process Communication (IPC) suite of functions, which allows SKILL scripts to interact with external processes in a more controlled and interactive manner than the system() function. This is particularly useful for applications where you need to start a process, send data to it, and receive data from it, enabling more complex integrations with external tools.

Let’s see how to read an output from a terminal command:

lisp
1let( (command process output nextLine) 2command = "find . -name '*.txt'" 3process = ipcBeginProcess(command) 4ipcWait(process) 5 6output = "" 7while( nextLine = ipcReadProcess(process) 8output = strcat(output nextLine) 9);while 10 11printf("%s\n" output) 12);let

Here, ipcBeginProcess() creates a subprocess to run the command. We wait for it to finish with ipcWait() and read its buffered output with ipcReadProcess(). The while loop is necessary because ipcReadProcess() reads output from the buffer, not all at once.

Using the function this way is still best for short tasks, as waiting for the process to finish freezes the Virtuoso session. However, now we know how to read commands’ output.

3. Using ipcBeginProcess() with Handlers

The ipcBeginProcess function can provide much more. We can use handlers to run long tasks and read their output, without freezing the Virtuoso session.

In the previous section, we used the command without additional options, which are:

  • Host name - Basically, where to run the command. Usually, we will run commands locally, by providing an empty string (“”).
  • Data and error handlers - Functions to handle received data from stdout and stderr pipes, useful for reading output and error checking.
  • Post or exit handler - Function called when the subprocess terminates, useful for post-processing and error handling.
  • Log file - Path to a log file where all output will be saved.

Let’s look at the next example. We’ll run the “find” command from previous example, using the additional options:

lisp
1let( (command) 2command = "find . -name '*.txt'" 3ipcBeginProcess( 4command 5"" ; Local 6'readData 7'readError 8'exitHandler 9) 10);let 11 12procedure( readData(childId data) 13; Do data handling stuff here 14printf("Process: %d\nOutput:\n%s\n" childId data) 15);procedure 16 17procedure( readError(childId data) 18; Do error handling stuff here 19printf("Process: %d\nOutput:\n%s\n" childId data) 20);procedure 21 22procedure( exitHandler(childId exitStatus) 23; Do post processing stuff here 24printf("Process %d finished with code %d\n" childId exitStatus) 25);procedure

In this example, the Virtuoso session remains responsive, allowing you to continue working while the process runs. The buffered output is printed in real-time, demonstrating how handlers can be used to manage and display process data effectively.

Tip: Debugging handler functions can be challenging because SKILL IDE doesn't highlight errors within them. Test your handlers before running long tasks. Use breakpoints, which do work, to analyze the state of your handler functions.

4. Conclusion

In this guide, we've explored how to run external tools via SKILL using system() and ipcBeginProcess(). Use system() for short tasks where output isn't needed, and ipcBeginProcess() with handlers for longer tasks requiring output handling. Each approach has its place, depending on the task at hand.