How do I read a local file in Deno?
Asked Answered
A

3

30

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?

Autry answered 21/8, 2018 at 3:12 Comment(1)
I opened an issue about fetch() syntax for files, track it @ github.com/denoland/deno/issues/2150Spruill
I
32

(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

Ingratitude answered 16/4, 2019 at 10:46 Comment(2)
Hah, I was just listening to Ryan's latest talk last night and thinking "wait that's not the standard module structure I remember". Thanks for the updated answer.Autry
This answer is now out of date with github.com/denoland/deno/releases/tag/v1.2.1 BREAKING(std/fs): remove readFileStr and writeFileStr . Now Deno has Deno.readTextFileSync and Deno.readTextFile to use instead.Hidrosis
A
6

Obsolete: This answer has been broken by the removal of the 'deno' module in favour of the Deno global in release v0.3.4.

Deno includes a readFileSync(filename: string): Uint8Array function in its standard library, which is available at the special import path 'deno'.

import {readFileSync} from 'deno';

const bytes = readFileSync('./input.txt'); 

This reads the contents of the file as a byte array. If the file contains text that you want as a string, as in your example, you can use the standard ECMAScript TextDecoder class (MDN docs).

const utf8Decoder = new TextDecoder('utf-8');
const string = utf8Decoder.decode(bytes);

For the original example, this gives us:

  word_count.ts
import {readFileSync} from 'deno';

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

const main = async () => {
    const decoder = new TextDecoder("utf-8");
    const bytes = readFileSync("./input.txt");
    const text = decoder.decode(bytes);
    const count = countWords(text);
    console.log(`I read ${count} words.`);
};

main();

Which produces the desired output:

$ deno ./word_count.ts
I read 9 words.
Autry answered 21/8, 2018 at 3:12 Comment(0)
K
3

std 0.62.0 / Deno 1.2.1 +

Since std v0.62.0, readFileStr and readFileStrSync are removed from Standard Library.

Deno.readTextFile and Deno.readTextFileSync have the same API and now are the way to go.

(same for writeFileStr / writeFileStrSync)

Katharynkathe answered 2/8, 2020 at 10:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.