REBOL and the shell

REBOL's expressive power shines when you see what can be accomplished with small amounts of code. The following article demonstrates the use of REBOL to develop useful shell scripts. Using REBOL you can enhance the capabilities of the shell with relatively little effort.

This document assumes you have some familiarity with REBOL and the unix shell. If you have more familiarity with REBOL than unix, or the other way around, you still may find this document informative, or hopefully interesting.

  Making a REBOL shell script


In order to turn a REBOL script into a general shell script, place the following "interpreter" line at the top of the script:

    #!/path/to/rebol -qs


Replace "/path/to/" with the path that leads to where REBOL is installed on your system. The interpreter line is the very first thing in the script, preceding the REBOL header which follows on the next line.

The flags "-qs" mean to run the script quiet (no banners are printed) and to run the script with out security. REBOL's default security level allows it to write and read stuff from the network as well as reading files, but REBOL will ask permission before writing files. However, when REBOL is running at the default security level with the -q flag, any attempt to write a file will cause the script to immediately quit. Therefore, lowering security on the interpreter line is necessary only if your script is going to write files. Keep in mind that ordinary shell scripts have no additional security measures built in at all! The examples included in this document specify the security lowering -s flag on the interpreter line only where necessary.

To finish turning a REBOL script into a usable shell script it must be executable and in your path. A convenient spot to put your scripts is in your personal bin/ directory off your home directory. You can check your PATH variable by doing "echo $PATH" to see that your bin/ directory is there. If it is not, you can add your bin/ directory to your path as follows.

Under bash flavors:

    export PATH=$HOME/bin/:$PATH


Under csh:

    setenv PATH=$HOME/bin/:$PATH   


The above line can go in your shell init script (.login, .profile, .cshrc, .bash_rc, etc...) to make the change permanent. Finally, to make the script executable:

    chmod u+x ~/bin/script


Afterwards, you should be able to type the name of your script at the shell and REBOL will execute it.

In the examples below, we'll include the so called "interpreter" line pointing to /usr/local/bin/rebol and a small REBOL header.

  Strip control-Ms from all files in a directory


I've seen hackers argue for days about the simplest way to strip the control-M's from text files that come from ms-dos. There are certainly various terse ways to do this using sed, awk, and tr, but here is how simple it is to do using REBOL:


    #!/usr/local/bin/rebol -qs
    REBOL [Title: "Strip-Ms"]
 
    foreach file read change-dir system/options/path [
        if not dir? file [write file read file]
    ]


REBOL writes out files in the your native operating system format, so by simply reading in a file and writing it out those obnoxious ms-dos control-Ms are taken care of. Presuming you installed the above script in your path, you would visit a directory containing text files from dos land and type in the name you gave your script (stripms perhaps?). Be a little careful to only do this in a directory with text files. To read and write binary files use READ/binary and WRITE/binary. This leads to an obvious improvement of the script: checking the suffixes of files before reading and writing them -- only doing so for ".txt" or ".r" files, etc..

Notice that the script used system/options/path to determine where to go. There are a few different paths found in REBOL's system object:

  • system/user/home -- user's home directory
  • system/script/path -- where the script is found
  • system/options/path -- where you were when you invoked the script


  Lowercase a directory of files.


Sometimes you wind up with some disk or archive full of annoying all upper case filenames. It's a snap to lowercase the whole directory with REBOL:

    #!/usr/local/bin/rebol -qs
    REBOL [Title: "Lowercase em"]
 
    foreach file read change-dir system/options/path [rename file lowercase file]


The above script works a lot like the first script we looked at. The same approach works for many scripts that operate on the contents of a directory.

  Dump web page text and data


Here's a small script to dump the plain text of a web page:

    #!/usr/local/bin/rebol -q
    REBOL [Title: "Dump Web"]
 
    parse load/markup to-url system/script/args [
        some [tag! | set x string! (prin x)]
    ]


If you called this script "dweb", then you'd would invoke it from the command line like this:

    dweb http://www.rebol.com


