39

How do I receive text from stdin into sublimetext editor? With vim it works like this:

echo "potato potato potato" | vim -

The same thing works with gedit, creating a new document with the contents.

Sublimetext seems to just start editing a file called "-" in a new tab, is there some other shell trick which could work?

wim
  • 338,267
  • 99
  • 616
  • 750
  • 1
    I tried this on mac: echo "hey" | sublime - I made a new file called 'subl stdin ICgUXZ.txt' with the contents 'hey'. Seems to be working for me. – Siddhartha Jan 14 '14 at 22:48
  • 3
    @Siddharta: From http://sublimetext.userecho.com/topic/87972-allow-editing-stdin-on-linux/ : "The 'subl' command-line on OS X allows for editing or displaying stdin; but commandline invocation on Linux appears to only support existing filesystem files." – Ruud Helderman Jan 14 '14 at 22:54
  • Please clarify and tag the OS you're working on - sounds like Linux. – mklement0 Jan 14 '14 at 23:50
  • Ubuntu 13.10, and I'm fresh out of tags .. – wim Jan 15 '14 at 00:00

8 Answers8

21

Assuming Sublime still doesn't support opening STDIN, a straightforward solution is to dump the output to a temporary file, open the file in sublime, and then delete the file. Say you create a script called tosubl in your bin directory as follows:

#!/bin/bash

TMPDIR=${TMPDIR:-/tmp}  # default to /tmp if TMPDIR isn't set
F=$(mktemp $TMPDIR/tosubl-XXXXXXXX)
cat >| $F  # use >| instead of > if you set noclobber in bash
subl $F
sleep .3  # give subl a little time to open the file
rm -f $F  # file will be deleted as soon as subl closes it

Then you could use it by doing something like this:

$ ps | tosubl

But a more efficient and reliable solution would be to use to use Process Substitution if your shell supports it (it probably does). This does away with the temporary files. Here it is with bash:

tosubl() {
    subl <(cat)
}

echo "foo" | tosubl

I'm pretty sure you can somehow remove the use of cat in that function where it's redundantly shoveling bits from stdin to stdout, but the syntax to do so in that context eludes me at the moment.

tylerl
  • 30,197
  • 13
  • 80
  • 113
15

I don't know Sublime Text, but your problem should be generic in that it applies to any program that does accept a filename as argument, but refuses to read from stdin.

Fortunately, Bash allows you to pipe stdout from one process into some kind of temporary file, then pass the name of that file to another process.

From man bash:

Process substitution is supported on systems that support named pipes (FIFOs) or the /dev/fd method of naming open files. It takes the form of <(list) or >(list). The process list is run with its input or output connected to a FIFO or some file in /dev/fd. The name of this file is passed as an argument to the current command as the result of the expansion. If the >(list) form is used, writing to the file will provide input for list. If the <(list) form is used, the file passed as an argument should be read to obtain the output of list.

Assuming SomeProcess produces output that you would like to capture in your editor:

sublimetext <(SomeProcess)

or:

SomeProcess | sublimetext <(cat)

If you think you will be typing this in by hand a lot, then you may want to put sublimetext <(cat) into a shell script or alias.

Just in case your OS does not support process substitution, then you can always specify a temporary file yourself of course:

SomeProcess > /tmp/myoutput
sublimetext /tmp/myoutput
Ruud Helderman
  • 10,563
  • 1
  • 26
  • 45
  • That's strange, `subl <(echo "potato")` does work, but `subl <(history)` and `subl <(ls)` don't – wim Jan 14 '14 at 23:00
  • Actually, `subl <(echo)` is not reliably working now either. Some kind of buffering going on here maybe ... – wim Jan 14 '14 at 23:01
  • `subl <(history)` kinda worked once, and then the text inside the editor just disappeared after a few seconds! d'oh! – wim Jan 14 '14 at 23:03
  • 1
    Possibly the CLI invocation of Sublime spawns a new (GUI) process for the editor; the original process gets killed, and the OS automatically removes the temporary file. Apparently Sublime actively monitors any 'outside' changes to the file, and responds to its deletion. I'm afraid the final suggestion in my answer above is your best option: specify a temp file yourself. FYC, you can wrap that logic in a shell script: `cat > /tmp/myoutput && sublime /tmp/myoutput` – Ruud Helderman Jan 14 '14 at 23:14
  • @wim: Similar question: http://stackoverflow.com/questions/20605150/how-i-can-save-git-commit-messages-to-the-file – Ruud Helderman Jan 27 '14 at 12:50
  • This seems to work for me in that it provides the data and shows it in sublime, but unfortunately, for some reason, sublime thinks it's a binary file and opens in hex mode. Any ideas why that might be? – Vala Feb 24 '16 at 15:19
  • @Thor84no Does the same happen if you pipe to file and open that file in Sublime? If not, then the bottom solution in my answer should work for you. If the problem persists, then I suppose there _is_ something in your output that is considered 'binary' by Sublime. Check the hex dump; maybe there's a NUL character in there (`00` in hex). Also check character encoding. – Ruud Helderman Feb 24 '16 at 15:46
  • Piping to a file does work, I was just hoping to skip that step. I tried the answer suggesting piping it to `vipe` and that seems to work, so at least there's that. – Vala Feb 24 '16 at 21:47
  • `FIFO` does not work with Sublime. I have no problem doing `leafpad <(echo "a")`. If I do `subl <(echo "a")`, Sublime opens a list of hexadecimal numbers, 8 per line, with many lines (6000+) – Marco Sulla Jul 26 '19 at 12:56
14

You might find vipe from moreutils useful. If you've set EDITOR='subl --wait', you can simply:

