23 February, 2012

I just can't remember bash/batch Syntax - CoffeeScript to the rescue!

I regularly need some scripts for copying files to folders or remote servers, for creating folders, checking file existence, etc. The scripts should also be able to accept parameters. Often I try to write a bash script for these tasks, but it always takes much longer than expected. Similarly, on Windows, I do the same with batch/cmd-files, whose syntax is not much better to remember than bash.

I will not do this anymore, it is too cumbersome for me!

From now on, I will use a language I know very well: JavaScript!. Might sound strange but it is actually quite easy. I use node.js as "interpreter" and call the OS functions via the child_process or fs modules.  Esp. error checking is much more versatile now!
Combine that with CoffeeScript, and take shell/batch-scripting tasks to the next level, including easy async. task execution with callbacks! Thanks to CoffeeScript, I get rid of the bad parts of JavaScript. Here is a simple copy script as example (replace the echo cmd with a copy command of your choice)
fs     = require("fs")
exec   = require("child_process").exec
files  = process.argv.slice 2
target = files.pop()     #last arg is target dir/name

stop_onerror = true
error_count  = 0

copy_next = ->
    if files.length == 0 then stop()
        file = files.pop()
        exec "echo copying #{file} to #{target}", (error, out) ->
            console.log out.trim()
            if error?
                console.error "ERROR(#{error_count++})! copy of #{file} failed"
            if error? && stop_onerror then stop() else copy_next()

stop = ->
    if error_count > 0
        console.log "Files copied (with #{error_count} errors)."
        console.log "All files copied successfully."

usage = -> console.log '''
    Usage (with node)
        node   script.js     file1 file2 file3 ... target

    Usage (with coffee)
        coffee file1 file2 file3 ... target

if files.length > 0 && target? then fs.stat target, (error, fstat) ->
    if error? then console.log "#{target} not found!"
    else console.log "copying #{files} to #{target}"; copy_next()
else usage()
This basic script just mimics a normal copy command and is therefore rather useless. But it shows that you can easily combine node.js' file-system functions with operating system calls via exec, and that it is really easy to add more features, such as automatic target-dir creation, file-renames, error logging, etc. Esp. such error checking and other conditional stuff is really cumbersome in plain bash/batch scripting.

Ciao, Juve

Edit: Using bash a lot in the past years, I am now happy with it and actually like it. I also do not have a Windows PC/Laptop anymore and can often safely rely on the *nix commands: cp, mv, rmdir, mkdir to deal with files. However, For real software development, you should better conduct file-related tasks using build and deployment scripts, e.g., using Grunt.

1 comment:

Leon van Kammen said...

I had the same complaints about the syntax, so I also switched to coffeescript.
However, eventually switched back to bash for shellrelated things.
Reasons: portability, zero installation, and somehow simple shell things can become pretty hairy in javascript too.

I tried other solutions, but eventually I started powscript (shameless plug)

It's focus:

* balance between coffeescript and bash
* hasslefree portable all-in-one-file compiler/runtime, written in bash
* loose transpiler: inline bash always possible