echo '<h1>hello, world</h1>' |  firefox
cat index.html | firefox

These commands don't work.
If firefox can read stdin, I can send HTML to firefox via a pipe.
Is it possible to make firefox read stdin?

  • 2
    What exactly you want to accomplish?
    – pbm
    Nov 19, 2011 at 9:07
  • 7
    @pbm: It might be useful to avoid storing temporary data...
    – l0b0
    Feb 2, 2012 at 10:39

9 Answers 9


You can use data URIs, like this:

echo '<h1>hello, world</h1>' |firefox "data:text/html;base64,$(base64 -w 0 <&0)"

&0 is the file descriptor for stdin, so it encodes stdin to base64, then interpolates that into the data URI.

The same trick works for other browsers, too:

echo '<h1>hello, world</h1>' |chromium "data:text/html;base64,$(base64 -w 0 <&0)"
echo '<h1>hello, world</h1>' |opera    "data:text/html;base64,$(base64 -w 0 <&0)"

If you want, you can put the second part in a bash script (I'll call it pipefox.sh):

firefox "data:text/html;base64,$(base64 -w 0 <&0)"

Now you can do:

echo '<h1>hello, world</h1>' |pipefox.sh
  • 1
    totally awesome!, How the hell did you came up with this?. You can improve pipefox.sh renaming to pipebrowser with context: $1 "data:text/html;base64,$(base64 -w 0 <&0)" allowing to choose the browser to your liking
    – albfan
    Apr 22, 2014 at 22:24
  • I have a similar question here if it is the same difference, stackoverflow.com/questions/32303025/… Aug 31, 2015 at 2:25
  • 4
    This unfortunately doesn't work anymore, see blog.mozilla.org/security/2017/11/27/… for why most mimetypes in data urls now get blocked from toplevel navigation.
    – TheDiveO
    Jul 23, 2018 at 19:07

The short answer is, you're better off writing a temporary file and opening that. Getting pipes to work properly is more complicated and probably won't give you any extra advantages. That said, here's what I've found.

If your firefox command is actually starting Firefox instead of talking with an already-running Firefox instance, you can do this:

echo '<h1>hello, world</h1>' | firefox /dev/fd/0

Which tells Firefox explicitly to read its standard input, which is where the pipe is putting its data. But if Firefox is already running, the firefox command is just going to pass that name to the main Firefox process, which will read its own standard input, which probably won't give it anything and certainly isn't connected to your pipe.

Furthermore, when reading from a pipe, Firefox buffers things pretty heavily, so it's not going to update the page each time you give it a new line of HTML, if that's what you're going for. Try closing Firefox and running:

cat | firefox /dev/fd/0

(N.B. you do actually need the cat here.) Paste some long lines into your shell window repeatedly until Firefox decides to update the page, and you can see how much data it takes. Now send an End-Of-File signal by hitting Ctrl+D on a new line, and watch Firefox update instantly. But then you can't add any more data.

So best is probably:

echo '<h1>hello, world</h1>' >my_temporary_file; firefox my_temporary_file
  • 2
    You can force firefox to open a new process with -new-instance, so it becomes ... | firefox -new-instance /dev/fd/0.
    – rampion
    Jan 23, 2015 at 14:44
  • this works great, thanks! anyone know how to do this with Chrome instead? Apr 17, 2018 at 21:07
  • 1
    Not working for me with Firefox 75.0. I get a blank page. FWIW the URL is file:///dev/null. May 4, 2020 at 4:18

I found this:

bcat -- pipe to browser utility

... to install on Ubuntu Natty, I did:

sudo apt-get install rubygems1.8
sudo gem install bcat
# to call
ruby -rubygems /var/lib/gems/1.8/gems/bcat-0.6.2/bin/bcat
echo "<b>test</b>" | ruby -rubygems /var/lib/gems/1.8/gems/bcat-0.6.2/bin/bcat

I thought it works with its own browser - but running the above opened a new tab in an already running Firefox, pointing at a localhost address ... With bcat installation you can also do:

tail -f /var/log/syslog | ruby -rubygems /var/lib/gems/1.8/gems/bcat-0.6.2/bin/btee

... a tab will again open, but Firefox will keep showing the loading icon (and apparently would update the page when syslog updates).

The bcat homepage also references the uzbl browser, which can apparently handle stdin - but for its own commands (should probably look into this more, though)

EDIT: As I needed something like this badly (mostly to view HTML tables with data generated on the fly (and my Firefox is getting really slow to be useful with bcat), I tried with a custom solution. Since I use ReText, I already had installed python-qt4 and WebKit bindings (and dependencies) on my Ubuntu. So, I put together a Python/PyQt4/QWebKit script - which works like bcat (not like btee), but with its own browser window - called Qt4WebKit_singleinst_stdin.py (or qwksisi for short):

Basically, with the downloaded script (and dependencies) you can alias it in a bash terminal like this:

$ alias qwksisi="python /path/to/Qt4WebKit_singleinst_stdin.py"

... and in one terminal (after aliasing), qwksisi will raise the master browser window; while in another terminal (again after aliasing), one could do the following to obtain stdin data:

$ echo "<h1>Hello World</h1>" | qwksisi - 

... as shown below:


Don't forget the - at end to refer to stdin; otherwise a local filename can be used as last argument, as well.

Basically, the problem here is to solve:

  • single instance problem (so first run of script becomes a "master" and raises a browser window - while subsequent runs simply pass data to master and exit)
  • interprocess communication for sharing variables (so exiting processes can pass data to the master browser window)
  • Timer update in the master that checks for new content, and updates the browser window if new content arrived.

As such, the same could be implemented in, say, Perl with Gtk bindings and WebKit (or other browser component). I wonder, though, if the XUL framework by Mozilla could be used to implement the same functionality - I guess in that case, one would work with the Firefox browser component.

  • The link is broken.
    – user298742
    Jul 9, 2020 at 21:26

Look what searching for 'browser stdin' turned up!, a nice little shell script:


# read from stdin, write to a temp file, open the temp file in a browser, then delete it
tmpfile=$(tempfile); cat > $tmpfile; x-www-browser $tmpfile; rm $tmpfile

If you save this in stdin2www, make it executable (chmod +x stdin2www), your examples should work via cat index.html | ./stdin2www. Just note that relative links, images, etc, will fail since the page that will be opened is something /tmp/; more work would be needed to fix this.

  • mktemp on macos and probably other. Also rm-ing file was to quick for browser to open on macos.
    – rofrol
    Jan 14 at 15:06

You can use process substitution:

 firefox <( echo '<h1>hello, world</h1>' )

 firefox <( cat page_header.html contents.html footer.html )

 firefox  <( echo "<h1>Hello number "{1..23}"!</h1>" )
  • 2
    Can't get these to work with Ubuntu 14.04 using bash and Firefox 29.0 May 7, 2014 at 22:07

I wrote a python script to write stdin to a temporary file and then open the temporary file with Firefox.

#!/usr/bin/env python
import sys
import tempfile
import subprocess

with tempfile.NamedTemporaryFile() as f:
  process = subprocess.Popen(['firefox', f.name])

You can run the below command from a shell script/terminal window.

Before launching Firefox (or any other browser), it will read from it's stdin the content to display upon opening.

If it is not HTML being sent, change the text/html string in the below URL to whatever the file type is (for example, text/plain or image/png).

firefox "data:text/html;base64,$(base64)"

Although this question is seven years old I am surprised that nobody proposed a solution serving the file through a web server. This is achieved with the following compact Python3 script. Save it as an executable file, say, browse.py:

#!/usr/bin/env python3
import sys, os, time, platform, signal
from subprocess import Popen
from http.server import HTTPServer, BaseHTTPRequestHandler
sys.stderr = open(os.devnull, 'w')
def timeoutHandler(signum, frame):
signal.signal(signal.SIGALRM, timeoutHandler)
html = sys.stdin.read()
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8000
class Handler(BaseHTTPRequestHandler):
    def _set_headers(self):
        self.send_header("content-type", "text/html")
    def do_GET(self):
        self.wfile.write(b = bytes(html, "utf-8"))
platform = platform.system().lower()
if platform.find("win") >= 0: command = "start"
elif platform.find("mac") >= 0 or platform.find("darwin") >= 0: command = "open"
else: command = "xdg-open"
p = Popen([command, "http://localhost:" + str(port) + "/"])
httpd = HTTPServer(("localhost", port), Handler)

Then you can redirect standard input to the default browser:

./browser.py < somewebpage.html
echo "<html><body><h1>Hello</h1></body></html>" | browse.py

By default the server operates on port 8000, but that behavior can be changed with a command line argument:

./browser.py 9000 < website.html

I tested this script on Linux. It should handle other UNIX systems including MacOS out of the box. It is in principle even prepared for Windows (I don't have one for testing), but there it may be necessary to implement the timeout functionality differently.


A simple ffpipe alias.

note (Aug 2020): This no longer works to start a new instance in the latest Firefox, but still works with an existing instance open.

The data URI solutions given by snowball and luk3yx aren't working for me on GNU/Linux.

The following alias should work:

alias ffpipe='base64 -w0 <&0 | read -r x; firefox "data:text/html;base64,$x"'


echo '<h1>hello, world</h1>' | ffpipe


The page will only load once the pipe is closed (ie. end-of-file has been reached).

If incremental rendering of the piped content is required, you're better off using something like the previously-mentioned bcat utility.

  • 1
    @downvoters: if a four-year-old answer has issues in the latest version of an application, it would be more constructive to make a comment to that effect.
    – pyrocrasty
    Aug 14, 2020 at 16:06
  • With Firefox 81 this still works (or again?). Only if Firfox is not running you have the problem of it being started by the alias in the shell session. So Ctrl-C in the terminal kills the firefox instance. Oct 16, 2020 at 8:35
  • I found one flaw with this solution. If the html output is large (like a report generated with pandoc) the my zsh will complain with zsh: argument list too long: Oct 16, 2020 at 8:45

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .