dodgethesteamroller
dodgethesteamroller

Reputation: 1302

Emacs - noninteractive call to shell-command-on-region always deletes region?

The Emacs Help page for the function shell-command-on-region says (elided for space):

(shell-command-on-region START END COMMAND &optional OUTPUT-BUFFER
REPLACE ERROR-BUFFER DISPLAY-ERROR-BUFFER)

...
The noninteractive arguments are START, END, COMMAND,
OUTPUT-BUFFER, REPLACE, ERROR-BUFFER, and DISPLAY-ERROR-BUFFER.
...

If the optional fourth argument OUTPUT-BUFFER is non-nil,
that says to put the output in some other buffer.
If OUTPUT-BUFFER is a buffer or buffer name, put the output there.
If OUTPUT-BUFFER is not a buffer and not nil,
insert output in the current buffer.
In either case, the output is inserted after point (leaving mark after it).

If REPLACE, the optional fifth argument, is non-nil, that means insert
the output in place of text from START to END, putting point and mark
around it.

This isn't the clearest, but the last few sentences just quoted seem to say that if I want the output of the shell command to be inserted in the current buffer at point, leaving the other contents of the buffer intact, I should pass a non-nil argument for OUTPUT-BUFFER and nil for REPLACE.

However, if I execute this code in the *scratch* buffer (not the real code I'm working on, but the minimal case that demonstrates the issue):

(shell-command-on-region
 (point-min) (point-max) "wc" t nil)

the entire contents of the buffer are deleted and replaced with the output of wc!

Is shell-command-on-region broken when used non-interactively, or am I misreading the documentation? If the latter, how could I change the code above to insert the output of wc at point rather than replacing the contents of the buffer? Ideally I'd like a general solution that works not only to run a command on the entire buffer as in the minimal example (i.e., (point-min) through (point-max)), but also for the case of running a command with the region as input and then inserting the results at point without deleting the region.

Upvotes: 2

Views: 1381

Answers (3)

sds
sds

Reputation: 60014

You are wrong

It is not a good idea to use an interactive command like shell-command-on-region in emacs lisp code. Use call-process-region instead.

Emacs is wrong

There is a bug in shell-command-on-region: it does not pass the replace argument down to call-process-region; here is the fix:

=== modified file 'lisp/simple.el'
--- lisp/simple.el  2013-05-16 03:41:52 +0000
+++ lisp/simple.el  2013-05-23 18:44:16 +0000
@@ -2923,7 +2923,7 @@ interactively, this is t."
      (goto-char start)
      (and replace (push-mark (point) 'nomsg))
      (setq exit-status
-       (call-process-region start end shell-file-name t
+       (call-process-region start end shell-file-name replace
                     (if error-file
                     (list t error-file)
                       t)

I will commit it shortly.

Upvotes: 6

Diego Sevilla
Diego Sevilla

Reputation: 29021

In my case (emacs 24.3, don't know what version you're using), the documentation is slightly different in the optional argument:

Optional fourth arg OUTPUT-BUFFER specifies where to put the
command's output.  If the value is a buffer or buffer name, put
the output there.  Any other value, including nil, means to
insert the output in the current buffer.  In either case, the
output is inserted after point (leaving mark after it).

The code that checks whether to delete the output (current) buffer contents is the following:

(if (or replace
    (and output-buffer
     (not (or (bufferp output-buffer) (stringp output-buffer)))))

So clearly putting t as in your case, it is not a string or a buffer, and it is not nil, so it will replace current buffer contents with the output. However, if I try:

(shell-command-on-region
 (point-min) (point-max) "wc" nil nil)

then the buffer is not deleted, and the output put into the "Shell Command Output" buffer. At first sight, I'd say the function is not correctly implemented. Even the two versions of the documentation seem not to correspond with the code.

Upvotes: 1

Stefan
Stefan

Reputation: 28541

If you follow the link to the functions' source code, you'll quickly see that it does:

(if (or replace
    (and output-buffer
     (not (or (bufferp output-buffer) (stringp output-buffer)))))

I don't know why it does that, tho. In any case, this is mostly meant as a command rather than a function; from Elisp I recomend you use call-process-region instead.

Upvotes: 2

Related Questions