How to Write Shell Command Within Pharo Smalltalk

Is it possible to write shell command within Pharo smalltalk?

Pharo does allow the OS interaction. The best way, in my eyes, is to use OSProcess (as MartinW already suggested).

Those that think it is a duplicate are missing this part:

... running a script that should be able to automate a tasks and
return it to some value...

There is nothing about return value in the invoking shell commands from squeak or pharo

To get a return value you would do it the following way:

command := OSProcess waitForCommand: 'ls -la'.
command exitStatus.

If you print out the above code you will get most probably a 0 as success.

If you do an obvious error:

command := OSProcess waitForCommand: 'ls -la /dir-does-not-exists'.
command exitStatus.

You will get ~= 0 value in my case 512.

Edit adding more details to cover more ground

I agree with eMBee that a statement

return it to some value

is rather vague. I'm adding information about I/Os.

As you may know there are three basic IO: stdin, stdout, and stderr. These you need to interact with shell. I'll add these examples first then I'll get back to your description.

Each of them is represented by instance of AttachableFileStream in Pharo. For the above command you will get initialStdIn (stdin), initialStdOut (stdout), initialStdError (stderr).

To write into the terminal from Pharo:

  1. stdout and stderr (you stream string into terminal)

    | process |

    process := OSProcess thisOSProcess.
    process stdOut nextPutAll: 'stdout: All your base belong to us'; nextPut: Character lf.
    process stdErr nextPutAll: 'stderr: All your base belong to us'; nextPut: Character lf.

Check your shell you should see the output there.


  1. stdin - to get what you typed

    | userInput handle fetchUserInput |

    userInput := OSProcess thisOSProcess stdIn.
    handle := userInput ioHandle.
    "You need this in order to use terminal -> add stdion"
    OSProcess accessor setNonBlocking: handle.
    fetchUserInput := OS2Process thisOSProcess stdIn next.
    "Set blocking back to the handle"
    OSProcess accessor setBlocking: handle.
    "Gets you one input character"
    fetchUserInput inspect.

If you want to grab an output from the command into Pharo a resonable way is to use PipeableOSProcess which, as apparent from his name, can be used in conjunction with pipes.

Simple example:

| commandOutput |

commandOutput := (PipeableOSProcess command: 'ls -la') output.
commandOutput inspect.

More complex example:

| commandOutput |

commandOutput := ((PipeableOSProcess command: 'ps -ef') | 'grep pharo') outputAndError.
commandOutput inspect.

I like the use of outputAndError because of typos. If you have an incorrect command you will get the error message:

| commandOutput |

commandOutput := ((PipeableOSProcess command: 'ps -ef') | 'grep pharo' | 'cot') outputAndError.
commandOutput inspect.

In this case '/bin/sh: cot: command not found'

That is about it.

Update 29-3-2021 The OSProcess works up to Pharo 7. It was not upgraded to work with the changes at Pharo 8 or newer.

Invoking shell commands from Squeak or Pharo

In Squeak you can use CommandShell, but I don't know what (if anything) is available for Pharo at this time.

How to interact with a subprocess through its stdin, stdout, stderr in Smalltalk?

Let me start with that the PipeableOsProcess is probably broken on Windows. I have tried it and it just opened a command line and nothing else (it does not freeze my Pharo 8). The whole OSProcess does not work correctly in my eyes.

So I took a shot at LibC which is supposed to not work with Windows.

I’m a module defining access to standard LibC. I’m available under Linux and OSX, but not under Windows for obvious reasons :)

Next is to say that Python's Windows support is probably much better than Pharo's.

The solution, which is more like a workaround using files, is to use LibC and #runCommand: (I tried to come up with a similar example as you had shown above):

| count command result outputFile errorFile  |

count := 9+1. "The counting"
command := 'echo ', count asString. "command run at the command line"

outputFile := 'output'. "a file into which the output is redirected"
errorFile := 'error'. "a file where the error output is redirected "

result := LibC runCommand: command, "run the command "
' >', outputFile, "redirect the output to output file"
' 2>', errorFile.

"reading back the value from output file"
outputFile asFileReference contents lines.
"reading back the value from the error file - which is empty in this case"
errorFile asFileReference contents lines.

