Exercício

Escreva um programa que usa uma única operação síncrona de sistema de arquivos para ler e imprimir o número de novas linhas que ele contém no console, algo similar à executar cat file | wc -l.

Todo o caminho até o arquivo à ser lido será fornecido como primeiro argumento da linha de comando.

Dicas

Para realizar uma operação de sistema de arquivos (filesystem), você vai precisar do módulo fs da biblioteca (library) principal do Node. Para carregar esse tipo de módulo ou qualquer outro módulo “global”, use o seguinte código:

var fs = require('fs')

Agora você tem o módulo fs completo disponível em uma variável chamada fs.

Todas os métodos de sistema de arquivos síncronas (ou bloqueantes) no módulo fs terminam com Sync.

Para ler um arquivo, você vai precisar usar fs.readFileSync('caminho/do/arquivo'). Esse método irá retornar um objeto Buffer contendo o conteúdo completo do arquivo.

Objetos Buffer são a maneira do Node de representar eficientemente arrays arbitrários de dados, sejam eles ascii, binários ou quaisquer outros formatos. Objetos Buffer podem ser convertidos em strings invocando o método toString() neles, por exemplo var str = buf.toString(). Leia mais sobre o módulo Buffer.

Se você estiver procurando por uma maneira fácil de contar o número de novas linhas em uma string, lembre-se que uma String JavaScript pode ser dividida usando .split() em um array de substrings e que '\n' pode ser usado como um delimitador. Note que o arquivo de teste não possui um caractere de nova linha ('\n') no fim da última linha, então ao usar esse método você vai acabar tendo um array com um elemento a mais do que o número de novas linhas.

Solução

A primeira coisa que você precisará para este exercício será um arquivo de texto simples, veja um exemplo abaixo.

1 linha 01
2 linha 02
3 linha 03
4
5

Eu dei o nome program03-texto.md ao arquivo, ele contém 5 linhas onde as 3 primeiras contém algum conteúdo e as 2 últimas estão vazias.

O segundo passo é analisar o arquivo com o comando Linux sugerido cat file | wc -l, no terminal do Linux execute:

cat program03-texto.md | wc -l
5

O resultado é 5, já sabemos o que esperar de nosso programa.

Agora podemos abrir e ler o arquivo de texto.

var contents = fs.readFileSync("program03-texto.md")

Se olharmos para a variável contents teremos…

<Buffer 6c 69 6e 68 61 20 30 31 0a 6c 69 6e 68 61 20 30 32 0a 6c 69 6e 68 61 20 30 33 0a 0a 0a>

O método toString() vai nos ajudar com a leitura…

contents.toString()
'linha 01\nlinha 02\nlinha 03\n\n\n'

Após aplicarmos o método toString() podemos então dividir a string conforme o caracter de final de linha \n utilizando o método split().

contents.toString().split('\n')

O resultado é um array como o apresentado abaixo:

[ 'linha 01',
  'linha 02',
  'linha 03',
  '',
  '',
  '' ]

Agora podemos saber o tamanho do array acessando a propriedade length.

contents.toString().split('\n').length

Reparamos que o símbolo \n está separando o final da linha. Se contarmos (visualmente) a quantidade de “\n” veremos que são 6 ao todo, mas sabemos que o valor esperado é 5 conforme o resultado do comando cat. Teremos que subtrair uma unidade ao total de linhas.

contents.toString().split('\n').length - 1

Atribuímos o valor a uma variável denominada line.

...
var lines = contents.toString().split('\n').length - 1
console.log(lines)

E não podemos nos esquecer de que o nome do arquivo deve chegar como um parâmetro de entrada na execução do script. Vimos como fazer isso no exercício 02 baby steps.

...
var contents = fs.readFileSync(process.argv[2])
...

Código final

// program03a.js
var fs   = require('fs')
var file = process.argv[2]

var contents = fs.readFileSync(file)
var lines    = contents.toString().split('\n').length - 1
console.log(lines)

Ao checar o resultado (learnyounode verify program03a.js) o aplicativo faz uma observação: “Você poderá evitar o método toString() passando utf8 como segundo parâmetro da função readFileSync() e então você terá uma string.”

Exemplo:

fs.readFileSync(process.argv[2], 'utf8').split('\n').length - 1

Isso significa que podemos despresar a função .tostring(), pois o resultado de fs.readFileSync(file, 'utf8') é igual a fs.readFileSync(file).toString().

Atualizando o arquivo temos…

// program03b.js
var fs   = require('fs')
var file = process.argv[2]

var lines = fs.readFileSync(file, 'utf8').split('\n').length - 1

console.log(lines)