echo 'potato potato potato' | vipe
ændrük
  • 782
  • 1
  • 8
  • 22
  • This is my solution, because @RuudHelderman's answer generates effect witch looks like Disk Editor in Sublime Text (504b 0304 1400 0000 0000 7b99 4b4d ed4c). The problem with `vipe` is that it does not end and blocks command line. I have to press `CTRL+C` to stop it. – Marecky Aug 09 '19 at 10:15
  • setting `EDITOR=subl` (without --wait) does not fill the new Sublime document with content (my command is `ls | vipe`) – Marecky Aug 09 '19 at 10:18
12

Starting from version 4, SublimeText supports piping in from stdin.

From the changelog:

Build 4063

Command Line: subl - can now be used to read from stdin on all platforms

Update !only! on MacOS: As of build 4109

subl can now be used to edit stdin, eg: echo test | subl | cat

milkman
  • 476
  • 9
  • 11
  • 2
    The stable branch of Sublime Text 4 has been released, so now it supports stdin input. Read: https://www.sublimetext.com/blog/articles/sublime-text-4 – Loscil94 May 26 '21 at 18:05
  • I was hoping to be able to open up to a specific line so instead of `subl file.txt:345` do something like `git show master:file.txt | subl -:345` to open up on a specific line from stdin. Has anyone figured out a way to do that? I've tried passing the line to the flag like this but no luck `subl --command ':345' -`. – Andrew Jun 10 '21 at 05:13
  • Note it's worth remembering that there may be a lag example if `kustomize build .` normally lags for 4 seconds and then goes to stdout, then `kustomize build . | subl -` (will also lag for 4 seconds before working, I was impatient and thought it wasn't working, then waited long enough on accident once.) – neoakris Mar 13 '22 at 02:01
1

Piggybacking on https://stackoverflow.com/a/31035834/2429912 a bit, since for me it does 90% but not all of it:

Using a temporary file is a way that can be used with virtually any editor. Even better if the editor supports waiting until the file is closed(sublime -w for Sublime Text) you can actually edit it on the fly, which makes it more versatile. To do that you need to alter the script @tylerl provided - script named tosubl in your PATH:

#!/bin/bash

TMPDIR=${TMPDIR:-/tmp} # default to /tmp if TMPDIR isn't set
F=$(mktemp $TMPDIR/tosubl-XXXXXXXX)
cat >| $F # use >| instead of > if you set noclobber in bash
subl -w $F # waits until the file is closed
cat $F # prints out the contents of the file to stdout
rm -f $F # clean up the file

Now running echo "hello" | tosubl > local.file would open the output of the first script in Sublime and once you have closed it, save it to local.file.

Rauno56
  • 31
  • 4
1

If you want to display colored text with ANSI escape sequences (a terminal buffer for example), you can install the package ANSIescape and use the following command:

F=$(mktemp); cat > $F; subl $F; subl --command ansi; sleep .5; rm -f $F

danmou
  • 321
  • 2
  • 4
1

Well, it's 2020 and sublime still can't (won't?) read from stdin. None of the answers in this thread were working satisfactorily for me due to the program I was having pass input to sublime.

In my case, I wanted to take advantage of the "paste current selection to program" capability of the kitty terminal emulator. That functionality takes the current selection in the terminal and pipes it into a program for you when you press the keyboard shortcut. For some reason, the developer of kitty decided to pass the selection as an argument rather than via stdin.

To remedy this, I wrote a small C script that can handle both scenarios. In order to run the script, you'll need to install TCC. You may want to check your repo first. I know on debian you can install it with apt: sudo apt install tcc. Alternatively, you can compile the following code with GCC, just remove the first line.

#!/usr/bin/tcc -run

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    uint64_t randVal = 0;
    FILE *fp = fopen("/dev/urandom", "rb");
    fread((char *) &randVal, 8, 1, fp);
    fclose(fp);
    
    char tmpfile[512] = {0};
    sprintf(tmpfile, "/tmp/piped_input_%llu.txt", randVal);
    
    fp = fopen(tmpfile, "wb");
    
    if(!fp)
    {
        fprintf(stderr, "Could not open temporary file: '%s'\n", tmpfile);
        return -1;
    }
    
    if(argc == 2)
        fprintf(fp, "%s", argv[1]);
        
    if(argc == 1)
    {
        uint8_t tmpBuf[4096] = {0};
        memset(tmpBuf, 0, 4096); // Old habit... ¯\_(ツ)_/¯
        
        while(!feof(stdin))
        {
            int32_t numRead = fread((char *) tmpBuf, 1, 4096, stdin);
            
            if(numRead > 0)
                fwrite((char *) tmpBuf, numRead, 1, fp);
            else
                break;
        }
    }
    
    fflush(fp);
    fclose(fp);
    
    char cmdStr[512] = {0};
    sprintf(cmdStr, "subl %s", tmpfile);
    system(cmdStr);
    
    // Give sublime some time to open the file
    usleep(1000 * 250);
    unlink(tmpfile);
    
    return 0;
}

The above script will read from stdin unless an argument is passed to the program. In either case, it simply reads the input (stdin or argv[1]) into a temporary file, opens the file with sublime, waits ~250ms to give time for sublime to open the file, and deletes the temporary file.

You can copy the above code to a location in your path (like /usr/local/bin/subl_pipe). Make sure set the permissions: chmod 755 /usr/local/bin/subl_pipe.

Anyway, I hope this is useful to somebody else eventually... ^_^

Gogeta70
  • 881
  • 1
  • 9
  • 23
1

There is a straightforward way to do this: you can pipe input to Sublime Text (build 4121 and up) as it supports reading from STDIN:

cat file.txt | subl - 
Henry Ecker
  • 34,399
  • 18
  • 41
  • 57
tastydiff
  • 141
  • 8