0

Consider a script file: ~/project/script.sh

echo "arg 1 $1"  
echo "arg 2 $2"
echo "arg 3 $3"

And elisp file that invokes it using make-process: ~/project/special-mode/special-mode.el

(let* ((script-file  (shell-quote-argument (expand-file-name "~/project/script.sh")))
       (argA          (shell-quote-argument "A"))
       (argB          (shell-quote-argument "B")))
       (make-process
             :name            "special-mode"
             :buffer          "*SPECIAL-MODE*"
             :command         `( "sh" "-c" ,script-file ,argA  ,argB )
             :connection-type 'pipe
             :sentinel   nil)))

Question

The function evaluates but the arguments to the shell script (argA and argB) are never passed. Any idea how to fix this?

Emacs Version

GNU Emacs 28.0.50

2 Answers 2

1

Your issue is the use of sh -c. When using the -c flag, the next argument is expected to be the full command line that will be expanded by the subshell.

You can try this in a regular shell:

ditto:~% sh -c echo a b c

ditto:~% sh -c 'echo a b c'
a b c
ditto:~%

In the first example, echo is being executed correctly, but because only the first argument after -c is being passed to the subshell, the a b c arguments are lost to echo. In the second example, the entire command line is being passed as a single argument, and thus echo is seeing a b c in the subshell.

So naturally, you have to take this into account in your elisp by passing a single argument to -c:

(let* ((script-file (shell-quote-argument (expand-file-name "/bin/echo")))
       (arg-a       (shell-quote-argument "A"))
       (arg-b       (shell-quote-argument "B")))
  (make-process
   :name            "special-mode"
   :buffer          "*SPECIAL-MODE*"
   :command         `("sh" "-c" ,(concat script-file " " arg-a " " arg-b))
   :connection-type 'pipe
   :sentinel   nil))

I would recommend, however, that instead of using sh -c, just call your binaries directly unless you specifically need to use shell interpretatiton, because it works more naturally and has more predictable outcome.

0

Update:

As pointed out by xuchunyang, adding a shebang line to the script.sh and calling it like an ordinary executable is a much cleaner solution. That is:

#!/bin/bash 
echo "arg 1 $1"  
echo "arg 2 $2"
echo "arg 3 $3"

Old Solution

Removing the -c flag fixed the issue. The -c flag is used to call shell commands in-line(and probably more).

sh -c[-abCefhimnuvx][-o option][+abCefhimnuvx][+o option]command_string
               [command_name [argument...]]

From The Single UNIX ® Specification, Version 2

-c        Read  commands from the command_string operand. Set the value of
          special parameter 0 (see Special Parameters ) from the value  of
          the  command_name operand and the positional parameters ($1, $2,
          and so on) in sequence from the remaining argument operands.  No
          commands shall be read from the standard input.
1
  • You can just execute the program directly without sh by adding a shebang and making it executable. sh -c 'echo $0 $1' app_name arg1 works while sh -c 'echoargs' app_name arg1 doesn't because as you mentioned the former is a inline script, but the later the program echoargs will run in a new process with its own arguments.
    – xuchunyang
    Commented May 28, 2020 at 8:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.