Search code examples
shellclojure

clojure.java.shell/sh gives different behavior than from command line?


i'm using clojure to make shell calls of a program (https://github.com/kahypar) but the same call i can successfully make from command line is not working when invoked this way. the command line is this

kahypar -k 2 -e 0.03 -m direct -o cut -w true -p .../baseConfig-direct.ini -h .../g0.hgr

I create this command in clojure and use clojure.java.shell/sh (via Java's Runtime.exec()) like this:

(let [khpPath ".../git/kahypar/build/kahypar/application/KaHyPar"
      param (format "-k 2 -e 0.03 -m direct -o cut -w true -p %s -h %s" configFile hgrFile)
      rv (shell/sh khpPath param) ]

i get the following error kahypar/boost:

khpEval: exit=134 err=libc++abi: terminating with uncaught exception of type boost::wrapexcept<boost::program_options::invalid_option_value>: the argument (' 2 -e 0.03 -m direct -o cut -w true -p .../baseConfig-direct.ini -h .../g0.hgr') for option '--blocks' is invalid

but if i replace the call to kahypar with a call to this simple echoArgs.sh script

#!/bin/bash
echo "echo:"
echo "$@"
echo "printf"
printf '%s\n' "$*"

and replace khpPath with echoPath pointing to this script, it echos EXACTLY THE SAME ARGUMENTS as on the command line:

echo:
-k 2 -e 0.03 -m direct -o cut -w true -p ...baseConfig-direct.ini -h .../g0.hgr
printf
-k 2 -e 0.03 -m direct -o cut -w true -p ...baseConfig-direct.ini -h .../g0.hgr

i've also tried experiments passing the multiple arguments individually rather than as part of a single string; still no love.

because (shell/sh) uses futures i can't dig into the call stack to trace more deeply. is there anyway to see where clojure call of shell differs from standard command line?


Solution

  • clojure.java.shell/sh expects all arguments for the process to run as separate arguments. Your code passes all arguments as a single argument and therefor you get the error about the wrong argument.

    So the correct call should look like this:

    (sh "KaHyPar" "-k" "2" "-e" "0.03" "-m" "direct" "-o" "cut" "-w" "true" "-p" configFile  "-h" hgrFile)
    

    In cases where so many args are used or the command is already provided from somewhere else (e.g. user input or config file), parsing it by (UNIX) "shell rules" and split it like that might be tedious. In that case, or when you need (UNIX) "shell-ism" you can also run it via the (UNIX) shell:

    (sh "/bin/sh" "-c" "KaHyPar -k 2 -e 0.03 -m direct ...")
    

    This way the argument can follow shell-rules.