diff --git a/Makefile.am b/Makefile.am
index 9ae67a38d5116dcc92594e1d34421104f7c51dd8..870f73f2c6dafd089e4b53e26ed60fbfd0b100ee 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -5,6 +5,7 @@ ncdu_SOURCES=\
 	src/delete.c\
 	src/dirlist.c\
 	src/dir_common.c\
+	src/dir_export.c\
 	src/dir_mem.c\
 	src/dir_scan.c\
 	src/exclude.c\
diff --git a/src/dir.h b/src/dir.h
index e9d42f9bdfdb56cc0e2312011bd1908895f3d118..eded6ef35052483a8e053b6ec22b23734b1391f0 100644
--- a/src/dir.h
+++ b/src/dir.h
@@ -94,6 +94,9 @@ struct dir_output {
  */
 void dir_mem_init(struct dir *);
 
+/* Initializes the SCAN state and dir_output for exporting to a file. */
+int dir_export_init(const char *fn);
+
 
 /* Scanning a live directory */
 extern int dir_scan_smfs;
diff --git a/src/dir_export.c b/src/dir_export.c
new file mode 100644
index 0000000000000000000000000000000000000000..11cff16cd26963e8be12a57d5b24596ebda71d10
--- /dev/null
+++ b/src/dir_export.c
@@ -0,0 +1,146 @@
+/* ncdu - NCurses Disk Usage
+
+  Copyright (c) 2007-2012 Yoran Heling
+
+  Permission is hereby granted, free of charge, to any person obtaining
+  a copy of this software and associated documentation files (the
+  "Software"), to deal in the Software without restriction, including
+  without limitation the rights to use, copy, modify, merge, publish,
+  distribute, sublicense, and/or sell copies of the Software, and to
+  permit persons to whom the Software is furnished to do so, subject to
+  the following conditions:
+
+  The above copyright notice and this permission notice shall be included
+  in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+*/
+
+#include "global.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+
+static FILE *stream;
+static int level; /* Current level of nesting */
+
+
+static void output_string(const char *str) {
+  for(; *str; str++) {
+    switch(*str) {
+    case '\n': fputs("\\n", stream); break;
+    case '\r': fputs("\\r", stream); break;
+    case '\b': fputs("\\b", stream); break;
+    case '\t': fputs("\\t", stream); break;
+    case '\f': fputs("\\f", stream); break;
+    case '\\': fputs("\\\\", stream); break;
+    case '"':  fputs("\\\"", stream); break;
+    default:
+      if((unsigned char)*str <= 31 || (unsigned char)*str == 127)
+        fprintf(stream, "\\u00%02x", *str);
+      else
+        fputc(*str, stream);
+      break;
+    }
+  }
+}
+
+
+static void output_int(uint64_t n) {
+  char tmp[20];
+  int i = 0;
+
+  do
+    tmp[i++] = n % 10;
+  while((n /= 10) > 0);
+
+  while(i--)
+    fputc(tmp[i]+'0', stream);
+}
+
+
+static void output_info(struct dir *d) {
+  fputs("{\"name\":\"", stream);
+  output_string(d->name);
+  fputc('"', stream);
+  /* No need for asize/dsize if they're 0 (which happens with excluded or failed-to-stat files) */
+  if(d->asize) {
+    fputs(",\"asize\":", stream);
+    output_int((uint64_t)d->asize);
+  }
+  if(d->size) {
+    fputs(",\"dsize\":", stream);
+    output_int((uint64_t)d->size);
+  }
+  /* TODO: No need to include a dev is it's the same as the parent dir. */
+  fputs(",\"dev\":", stream);
+  output_int(d->dev);
+  fputs(",\"ino\":", stream);
+  output_int(d->ino);
+  if(d->flags & FF_HLNKC) /* TODO: Including the actual number of links would be nicer. */
+    fputs(",\"hlnkc\":true", stream);
+  if(d->flags & FF_ERR)
+    fputs(",\"read_error\":true", stream);
+  if(!(d->flags & (FF_DIR|FF_FILE)))
+    fputs(",\"notreg\":true", stream);
+  if(d->flags & FF_EXL)
+    fputs(",\"excluded\":\"pattern", stream);
+  else if(d->flags & FF_OTHFS)
+    fputs(",\"excluded\":\"othfs", stream);
+  fputc('}', stream);
+}
+
+
+/* TODO: Error handling / reporting! */
+static void item(struct dir *item) {
+  if(!item) {
+    if(!--level) { /* closing of the root item */
+      fputs("]]", stream);
+      fclose(stream);
+    } else /* closing of a regular directory item */
+      fputs("]", stream);
+    return;
+  }
+
+  dir_output.items++;
+
+  /* File header.
+   * TODO: Add scan options? */
+  if(item->flags & FF_DIR && !level++)
+    fputs("[1,0,{\"progname\":\""PACKAGE"\",\"progver\":\""PACKAGE_VERSION"\"}", stream);
+
+  fputs(",\n", stream);
+  if(item->flags & FF_DIR)
+    fputc('[', stream);
+
+  output_info(item);
+}
+
+
+static int final(int fail) {
+  return fail ? 1 : 1; /* Silences -Wunused-parameter */
+}
+
+
+int dir_export_init(const char *fn) {
+  /* TODO: stdout support */
+  if((stream = fopen(fn, "w")) == NULL)
+    return 1;
+
+  level = 0;
+  pstate = ST_CALC;
+  dir_output.item = item;
+  dir_output.final = final;
+  dir_output.size = 0;
+  dir_output.items = 0;
+  return 0;
+}
+
diff --git a/src/main.c b/src/main.c
index 4088f59e65d93439a0a7cba69920ed9ec57984fd..176d51f874d6fd0703f1fff2b4bd5cb58fa81b70 100644
--- a/src/main.c
+++ b/src/main.c
@@ -103,14 +103,16 @@ int input_handle(int wait) {
 /* parse command line */
 static char *argv_parse(int argc, char **argv) {
   int i, j, len;
+  char *export = NULL;
   char *dir = NULL;
-  dir_ui = 2;
+  dir_ui = -1;
 
   /* read from commandline */
   for(i=1; i<argc; i++) {
     if(argv[i][0] == '-') {
       /* flags requiring arguments */
-      if(!strcmp(argv[i], "-X") || !strcmp(argv[i], "-u") || !strcmp(argv[i], "--exclude-from") || !strcmp(argv[i], "--exclude")) {
+      if(!strcmp(argv[i], "-X") || !strcmp(argv[i], "-u") || !strcmp(argv[i], "-o")
+          || !strcmp(argv[i], "--exclude-from") || !strcmp(argv[i], "--exclude")) {
         if(i+1 >= argc) {
           printf("Option %s requires an argument\n", argv[i]);
           exit(1);
@@ -121,7 +123,9 @@ static char *argv_parse(int argc, char **argv) {
             exit(1);
           }
           dir_ui = argv[i][0]-'0';
-        } else if(strcmp(argv[i], "--exclude") == 0)
+        } else if(strcmp(argv[i], "-o") == 0)
+          export = argv[++i];
+        else if(strcmp(argv[i], "--exclude") == 0)
           exclude_add(argv[++i]);
         else if(exclude_addfile(argv[++i])) {
           printf("Can't open %s: %s\n", argv[i], strerror(errno));
@@ -144,6 +148,7 @@ static char *argv_parse(int argc, char **argv) {
             printf("  -v                         Print version\n");
             printf("  -x                         Same filesystem\n");
             printf("  -r                         Read only\n");
+            printf("  -o FILE                    Export scanned directory to FILE\n");
             printf("  -u <0-2>                   UI to use when scanning (0=minimal,2=verbose)\n");
             printf("  --exclude PATTERN          Exclude files that match PATTERN\n");
             printf("  -X, --exclude-from FILE    Exclude files that match any pattern in FILE\n");
@@ -158,6 +163,20 @@ static char *argv_parse(int argc, char **argv) {
     } else
       dir = argv[i];
   }
+
+  if(export) {
+    /* TODO: Support exporting to stdout */
+    if(dir_export_init(export)) {
+      printf("Can't open %s: %s\n", export, strerror(errno));
+      exit(1);
+    }
+  } else
+    dir_mem_init(NULL);
+
+  /* Use the single-line scan feedback by default when exporting. */
+  if(dir_ui == -1)
+    dir_ui = export ? 1 : 2;
+
   return dir;
 }
 
@@ -186,7 +205,6 @@ int main(int argc, char **argv) {
   if((dir = argv_parse(argc, argv)) == NULL)
     dir = ".";
 
-  dir_mem_init(NULL);
   dir_scan_init(dir);
 
   if(dir_ui == 2)
@@ -202,8 +220,11 @@ int main(int argc, char **argv) {
     }
 
     if(pstate == ST_CALC) {
-      if(dir_scan_process())
+      if(dir_scan_process()) {
+        if(dir_ui == 1)
+          fputc('\n', stderr);
         break;
+      }
     } else if(pstate == ST_DEL)
       delete_process();
     else if(input_handle(0))