Mirzhan Irkegulov
Mirzhan Irkegulov

Reputation: 18055

Run multi-string bash script from Emacs

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?

Upvotes: 5

Views: 1218

Answers (3)

user797257
user797257

Reputation:

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.

Upvotes: 2

Sean
Sean

Reputation: 29772

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)))

Upvotes: 6

Mirzhan Irkegulov
Mirzhan Irkegulov

Reputation: 18055

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.

Upvotes: 0

Related Questions