{"id":6233,"date":"2014-12-10T14:17:09","date_gmt":"2014-12-10T14:17:09","guid":{"rendered":"http:\/\/livecode.com\/?p=6233"},"modified":"2014-12-10T14:17:09","modified_gmt":"2014-12-10T14:17:09","slug":"a-livecode-shell","status":"publish","type":"post","link":"https:\/\/legacy.livecode.com\/a-livecode-shell\/","title":{"rendered":"A LiveCode Shell"},"content":{"rendered":"<p dir=\"ltr\">For some time, I have been wishing that I could have some kind of command-line oriented functionality to LiveCode, beyond writing scripts with LiveCode Server. What if I could pipe things directly to it, or run it as an interactive shell? Being able to very quickly make use of the text handling in LiveCode would be extremely handy for certain command line oriented types of tasks. Alas, this is not possible with LiveCode or LiveCode Server as it currently stands. However, after some thought, I concluded that it should be possible to put together a script to provide this functionality.<\/p>\n<p><a href=\"http:\/\/livecode.com\/wp-content\/uploads\/2014\/12\/2014-12-10-11_55_44-MTPuTTY-Multi-Tabbed-PuTTY.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium wp-image-6236\" alt=\"2014-12-10 11_55_44-MTPuTTY (Multi-Tabbed PuTTY)\" src=\"http:\/\/livecode.com\/wp-content\/uploads\/2014\/12\/2014-12-10-11_55_44-MTPuTTY-Multi-Tabbed-PuTTY-300x139.png\" width=\"400\" height=\"185\" \/><\/a><\/p>\n<p><em>Click to Zoom<\/em><\/p>\n<p dir=\"ltr\">After some time, I had produced a script which operates as a simple shell type interface. But how does it work? It\u2019s actually fairly simple. The script is a LiveCode Server script, designed to be invoked via command line. When it starts up, it reads in the options passed to it on the command line to determine how it should behave. The options passed to it are in the $x variables, starting at $0. $# tells us how many were passed.<\/p>\n<div class=\"code\">\n<p dir=\"ltr\">#!\/usr\/local\/bin\/livecode-server<br \/><span style=\"line-height: 18.2000007629395px;\">## substitute this with the path to your livecode server executable<\/span><\/p>\n<p dir=\"ltr\">## array for storing the options passed to this program<br \/><span style=\"line-height: 18.2000007629395px;\">local sOpts<\/span><\/p>\n<p dir=\"ltr\">## load options<br \/><span style=\"line-height: 18.2000007629395px;\">repeat with x = 0 to ($# &#8211; 1)<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0do &#8220;put $&#8221; &amp; x &amp;&amp; &#8220;into tReadOpt&#8221;<br \/><\/span>\u00a0 \u00a0 \u00a0 \u00a0if char 1 of tReadOpt is &#8220;-&#8221; then<br \/><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 repeat with y = 2 to the number of chars in tReadOpt<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0put char y of tReadOpt into sOpts[(the number of lines in the keys of sOpts + 1)]<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0end repeat<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0else<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 put tReadOpt into sOpts[(the number of lines in the keys of sOpts + 1)]<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 end if<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">end repeat<\/span><\/p>\n<\/div>\n<p>\u00a0There are currently two modes: interactive and execute. We can also pass the -p option to tell it to expect data to be piped to it, and read it into a variable called \u2018input\u2019. If we pass it the -e option it starts up in execute mode, and expects you to have specified a command for it to run. It then exits after running the command. Usage for this would be like \u201clcsh -e \u2018put hello world\u2019\u201d. Otherwise, it starts up in interactive mode.<\/p>\n<div class=\"code\">\n<p dir=\"ltr\">## read from pipe<br \/><span style=\"line-height: 18.2000007629395px;\">if optEnabled(&#8220;p&#8221;) then<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0read from stdin until EOF<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0put it into input<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">end if<\/span><\/p>\n<p dir=\"ltr\">## get command from last command line option and execute it, then quit<br \/><span style=\"line-height: 18.2000007629395px;\">if optEnabled(&#8220;e&#8221;) then<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0do sOpts[(the number of lines in the keys of sOpts)]<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0put return<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0quit<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">end if<\/span><\/p>\n<p dir=\"ltr\">## interactive mode<br \/>repeat<br \/><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0put &#8220;lcsh[&#8221; &amp; the folder &amp; &#8220;]# &#8220;<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0read from stdin for 1 line<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0put it into tCommand<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 if isCommand(tCommand) then<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0put shell(tCommand)<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 else<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 try<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0do tCommand<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0catch pError<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 put &#8220;Error:&#8221; &amp; return<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 put pError<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0end try<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0end if<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0<\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 put return<br \/><\/span>end repeat\u00a0<\/p>\n<\/div>\n<p dir=\"ltr\">Execute mode simply takes the last command line option and uses do on it, then quits. Interactive mode reads 1 line from stdin at a time (i.e. it reads 1 typed line by the user at a time) and then determines what should be done with it. It uses the isCommand function to check the $PATH environment variable (which contains all of the directories which should be looked in for executable programs) to see if what was entered was a utility that is already installed, such as the standard Linux utilities. If it was, it uses shell() to execute the entered command. Otherwise, it uses the do command to execute it as normal LiveCode code, catching any generated errors so that they can be displayed without causing the program to exit:<\/p>\n<div class=\"code\">\n<p dir=\"ltr\">## checks to see if the first word is a command in $PATH<br \/>function isCommand pCommand<br \/><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0set the itemDel to &#8220;:&#8221;<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0put the folder into tFolder<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0put false into tIsCommand<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0repeat for each item tDir in $PATH<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0set the folder to tDir<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0if word 1 of pCommand is among the lines of the files then put true into tIsCommand<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0end repeat<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0set the folder to tFolder<br \/><\/span>\u00a0 \u00a0 \u00a0 \u00a0return tIsCommand<br \/>end isCommand<\/p>\n<\/div>\n<p dir=\"ltr\">There is also the optEnabled function that was referenced above. This checks to see if the program was loaded with a specific option enabled:<\/p>\n<div class=\"code\">\n<p dir=\"ltr\">function optEnabled pOpt<br \/>\u00a0 \u00a0 \u00a0 \u00a0repeat for each key tKey in sOpts<br \/><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0if sOpts[tKey] is pOpt then return true<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0end repeat<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">\u00a0 \u00a0 \u00a0 \u00a0return false<br \/><\/span><span style=\"line-height: 18.2000007629395px;\">end optEnabled<\/span><\/p>\n<\/div>\n<p>\u00a0This is quite clunky and has many, many problems &#8211; for example, there\u2019s no ability to pipe the output of some LiveCode to another program in interactive mode, and you can\u2019t open another program that requires interaction. However, I think that with a lot more work, this could become quite a robust and useful tool. It already provides some very handy functionality in it\u2019s current state, namely that I can pipe to it for text parsing:<\/p>\n<p><a href=\"http:\/\/livecode.com\/wp-content\/uploads\/2014\/12\/2014-12-10-12_44_57-MTPuTTY-Multi-Tabbed-PuTTY.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium wp-image-6234\" alt=\"2014-12-10 12_44_57-MTPuTTY (Multi-Tabbed PuTTY)\" src=\"http:\/\/livecode.com\/wp-content\/uploads\/2014\/12\/2014-12-10-12_44_57-MTPuTTY-Multi-Tabbed-PuTTY-300x63.png\" width=\"400\" height=\"84\" \/><\/a><\/p>\n<p><em>Click to Zoom<\/em><\/p>\n<p dir=\"ltr\">I will probably keep chipping away at this until I can use it as my primary command line interface. You can get the full code here: <a href=\"http:\/\/jasminedavid.on-rev.com\/lcsh.txt\" target=\"_blank\">http:\/\/jasminedavid.on-rev.com\/lcsh.txt<\/a><\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>For some time, I have been wishing that I could have some kind of command-line oriented functionality to LiveCode, beyond writing scripts with LiveCode Server. What if I could pipe things directly to it, or run it as an interactive shell? Being able to very quickly make use of the text handling in LiveCode would<\/p>\n","protected":false},"author":11,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"om_disable_all_campaigns":false,"footnotes":""},"categories":[45],"tags":[],"class_list":["post-6233","post","type-post","status-publish","format-standard","hentry","category-blog"],"acf":[],"_links":{"self":[{"href":"https:\/\/legacy.livecode.com\/wp-json\/wp\/v2\/posts\/6233","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/legacy.livecode.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/legacy.livecode.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/legacy.livecode.com\/wp-json\/wp\/v2\/users\/11"}],"replies":[{"embeddable":true,"href":"https:\/\/legacy.livecode.com\/wp-json\/wp\/v2\/comments?post=6233"}],"version-history":[{"count":0,"href":"https:\/\/legacy.livecode.com\/wp-json\/wp\/v2\/posts\/6233\/revisions"}],"wp:attachment":[{"href":"https:\/\/legacy.livecode.com\/wp-json\/wp\/v2\/media?parent=6233"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/legacy.livecode.com\/wp-json\/wp\/v2\/categories?post=6233"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/legacy.livecode.com\/wp-json\/wp\/v2\/tags?post=6233"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}