How to make Emacs read buffer from stdin on start?

40

8

With Vim I can easily do

$ echo 123 | vim -

Is it possible to do with Emacs?

$ echo 123 | emacs23
... Emacs starts with a Welcome message

$ echo 123 | emacs23 -
... Emacs starts with an empty *scratch* buffer and “Unknown option”

$ echo 123 | emacs23 --insert -
... “No such file or directory”, empty *scratch* buffer

Is it really impossible to read a buffer from a unix pipe?

Edit: As a solution, I wrote a shell wrapper named emacspipe:

#!/bin/sh
TMP=$(mktemp) && cat > $TMP && emacs23 $TMP ; rm $TMP

sastanin

Posted 2009-08-28T15:10:02.943

Reputation: 3 537

Answers

15

Correct, it is impossible to read a buffer from stdin.

The only mention of stdin in the Emacs info pages is this, which says:

In batch mode, Emacs does not display the text being edited, and the standard terminal interrupt characters such as C-z and C-c continue to have their normal effect. The functions prin1, princ and print output to stdout instead of the echo area, while message and error messages output to stderr. Functions that would normally read from the minibuffer take their input from stdin instead.

And the read function can read from stdin, but only in batch mode.

So, you can't even work around this by writing custom elisp.

Trey Jackson

Posted 2009-08-28T15:10:02.943

Reputation: 3 731

13I mean no disrespect to anyone, but this is abhorrent. This is a very basic editor feature and GNU EMACS has been around for decades. It should be built in. – user787832 – 2015-02-06T15:46:26.453

36

You could use process substitution:

$ emacs --insert <(echo 123)

Andrew Wood

Posted 2009-08-28T15:10:02.943

Reputation: 1 139

This is definitely the answer that gets closest to the Vim functionality. Pretty much just moving the piped part into a subprocess substitution. – dbmikus – 2015-01-29T15:42:29.023

@dbmikus I can't decide which I prefer between mine and Tomasz Obrębski's.. – Andrew Wood – 2015-01-29T17:02:46.190

Tomasz's results in me getting the following error for some reason:

emacs: standard input is not a tty – dbmikus – 2015-01-29T18:49:27.450

Oh indeed! I assumed he'd tested before posting. – Andrew Wood – 2015-01-29T19:56:08.133

Hmm.. I just tried Tomasz Obrębski's again, and it works for me this time :/ – Andrew Wood – 2015-02-06T20:11:41.873

1Maybe it depends on the month. – dbmikus – 2015-02-10T18:41:25.383

Thank you very much! Now I have a “wrap” function written with Emacs:

cat file-with-long-lines | emacs -nw --batch -Q --insert <(cat) --eval '(progn (fill-paragraph)(message (buffer-string)))' 2>&1 – Bao Haojun – 2017-02-16T02:31:06.623

what about (t)csh shell which is the default in the bsd family who lack such a feature? – None – 2018-02-09T15:25:12.763

Too bad it does no work with emacsclient – Alien Life Form – 2018-07-13T08:43:19.763

This is a very cool trick! I used it to make an executable Bash script that's also an Emacs lisp file that is effectively a single-file Emacs app installer and runner. https://github.com/jgkamat/matrix-client-el/blob/master/misc/matrix-client-standalone.el

– blujay – 2018-12-20T04:33:28.003

14

You can redirect to a file, then open the file. e.g.

echo 123 > temp; emacs temp

jweede notes that if you want the temp file to automatically be removed, you can:

echo 123 > temp; emacs temp; rm temp

The Emacsy way to do this is to run the shell command in Emacs.

M-! echo 123 RET

That gives you a buffer named *Shell Command Output* with the results of the command.

Richard Hoskins

Posted 2009-08-28T15:10:02.943

Reputation: 10 260

An even more emacs way to do this is to C-u M-! (prefix argument + shell command), which inserts the output at the current point. (the shell to command to execute would be for example cat the/file/you/want) – rien333 – 2017-08-29T22:49:09.933

Yes, I know there is emacsy way, but I hoped it may be used unixy way. Creating a temporary file is not a very nice option (I have to remember to delete it later). – sastanin – 2009-08-31T09:08:49.753

tacking on ';rm temp' should delete the file once emacs closes – jweede – 2009-08-31T12:06:46.993

1such as: echo 123 > temp; emacs temp; rm temp – jweede – 2009-08-31T12:10:34.677

2In general, there is a high impedance between Emacs and Unix. Or at least between Emacs and the traditional Unix work flow. – Richard Hoskins – 2009-08-31T15:28:28.293

2@jweede If you want to add M-! part of my answer to yours, then I could delete my answer. There is a large overlap in our answers, but I think meta-bang is important to future readers. – Richard Hoskins – 2009-08-31T16:03:59.283

1temp may already exist in the current directory, it's not safe; as a solution, I wrote a wrapper: TMP=$(mktemp) && cat > $TMP && emacs23 $TMP ; rm $TMP. Thanks everybody! – sastanin – 2009-09-01T12:36:49.113

9

It is possible, see https://stackoverflow.com/questions/2879746/idomatic-batch-processing-of-text-in-emacs

Here is echo in an emacs script (copied from the above link):

#!/usr/bin/emacs --script
(condition-case nil
    (let (line)
      (while (setq line (read-from-minibuffer ""))
        (princ line)
        (princ "\n")))
  (error nil))

