You probably know that when you make a bash script you give it a shebang like one of these:
#!/bin/sh
#!/bin/bash
#!/usr/local/bin/bash
#!/usr/bin/env bash
and then it's run-able even if you default shell is not one of these interpretors. You can also choose and different language and it will run from you terminal prompt or another script just as if it's a shell script in your default environment:
#!/usr/bin/ruby
#!/usr/local/bin/node
#!/usr/local/bin/coffee
#!/usr/bin/php
#!/usr/bin/python
#!/usr/bin/env ruby
#!/usr/bin/env node
#!/usr/bin/env coffee
#!/usr/bin/env php
#!/usr/bin/env python
after one of these is placed atop your script you can start using the other language.
What you might not know is that even after you define your interpretor for a file you can redirect some foreign code to another interpretor:
#!/bin/bash
echo "puts 'Hello from Ruby!'" | /usr/bin/env ruby
Above, we are using echo
simply to deliver some code to another interpretor. Instead of echo
ing to you at that terminal the |
is redirecting the code via standard stream, which is basically just syntax-less text, to /usr/bin/env ruby
. We could have used printf
instead but it would look at any escaped characters like \n
and replace them with what the mean before the code is sent. That means if you want to actually use the \n
in the Ruby code you won't have it.
Armed with this, we can create a bash function that does this for us and works in any number of languages:
run () {
local sh="/usr/bin/env sh"
local bash="/usr/bin/env bash"
local rb="/usr/bin/env ruby"
local js="/usr/bin/env node"
local cs="/usr/bin/env coffee"
local php="/usr/bin/env php"
local py="/usr/bin/env python"
local lang="${!1}"
shift
echo "$@" | $lang # DON'T wrap $lang in quotes!
}
The last line just did what we were doing before but now it uses a variable for the destination interpretor. The first arg will take any of those local variable names, which are mostly the languages file extensions, and replace it with the value. This replacing is what that !
is doing in "${!1}"
. Here it is in use:
run rb "puts 'Hello from Ruby!'"
run js "console.log('Hello from JavaScript!')"
run cs "console.log 'Hello from CoffeeScript!'"
run php '<?php print "Hello from php!\n"; ?>'
run py "print 'Hello from Python!'"
This is great! We can even capture the response instead of just sending it straight out to the user at the terminal:
rb_response=$(run rb "puts 'Hello from Ruby!'")
js_response=$(run js "console.log('Hello from JavaScript!')")
cs_response=$(run cs "console.log 'Hello from CoffeeScript!'")
php_response=$(run php '<?php print "Hello from php!\n"; ?>')
py_response=$(run py "print 'Hello from Python!'")
printf "$js_response\n$cs_response\n$php_response\n$py_response\n"
This is just a demo so we want to SEE the results but a response can be used any way you want. We could even do some complicated math in the other language and get the reply and process it further in Bash.
Bash doesn't have the same sort of return
functionality as other languages do. It has it's own set of rules, and if you are to use other languages in Bash you have to play by those rules. For this reason we need to understand them. Take this example:
a_func_or_script () {
if [ "$1" = stdout ]; then echo "stdout" # can be captured
elif [ "$1" = stderr ]; then >&2 echo "stderr" # can be captured
elif [ "$1" = ret_st ]; then return "string" # ERROR, don't bother
elif [ "$1" = retn_0 ]; then return 0 # non exiting status code
elif [ "$1" = exit_0 ]; then exit 0 # exiting status code
fi
}
echo $(a_func_or_script stdout) # re-echoes echo in function
echo $(a_func_or_script stderr) # re-echoes echo in function
echo $(a_func_or_script retn_0) # echoes nothing, fairly pointless
echo $(a_func_or_script exit_0) # echoes nothing, fairly pointless
stdout_out=$(a_func_or_script stdout) # stores "stdout"
stderr_out=$(a_func_or_script strerr) # stores "stderr"
a_func_or_script retn_0 # no problems, but pointless
# a_func_or_script exit_0 # if not commented, next line never runs!
echo "done"
So we see that we can store what ever the function or script echo
es as if it's a return statement in another language by capturing it with var=$()
.
This would work if printf
was used instead or if when were in any other language, using what it uses to print to stdout
or stderr
.
In Bash return
and exit
are fairly the same and both emit and integer status (0 is no error, anything else is an error) but exit
will actually throw you out of execution unless inside a sub-shell which is what the parenthesis in $()
are doing.
Status codes are probably less useful for us but one possible use as a sort of boolean value. Bash does not exactly have boolean values. It does have two built-ins: true
and false
but what they are really doing it return
ing either 0
or 1
, respectively. This is also what test
and [
are doing here, return
ing 0
or 1
to if
:
if [ "$1" = "$2" ]; then echo "do something"; fi
if test "$1" = "$2"; then echo "do something"; fi
We could always just use another languages stdout/err and then somehow generate an exit code from it for use in Bash or we could use their status codes directly, providing the language has them. In the first option we still have the issue of storing the code unless we use it in-place (next to if
, for example). Here is a Bash function which runs it's argument, discarding the output but forwarding out the status code:
getstat () {
typeset cmnd="$*"
typeset ret_code
eval $cmnd >/dev/null 2>&1
ret_code=$?
return $ret_code
}
Here is a similar Bash function which, again, runs it's argument, discarding the output but then string "true"
"false"
is sent out via stdout
, based on what the exit code was
stat2bool () {
typeset cmnd="$*"
typeset ret_code
eval $cmnd >/dev/null 2>&1
ret_code=$?
test $ret_code -eq 0 && echo true || echo false
}
I don't wont to sidetrack us too much with status codes because they can get complicated but just know that the first, getstat
would work to the right of if
in Bash and I'll leave it up to you on how to make it take the output of another language before returning to if
. The second, stat2bool
, can be be saved to a variable using $()
and then that variable will work to the right of if
just as if it's the the getstat
.
run()
is great but it would be nice to have more that one line of code that doesn't have to all be squeezed into one statement. Here is the solution.
var="'Hello from Bash!'"
/usr/bin/env ruby <<EORUBY
puts $var
x = 'Hello from Ruby!'
puts x
EORUBY
This couldn't be more straight-forward. You could even have /usr/bin/env ruby
in a Bash variable just to avoid typing it if you find yourself doing it a lot. Note this syntax allows for any content in the code being sent out to be replaced by values of Bash variables, which is very nice. It even does this without the need for double-quotes, which can get very confused and not work if you need double quotes in the actual redirected code!
This doesn't really give you much to work with after it's completed by you might want to try getting the last status code with $?
afterward but I haven't tested this.
Here it is, a Bash function written in Ruby, in a Bash script!
reduce_fraction_rb () {
local ruby_code=$(cat <<EOF
a = $1
b = $2
while b != 0 do
gcd = b
b = a % b
a = gcd
end
numer = $1 / gcd
denom = $2 / gcd
puts "#{numer}/#{denom}"
EOF
)
echo "$ruby_code" | /usr/bin/env ruby
}
# use it like this:
reduce_fraction_rb 4 6
The function takes it's Bash arguments and interpolates them right into the Ruby code, which is another heredoc. The Bash function runs it. It's up to use to actually use puts
in the Ruby code which will act as a sort of return
for you later. You can capture the output like this:
result=$(reduce_fraction_rb 4 6)
Bash does not care that the stdout
came from Ruby's puts
and not from it's own echo
or printf
, it's all just standard streams!
Here is the same thing with Node.js:
reduce_fraction_js () {
local js_code=$(cat <<EOF
var a = $1
var b = $2;
while (b !== 0) {
var gcd = b;
b = a % b;
a = gcd;
}
numer = $1 / gcd
denom = $2 / gcd
console.log (numer + "/" + denom);
EOF
)
echo "$ruby_code" | /usr/bin/env node
}