Reputation: 18055
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
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
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
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