Scripting

In the console section of the users guide, we introduced the scripting support.

Assignation

You already know the first usage of scripting: execution of command.

karaf@root()> echo hello world
hello world

You can also assign value to session variables:

karaf@root()> msg = "hello world"
hello world

Once you have assigned a value to a variable, you can display this value using the "resolved" variable name:

karaf@root()> echo $msg
hello world

The () are execution quotes (like the backquotes when you use bash on Unix).

karaf@root()> ($.context bundle 1) location
mvn:org.apache.karaf.jaas/org.apache.karaf.jaas.modules/3.0.1-SNAPSHOT

The $.context access the context variables in the current session. We access to the bundle variable (an array containing all bundles), and we want to display the bundle location for the bundle at the index 1 in the bundle array.

List, maps, pipes and closures

Using [], you can define array variable:

karaf@root()> list = [1 2 a b]
1
2
a
b

You can also create a map if you put variables assignation in the array:

karaf@root()> map = [Jan=1 Feb=2 Mar=3]
Jan                 1
Feb                 2
Mar                 3

Using the | character, you can pipe output from a command as an input to another one.

For instance, you can access to the bundles context variables and send it as input to the grep command:

karaf@root()> ($.context bundles) | grep -i felix
    0|Active     |    0|org.apache.felix.framework (4.2.1)
   21|Active     |   11|org.apache.felix.fileinstall (3.2.6)
   43|Active     |   10|org.apache.felix.configadmin (1.6.0)
   51|Active     |   30|org.apache.felix.gogo.runtime (0.10.0)

You can assign name to script execution. It’s what we use for alias:

karaf@root()> echo2 = { echo xxx $args yyy }
echo xxx $args yyy
karaf@root()> echo2 hello world
xxx hello world yyy

Startup

The etc/shell.init.script file is executed at startup in each shell session, allowing the definition of additional variables or aliases or even complex functions. It’s like the bashrc or profile on Unix.

Constants and variables

Apache Karaf console provides a set of implicit constants and variables that you can use in your script.

  • $.context to access a bundle context

  • $.variables to access the list of defined variables

  • $.commands to access the list of defined commands

The variables starting with a # that are defined as Function (such as closures) will be executed automatically:

karaf@root> \#inc = { var = "${var}i" ; $var }
var = "${var}i" ; $var
karaf@root> echo $inc
i
karaf@root> echo $inc
ii
karaf@root>

Built-in variables and commands

Apache Karaf console provides built-in variable very useful for scripting:

  • $args retrieves the list of script parameters, given to the closure being executed

  • $1 .. $999 retrieves the nth argument of the closure

  • $it (same as $1) is used in a loop to access the current iterator value

Apache Karaf console provides commands for scripting:

  • shell:if

  • shell:new

  • shell:each

  • …​

Leveraging existing Java capabilities (via reflection)

Apache Karaf console supports loading and execution of Java classes.

The $karaf.lastException implicit variable contains the latest Exception thrown.

karaf@root()> ($.context bundle) loadClass foo
Error executing command: foo not found by org.apache.karaf.shell.console [17]
karaf@root()> $karaf.lastException printStackTrace
java.lang.ClassNotFoundException: foo not found by org.apache.karaf.shell.console [17]
	at org.apache.felix.framework.BundleWiringImpl.findClassOrResourceByDelegation(BundleWiringImpl.java:1460)
	at org.apache.felix.framework.BundleWiringImpl.access$400(BundleWiringImpl.java:72)
	at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.loadClass(BundleWiringImpl.java:1843)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
	at org.apache.felix.framework.Felix.loadBundleClass(Felix.java:1723)
	at org.apache.felix.framework.BundleImpl.loadClass(BundleImpl.java:926)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.apache.felix.gogo.runtime.Reflective.invoke(Reflective.java:137)
	at org.apache.felix.gogo.runtime.Closure.executeMethod(Closure.java:527)
	at org.apache.felix.gogo.runtime.Closure.executeStatement(Closure.java:403)
	at org.apache.felix.gogo.runtime.Pipe.run(Pipe.java:108)
	at org.apache.felix.gogo.runtime.Closure.execute(Closure.java:183)
	at org.apache.felix.gogo.runtime.Closure.execute(Closure.java:120)
	at org.apache.felix.gogo.runtime.CommandSessionImpl.execute(CommandSessionImpl.java:89)
	at org.apache.karaf.shell.console.jline.Console.run(Console.java:166)
	at java.lang.Thread.run(Thread.java:680)

It’s possible to create objects to create commands "on the fly":

karaf@root()> addcommand system (($.context bundle) loadClass java.lang.System)
karaf@root()> system:getproperty karaf.name
root

It means that you can create object using the new directive, and call methods on the objects:

karaf@root> map = (new java.util.HashMap)
karaf@root> $map put 0 0
karaf@root> $map
0                   0

Examples

The following examples show some scripts defined in etc/shell.init.script.

The first example show a script to add a value into a configuration list:

#
# Add a value at the end of a property in the given OSGi configuration
#
# For example:
# > config-add-to-list org.ops4j.pax.url.mvn org.ops4j.pax.url.mvn.repositories http://scala-tools.org/repo-releases
#
config-add-to-list = {
  config:edit $1 ;
  a = (config:property-list | grep --color never $2 | tac) ;
  b = (echo $a | grep --color never "\b$3\b" | tac) ;
  if { ($b trim) isEmpty } {
    if { $a isEmpty } {
      config:property-set $2 $3
    } {
      config:property-append $2 ", $3"
    } ;
    config:update
  } {
    config:cancel
  }
}

This second example shows a script to wait for an OSGi service, up to a given timeout, and combine this script in other scripts:

#
# Wait for the given OSGi service to be available
#
wait-for-service-timeout = {
  _filter = $.context createFilter $1 ;
  _tracker = shell:new org.osgi.util.tracker.ServiceTracker $.context $_filter null ;
  $_tracker open ;
  _service = $_tracker waitForService $2 ;
  $_tracker close
}
#
# Wait for the given OSGi service to be available with a timeout of 10 seconds
#
wait-for-service = {
  wait-for-service-timeout $1 10000
}
#
# Wait for the given command to be available with a timeout of 10 seconds
# For example:
# > wait-for-command dev watch
#
wait-for-command = {
  wait-for-service "(&(objectClass=org.apache.felix.service.command.Function)(osgi.command.scope=$1)(osgi.command.function=$2))"
}