5

I want to run the following bash script, that is stored in an Elisp string, not in a .sh file, then store the shell output in a variable.

#!/bin/bash
IFS=: read -ra _dirs_in_path <<< "$PATH"

for _dir in "${_dirs_in_path[@]}"; do
    for _file in "${_dir}"/*; do
        [[ -x ${_file} && -f ${_file} ]] && printf '%s\n' "${_file##*/}"
    done
done

I couldn't run shell-command on bash scripts, consisting of multiple strings. Emacs and Long Shell Commands didn't help me either, as compile and comint-run also require commands, not bash syntax.

How do i run a complex bash script from Elisp?

1
  • if M-x shell-command-on-region fails, please send a bug report Commented Jul 27, 2012 at 18:48

3 Answers 3

6

Multiline commands are fine to provide as an argument to bash -c if you quote them as you would any other shell argument that might contain shell metacharacters, e.g.:

(setq my-command
      (concat "IFS=: read -ra dirs <<<\"$PATH\"\n"
              "for dir in ${dirs[@]}; do\n"
              " echo got dir \"$dir\"\n"
              "done\n"))

(shell-command (format "bash -c %s" (shell-quote-argument my-command)))
Sign up to request clarification or add additional context in comments.

Comments

2

This will perhaps do what you want. Add modifiers to taste :)

(defun example-multiline-shell-command ()
  (interactive)
  (with-temp-buffer
    (insert "#!/bin/bash
IFS=: read -ra _dirs_in_path <<< \"$PATH\"

for _dir in \"${_dirs_in_path[@]}\"; do
    for _file in \"${_dir}\"/*; do
        [[ -x ${_file} && -f ${_file} ]] && printf '%s\n' \"${_file##*/}\"
    done
done")
    (write-region (point-min) (point-max) "~/temp.sh")
    (shell-command "source ~/temp.sh" (current-buffer))
    (buffer-string)))

EDIT Oh, and FYI "${_dirs_in_path[@]}" is going to end bad if files have spaces or other characters that might be treated as separators in the names.

1 Comment

No, the "${arrayvar[@]}" syntax is perfectly safe regardless of the presence of shell metacharacters in any element of arrayvar.
0

shell-command actually works on multi-string bash syntax. My problem was that shell-command doesn't know bash environment variables, including PATH. What i did: replaced all " with \" in the script and put it in an elisp string, then assigned some directories to PATH. Here is the code, that successfully outputs all executables in the system to the *Shell Command Output* buffer.

(let ((path "PATH='/usr/local/bin:/usr/bin:/bin'")
      (command "IFS=: read -ra _dirs_in_path <<< \"$PATH\"

for _dir in \"${_dirs_in_path[@]}\"; do
    for _file in \"${_dir}\"/*; do
        [[ -x ${_file} && -f ${_file} ]] && printf '%s\n' \"${_file##*/}\"
    done
done"))
  (shell-command (concat path ";" command)))

I'm still interested in how to make compile work with multi-string bash scripts too.

Note on PATH: I didn't use (getenv "PATH") in the above solution because, as far as i understand, X display managers (including xdm, gdm and kdm) do not run shell before Xsession, so an emacs run from GUI will have different environment variables from the bash ones. I run emacs --daemon on startup through cron, my paths are set up in /etc/profile and ~/.profile, so Emacs doesn't get its PATH from there.

Steve Purcell proposes a code (see also variant one and two of it on SO) to make sure Emacs has the same environment variables, including PATH, as the shell.

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.