How do I read a local file in Deno?

Jeremy picture Jeremy · Aug 21, 2018 · Viewed 9.2k times · Source

I'm writing a word count program written in TypeScript that I'm trying to run in Deno. I'm invoking it with no arguments, just deno ./word_count.ts, so it should have the default read-only filesystem access. I was hoping that I might be able to use the standard browser fetch() API with the file: URL scheme to read from the filesystem, like this:

  word_count.ts
const countWords = (s: string): number =>
s.split(/\s+/g).filter(w => /[a-z0-9]/.test(w)).length;

const main = async () => {
    const text = await (await fetch("file:///./input.txt")).text();
    const count = countWords(text);
    console.log(`I read ${count} words.`);
};

main();
  input.txt
The quick brown fox jumps over the lazy dog.

But when I try I see I see that fetch doesn't support file URLs:

Error: an error occurred trying to connect: invalid URL, scheme must be http
    at FetchResponse.onMsg (deno/js/fetch.ts:121:21)
    at FetchRequest.onMsg (deno/js/fetch.ts:152:19)
    at onFetchRes (deno/js/fetch.ts:28:8)
    at onMessage$1 (deno/js/main.ts:30:7)

How can I read the contents of a local file in Deno?

Answer

rsp picture rsp · Apr 16, 2019

(Update: wrapping code in async function main() { ... } is no longer needed because Deno now supports top-level await)

Using built-in Deno.readTextFile

const countWords = (s: string): number =>
  s.split(/\s+/g).filter(w => /[a-z0-9]/.test(w)).length;

const text = await Deno.readTextFile('input.txt');
const count = countWords(text);
console.log(`I read ${count} words.`);

See the docs at: https://doc.deno.land/builtin/stable#Deno.readTextFile

Using built-in Deno.readFile

const countWords = (s: string): number =>
  s.split(/\s+/g).filter(w => /[a-z0-9]/.test(w)).length;

const decoder = new TextDecoder('utf-8');

const text = decoder.decode(await Deno.readFile('input.txt'));
const count = countWords(text);
console.log(`I read ${count} words.`);

Note that you need to explicitly decode the data as UTF-8.

See the docs at: https://deno.land/typedoc/index.html#readfile

Using blocking reads

The accepted answer uses readFileSync() which is a blocking function so the main() being async is not needed (Update: it is no longer needed for non-blocking await as well). A simplified (and working) example would be:

const countWords = (s: string): number =>
  s.split(/\s+/g).filter(w => /[a-z0-9]/.test(w)).length;

const decoder = new TextDecoder('utf-8');

const text = decoder.decode(Deno.readFileSync('input.txt'));
const count = countWords(text);
console.log(`I read ${count} words.`);

Note that there is no await anywhere, so the code is slightly simpler (Update: before Deno supported top-level await the difference in simplicity was bigger) but the Deno.readFileSync() will block the thread until the file is read - for a simple script that does a sequence of steps like in this example this is fine, but if it was inside a request handler in a server then it would be a disaster for the performance.

Using lower-level Deno.open and Deno.readAll

const countWords = (s: string): number =>
  s.split(/\s+/g).filter(w => /[a-z0-9]/.test(w)).length;

const decoder = new TextDecoder('utf-8');

const file = await Deno.open('input.txt');
const text = decoder.decode(await Deno.readAll(file));
const count = countWords(text);
console.log(`I read ${count} words.`);

You could put the first two lines of main() in a single line:

const text = decoder.decode(await Deno.readAll(await Deno.open('input.txt')));

but it would be less readable.

See the docs at: https://deno.land/typedoc/index.html#readall

Even lower-level Deno.open and Deno.read

You could use even lower-lever Deno.read but then you'd also have to allocate the buffers

See the docs at: https://deno.land/typedoc/index.html#read

Using new File() abstraction

There is also a class-style abstraction for reading and writing files.

See the docs at: https://deno.land/typedoc/classes/deno.file.html