Is it possible to run Smalltalk scripts from the command line?

Pharo has decent command line support and a simple zeroconf script to install it:

curl get.pharo.org | bash
./pharo Pharo.image --help
./pharo Pharo.image eval "1+2"

We use these tools on a regular basis on our ci servers.

New command line handles can be installed easily by subclassing.
You will find a partial documentation here.

Coral aims at more complex interfaces and supports complex parameter parsing. The default command line tools shipped with Pharo follow a rather simplistic approach and you have to check and process parameters manually.

how to write string literal with new lines in Pharo

You just do your line:

multiLineString := 'paragraph1
paragraph2
paragraph3'.

Pharo (as any other Smalltalk AFAIK) has multiline strings, you do not need any special notation as in Python or others.

EDIT: Note that while my example will be a literal, yours will not (there will be 2 literals there, and the resulting string will not be a literal.

EDIT 2: There is also String cr.

EDIT 3: It can also be constructed with streams:

myMultiLineString := String streamContents: [ :stream |
stream
nextPutAll: 'paragraph1'; cr;
nextPutAll: 'paragraph2'; cr ]

What is the at ( @ ) operator in Pharo?

In Smalltalk, the @ symbol is used to create instances of the class Point. An instance of such a class has two ivars x and y. You can create a Point using the x:y: message, like this

  Point x: 3 y: 4.

However, it is less verbose to use the message @ like this

  3 @ 4

to create the same thing.

Note that while x:y: is a message you send to the class Point, the message @ 4 is sent to the integer 3. In other words, the former is a class message, the latter an instance message.

Note that, since many people write 3@4 instead of 3 @ 4, this has the risk of creating a surprising side effect. In fact

  3@-4

should be (in principle) the Point with coordinates 3 and -4. However, the Smalltalk syntax is different and will parse it as the message with selector @- and argument 4 sent to the receiver 3. This is why some dialects make an exception so that the message is interpreted as 3 @ -4, which can be achieved by implementing the method @- in Number or by tweaking the parser.

Using Squeak from a shell

Here is a (hackish) solution:
First, you need OSProcess, so run this in a Workspace:

Gofer new squeaksource:'OSProcess'; package:'OSProcess';load.

Next, put this in the file repl.st:

OSProcess thisOSProcess stdOut 
nextPutAll: 'Welcome to the simple Smalltalk REPL';
nextPut: Character lf; nextPut: $>; flush.
[ |input|
[ input := OSProcess readFromStdIn.
input size > 0 ifTrue: [
OSProcess thisOSProcess stdOut
nextPutAll: ((Compiler evaluate: input) asString;
nextPut: Character lf; nextPut: $>; flush
]
] repeat.
]forkAt: (Processor userBackgroundPriority)

And last, run this command:

squeak -headless path/to/squeak.image /absolute/path/to/repl.st

You can now have fun with a Smalltalk REPL. Dont forget to type in the command:

Smalltalk snapshot:true andQuit:true

if you want to save your changes.

Now, onto the explanation of this solution:
OSProcess is a package that allows to run other processes, read from stdin, and write to stdout and stderr. You can access the stdout AttachableFileStream with OSProcess thisOSProcess (the current process, aka squeak).

Next, you run an infinite loop at userBackgroundPriority (to let other processes run). In this infinite loop, you use Compiler evaluate: to execute the input.

And you run this in a script with a headless image.

Standard Input in Smalltalk (Pharo) on Windows

Getting to standard input from a GUI application on Windows is not trivial (see https://social.msdn.microsoft.com/Forums/vstudio/en-US/799cc2b6-309e-4758-8c3b-7c602bbfb736/in-a-gui-program-where-is-stdout?forum=vcgeneral) and few GUI applications support it.

Why do you want to do this? Is it so you can transfer what you've learned from C/C++/C#/Java/Python and other such console-based environments? If so, I suggest you change your approach. Instead of trying to transfer your C tutorials to Smalltalk, I suggest that you learn Pharo using https://mooc.pharo.org. If you want to look at user input in a GUI context, take a look at the UIManager.

On the other hand, if you must write a console-based application, see CommandLineUIManager.



Related Topics



Leave a reply



Submit