Changing this script to print out the links on a web page is trivial:

    #!/usr/local/bin/rebol -q
    REBOL [Title: "Dump Web links"]
    
    parse load/markup to-url system/script/args [
        some [set x tag! (
                if x: find/tail x "a href=" [print trim/with form x {<">}]
            ) | string!
        ]
    ]


Call it dlink, and try it out:

    dlink http://slashdot.org


  Passing arguments to your script


Arguments you specify on the command line will be found in system/options/args. Here's a little script to help see what these all these path, home, and args are all about:

    #!/usr/local/bin/rebol -q
    REBOL [Title: "Rebol paths and args"]
 
    foreach item [
         system/user/home 
         system/script/path 
         system/options/path 
         system/options/args
    ][print [:item "==" mold item]]


Call the script "rargs" and put it in your bin/ directory. Change directories to /usr/local and type 'rargs foo bar'. You should see something like this:

    system/user/home == %/home/yourname/
    system/script/path == %/home/yourname/bin/
    system/options/path == %/usr/local/
    system/options/args == "foo bar"


If no arguments were provided the args would be set to NONE. As you see, if you supply more than one argument, they'll actually come in as a single string. You can break strings up into separate pieces with this handy one liner:

    parse "foo bar" none
    == ["foo" "bar"]


A block containing each separate word is returned. Because the args can sometimes be NONE you have to check first before you try to split the args as a string:

    if not none? system/options/args [args: parse system/options/args none]


That's too much writing for my tastes, so you can get away with a little hack like this;

    parse any [system/options/args ""] none


In the example above, if system/options/args is NONE you'll end up parsing an empty string instead, yielding an empty block. No harm, no foul, however code that comes afterwards must deal with a possible empty block.

Note: command line arguments undergo shell expansion first, so REBOL shell scripts can be composed with other shell expressions. For instance, with the our rargs script from above, try these out:

    rargs *
    rargs $HOME
    rargs `ls /`


  Quick one liners


Occasionally there is a need to do a quick math calculation, or some fast string manipulation. REBOL allows you to do these sorts of one liners from the command line using the --do switch:

    rebol --do "print 2 * pi * 4"


But it is convenient to have that process shortened a little. Call the next script "reb":

    #!/usr/local/bin/rebol -qs
    REBOL [Title: "Reb"]
    random/seed now
    print do system/options/args


First, the random number generator is seeded as a convenience for when we want to use our mini REBOL expression evaluator to produce random items. Then our script merely DOes the arguments. Using "reb" at the command line we have many nice possibilities:

    reb "1 * 2"
    reb head reverse read %.
    reb pick x: read %. random length? x


You may ask what use is picking a random file? Perhaps each day 'xv' picks a random image from a directory and puts it on your root window, or perhaps you have a cron job that periodically changes your web pages.

    reb random 100
    reb send friend@somewhere.com '"Hey!!"' 0


Or more complex expressions:

    reb "do fib: func [n m][if n > 50 [quit] fib m probe n + m] 0 1"
    reb "0.0.0.255 + read dns://yahoo.com"
    reb `find . | grep -c foo`" * 4" 


The last example above would print 4 times the number of files found recursively from the present directory that contain the string "foo". Using shell quoting rules you can mix shell substitutions with other REBOL expressions to evaluate.

Much of the time the arguments can be provided as is, but sometimes they need to be quoted to avoid unwanted shell substitutions. Beyond the basic shell quoting rules, we also have to quote lines that contain dashes or pluses which normally signal command line flags for REBOL.

  FROM via pop


Sometimes you are on a system that does not have mail delivered locally and can only be retrieved via pop. I've always been a big fan of the FROM command (FRM on some systems). FROM allows you to see a quick summary of your mail box, who each message is from and the subject line. If there's something interesting you might want to read your mail, otherwise you go on about what ever it is you were doing. Here is an example of doing a FROM through pop which also demonstrates how easy network protocols are to manipulate in REBOL:

    #!/usr/local/bin/rebol -q
    REBOL [Title: "POP FROM"]
  
    me: "account"
    pass: ask/hide "Password? "
    server: "pop.server.dom"
 
    pop: open rejoin [pop:// me ":" pass "@" server]
 
    forall pop [
        message: import-email first pop 
        print [index? pop message/from tab message/subject]
    ]
    close pop


This pop FROM script will first ask you for your password, hiding the keys you type. You COULD put your password right in the script, but that might not be too safe in a multiuser environment. Of course, you need to plug in your account and your pop server in the appropriate places.

If you have multiple pop accounts, it would be easy for this script to be changed to go FROM them all together.

  FTP public_html


In the same vein as the previous script, here is another network convenience script. While working on a web site, you have a local directory where you work on the source files. Periodically you make some changes to your local copies of the web site files and you want to get them up to the remote web server. No need to fire up some dedicated application to move those files over, just roll a quick little script that takes all the hassle out of it.

    #!/usr/local/bin/rebol -qs
    REBOL [Title: "to-web"]
 
    me: "account"
    pass: ask/hide "Password? "
    server: "ftp.server.dom"
    foreach file read change-dir system/options/path [
        if not dir? file [
            write/binary rejoin [
                ftp:// me ":" pass "@" server "/public_html/" file
            ] read/binary probe file
        ]
    ]


The script is hard wired to upload every file in the directory you call it from to public_html/ on your remote server. Creating a recursive directory upload is also fairly easy.

If you end up getting network time outs using ftp, try adding the following line to the begining of your script:

    system/schemes/ftp/passive: true


Setting passive to true in REBOL's ftp scheme will turn on REBOL's ftp passive mode. In passive mode, REBOL will connect back to the ftp site for the data transmission, instead of the other way around as it normally goes. If you are behind a firewall, ftp servers will likely not be able to establish the data connection to you, and hence the timeouts.

  A simple reminder


Using REBOL/view it is a snap to create a quick and dirty reminder program:

    #!/usr/local/bin/rebol -q
    REBOL [Title: "Reminder"]
 
    if found? args: system/options/args [
        set [time message] load/next args
        wait time view layout compose [
	        text red (copy message) font [size: 20]
        ]
    ]


Call the script "remind" and put it in bin/ directory. Here is an example of invoking the script

    remind 2 update the database &


Two seconds later your message should pop up on your screen. If you want to wait a few hours you'd specify the time in a REBOL's time format:

    remind 4:00:00 it is four hours later &


LOAD/next was used to extract the time value from from the first position in the arguments.

  Quickly view an image


REBOL/view can display jpeg, gif and bmp files. With REBOL/view you can make a simple script to view an image. When you click on the image or hit space bar while having the window in focus, the window will close.

    #!/usr/local/bin/rebol -q
    REBOL [Title: "View an image"]
 
    change-dir system/options/path
    if found? args: system/options/args [
        view layout [image (to-file args) #" " [quit]]
    ]


The #" " is the space character and it defines a shortcut for the image. Following the space character is a block that is executed when that shortcut is pressed or when the image is clicked. REBOL/view's layout dialect is a very simple way to create dynamic GUIs.

I called the above script "eye". Moving into a directory with an image file, the script is invoked like this:

    eye picture.jpg


As expected, the image pops up in a window.

  That's just the beginning


These preceding little examples don't begin to scratch the surface of REBOL's capabilities. It is very easy to start building time saving REBOL scripts for the shell that make your life much easier.