or to read it into a buffer and then print it out all in one go

#!/usr/bin/emacs --script
(with-temp-buffer
  (progn
    (condition-case nil
    (let (line)
      (while (setq line (read-from-minibuffer ""))
        (insert line)
        (insert "\n")))
      (error nil))
    (princ (buffer-string))
    ))

cwitte

Posted 2009-08-28T15:10:02.943

Reputation: 91

6

Another possibility not mentioned in any of the previous answers is to use /dev/stdin if your chosen Unix variant has it.

Simply trying to open /dev/stdin directly doesn't work, because Emacs does a few checks and then reports Symbolic link that points to nonexistent file. (And if Emacs would have allowed you to load the file, then trying to save it again as /dev/stdin would rarely do what the user expected.)

However combining /dev/stdin with the --insert argument does work:

echo 123 | emacs --insert /dev/stdin

It should be noted that this version only works when using X. If you need a solution which works in a terminal I suggest you look at another answer.

kasperd

Posted 2009-08-28T15:10:02.943

Reputation: 2 691

i got emacs: standard input is not a tty message testing it on linux and bsd – None – 2018-02-09T15:28:08.623

1@Chinggis6 Looks like my suggestion only works when using X11. If I first type unset DISPLAY, I get the same error message as you. – kasperd – 2018-02-09T21:59:24.233

1@Chinggis6 I updated my answer to point out that it needs X to work and pointed to an answer which works without X. – kasperd – 2018-02-09T22:05:36.293

6

This works:

echo 123 | emacs --insert <(cat)

but, for some reason, only with graphical-mode emacs (Gnome,Konsole,GNU Emacs 23.4.1). The command:

echo 123 | emacs -nw --insert <(cat)

generates an error 'emacs: standard input is not a tty'. The same error appears when tried in raw text console.

Tomasz Obrębski

Posted 2009-08-28T15:10:02.943

Reputation: 61

echo 123 | exec emacs -nw --insert <(cat) </dev/tty should work. – pyrocrasty – 2015-05-16T17:15:56.473

That works; why the exec? This also works: emacs -nw --insert <(echo 123) </dev/tty – RoyM – 2015-10-28T16:32:55.520

Go figure: works beautifully on Emacs (w32) on Cygwin, where lots of other settings of mine do not – Charles Roberto Canato – 2016-01-05T23:37:57.393

6

It's possible to create a simple shell function which works as it is reading from stdin (although in fact it is writing to a temporary file then reading that). Here's the code I'm using:

# The emacs or emacsclient command to use
function _emacsfun
{
    # Replace with `emacs` to not run as server/client
    emacsclient -c -n $@
}

# An emacs 'alias' with the ability to read from stdin
function e
{
    # If the argument is - then write stdin to a tempfile and open the
    # tempfile.
    if [[ $# -ge 1 ]] && [[ "$1" == - ]]; then
        tempfile="$(mktemp emacs-stdin-$USER.XXXXXXX --tmpdir)"
        cat - > "$tempfile"
        _emacsfun --eval "(find-file \"$tempfile\")" \
            --eval '(set-visited-file-name nil)' \
            --eval '(rename-buffer "*stdin*" t))'
    else
        _emacsfun "$@"
    fi
}

You just use the function as an alias for emacs, e.g.

echo "hello world" | e -

or as normal from files

e hello_world.txt

Replacing emacs by emacsclient in the function works as well.

dshepherd

Posted 2009-08-28T15:10:02.943

Reputation: 288

This works well for me, but _emacsfun should be emacsclient -c -t $@, or at the very least drop the -n option. man pages with emacsclient -t --eval "(man \"$1\")" --eval "(delete-window)" (and now you can helm-swoop your way to Man Glory!) – Alejandro – 2016-03-31T19:14:49.643

1This is the only answer that I could get to work with emacsclient. I made a shell script out of the basic idea so I can call it from i3 config. +1 – ergosys – 2019-03-08T22:39:52.380

2

offhand, something like:

$ echo 123 > tmp.txt; emacs tmp.txt

or

$ echo 123 > tmp.txt; emacs tmp.txt; rm tmp.txt

is an option. Emacs just doesn't integrate with UNIX the way vim does.

jweede

Posted 2009-08-28T15:10:02.943

Reputation: 6 325

except when you're in a shell and want to do something like curl foo.bar | vim - .. I'm sorry. I meant curl foo.bar | emacs except you can't do that – dylnmc – 2018-10-19T01:02:59.567

1It is surprising that Emacs doesn't integrate with UNIX better, given its history and that one of the key tenets of UNIX is that "everything is a file". It feels intuitive to pipe output directly into Emacs. – SabreWolfy – 2011-09-11T11:11:11.773

5@SabreWolfy While GNU Emacs is most commonly hosted on Unix, it isn't "a Unix program" the way Vim is, but rather a Lisp machine that implements a largely platform-independent text editor. (See Richard Hoskins' answer; the "Emacs way" of doing this isn't to pipe shell command into Emacs, but to have invoke the shell command from within Emacs via M-!, which automatically captures the resulting output into a temporary buffer.) Holy wars aside, neither editor is "better" than the other; they just have very different perspectives on pretty much everything. – Aaron Miller – 2013-08-08T15:11:58.780