По аналогии с командой dir
в MS Windows (а точнее в DOS) или с командой ls
в Unix/Linux, мы напишем скрипт на Node.js,
реализующий такое же поведение. Он будет получать имя директории и возвращать содержимое директории с некоторой информацией о каждом элементе
в этой директории.
Мы уже знаем как получить информацию о файле или директории из inode, таким образом, мы можем просто вызвать fs.stat для каждого элемента в директории.
Этот скрипт получает путь к директории в командной строке (обязательный параметр), а затем перечисляет содержимое этой директории (без рекурсии).
var fs = require('fs');
if (process.argv.length <= 2) {
console.log("Usage: " + __filename + " path/to/directory");
process.exit(-1);
}
var path = process.argv[2];
fs.readdir(path, function(err, items) {
console.log(items);
for (var i=0; i<items.length; i++) {
console.log(items[i]);
}
});
Если вы читали статью о получении системной информации об одном файле, тогда вам уже известна первая часть нашего скрипта. А вот новая интересная часть:
fs.readdir(path, function(err, items) {
console.log(items);
for (var i=0; i<items.length; i++) {
console.log(items[i]);
}
});
Здесь мы используем метод readdir класса fs, который получает путь и функцию-коллбек в качестве параметров. Метод читает содержимое директории в память, а когда чтение завершено, то вызывает коллбек с двумя параметрами.
Если произошла какая-то ошибка, тогда первый параметр будет содержать информацию об этом. Если все прошло хорошо, тогда второй параметр будет содержать массив со всеми найденными в директории элементами (файлы, директории, символьный ссылки и т.д.).
С этого момента внутри нашей функции-коллбека мы можем просто напечатать весь массив, если мы просто хотим убедиться в успешном выполнении,
или пройти циклом по массиву с помощью оператора for
и сделать что-нибудь с каждым элементом. К примеру, мы можем напечатать каждый элемент.
Список будет содержать все кроме .
(указывает на текущую директорию) и ..
(представляет собой родительскую директорию).
Вот как это выглядит:
$ node examples/node/read_dir.js ~/work/code-maven.com/examples/
[ 'blocking-read-file.js',
'node_hello_world.js',
'node_hello_world_port.js',
'non-blocking-read-file.js',
'process_exit.js',
'raw_command_line_arguments.js',
'read_dir.js',
'stats.js' ]
blocking-read-file.js
node_hello_world.js
node_hello_world_port.js
non-blocking-read-file.js
process_exit.js
raw_command_line_arguments.js
read_dir.js
stats.js
Подробная информаци о каждом элементе
Теперь, когда мы знаем, как получить содержимое директории, и как получить информацию о файле, мы можем соединить эти две процедуры.
examples/node/list_dir_direct.js
var fs = require('fs');
if (process.argv.length <= 2) {
console.log("Usage: " + __filename + " path/to/directory");
process.exit(-1);
}
var path = process.argv[2];
fs.readdir(path, function(err, items) {
for (var i=0; i<items.length; i++) {
var file = path + '/' + items[i];
console.log("Start: " + file);
fs.stat(file, function(err, stats) {
console.log(file);
console.log(stats["size"]);
});
}
});
Код достаточно прост и понятен. И он также содержит ошибки, как мы увидим чуть позже.
Внутри коллбека для метода readdir
у нас есть цикл for
.
В этом цикле в каждой итерации мы выводим имя текущего файла (после добавления полного пути директории) - в основном для отладочных целей -
и вызываем fs.stat
. Этот метод в свою очередь тоже принимает коллбек. Там мы выводим имя файла - в этот раз, как часть результата,
и затем выводим размер файла. Мы могли бы вывести все данные о файле как мы это делали в
другой статье, но сейчас размера достаточно.
Вывод в консоль:
$ node examples/node/list_dir_direct.js ~/work/code-maven.com/examples/
Start: /home/gabor/work/code-maven.com/examples//blocking-read-file.js
Start: /home/gabor/work/code-maven.com/examples//node_hello_world.js
Start: /home/gabor/work/code-maven.com/examples//node_hello_world_port.js
Start: /home/gabor/work/code-maven.com/examples//non-blocking-read-file.js
Start: /home/gabor/work/code-maven.com/examples//process_exit.js
Start: /home/gabor/work/code-maven.com/examples//raw_command_line_arguments.js
Start: /home/gabor/work/code-maven.com/examples//read_dir.js
Start: /home/gabor/work/code-maven.com/examples//stats.js
/home/gabor/work/code-maven.com/examples//stats.js
97
/home/gabor/work/code-maven.com/examples//stats.js
243
/home/gabor/work/code-maven.com/examples//stats.js
270
/home/gabor/work/code-maven.com/examples//stats.js
151
/home/gabor/work/code-maven.com/examples//stats.js
18
/home/gabor/work/code-maven.com/examples//stats.js
324
/home/gabor/work/code-maven.com/examples//stats.js
27
/home/gabor/work/code-maven.com/examples//stats.js
1382
Отладочный вывод напечатал имена как и ожидалось, но внутри коллбека функции fs.stat()
мы снова печатаем одно и тоже имя файла.
Сравните результаты:
$ ls -l ~/work/code-maven.com/examples/
total 64
-rw-r--r-- 1 gabor staff 97 Jan 29 14:26 blocking-read-file.js
-rw-r--r-- 1 gabor staff 243 Jan 27 12:34 node_hello_world.js
-rw-r--r-- 1 gabor staff 270 Jan 27 12:34 node_hello_world_port.js
-rw-r--r-- 1 gabor staff 151 Jan 29 14:26 non-blocking-read-file.js
-rw-r--r-- 1 gabor staff 18 Jan 31 08:24 process_exit.js
-rw-r--r-- 1 gabor staff 27 Jan 29 14:54 raw_command_line_arguments.js
-rw-r--r-- 1 gabor staff 324 Jan 31 15:26 read_dir.js
-rw-r--r-- 1 gabor staff 1382 Jan 31 10:45 stats.js
Количество выведенных строк совпадает с количеством файлов (мы их печатали в том же порядке, как и вызывали fs.stat()
),
но по какой-то причине содержимое переменной file
было одно и то же для каждого коллбека.
Это случилось потому, что переменная file
это просто глобальная переменая (с точки зрения коллбека), и в первый раз, когда
коллбек был вызван, переменная file
содержала уже имя последнего файла в директории.
Таким образом, если мы хотим сочетать имя файла и результат вызова функции fs.stat()
, тогда мы должны опираться на порядок вызовов.
Но можем ли мы полагаться на него?
В этом конкретном случае вызова функции для каждого файла в директории это могло бы сработать как мы ожидаем - вызов функций по порядку.
Но в случае более сложных операций, особенно, если есть еще и внутренние коллбеки, мы не можем полагаться на то, что функции будут вызваны
в нужном нам порядке - в порядке их инициализации.
Следовательно, нам нужен способ передачи параметра file
во внутренний коллбек.
Генерация коллбеков
В этот раз, вместо добавления жестко заданного коллбека, мы будем вызывать функцию generate_callback()
, которая будет генерировать
для нас коллбеки.
Теперь каждый раз, когда мы вызываем fs.stat()
, до того как fs.stat()
будет реально выполнен,
JavaScript будет вызывать функцию generate_callback()
с текущим значением переменной file
.
Generate_callback
будет создавать новую функцию и затем возвращать ее нам.
Эта вновь созданная функция станет коллбеком для метода fs.stat()
.
examples/node/list_dir_generate.js
var fs = require('fs');
if (process.argv.length <= 2) {
console.log("Usage: " + __filename + " path/to/directory");
process.exit(-1);
}
var path = process.argv[2];
fs.readdir(path, function(err, items) {
for (var i=0; i<items.length; i++) {
var file = path + '/' + items[i];
console.log("Start: " + file);
fs.stat(file, generate_callback(file));
}
});
function generate_callback(file) {
return function(err, stats) {
console.log(file);
console.log(stats["size"]);
}
};
Результат:
$ node examples/node/list_dir_generate.js ~/work/code-maven.com/examples/
Start: /Users/gabor/work/code-maven.com/examples//blocking-read-file.js
Start: /Users/gabor/work/code-maven.com/examples//node_hello_world.js
Start: /Users/gabor/work/code-maven.com/examples//node_hello_world_port.js
Start: /Users/gabor/work/code-maven.com/examples//non-blocking-read-file.js
Start: /Users/gabor/work/code-maven.com/examples//process_exit.js
Start: /Users/gabor/work/code-maven.com/examples//raw_command_line_arguments.js
Start: /Users/gabor/work/code-maven.com/examples//read_dir.js
Start: /Users/gabor/work/code-maven.com/examples//stats.js
/Users/gabor/work/code-maven.com/examples//blocking-read-file.js
97
/Users/gabor/work/code-maven.com/examples//node_hello_world.js
243
/Users/gabor/work/code-maven.com/examples//node_hello_world_port.js
270
/Users/gabor/work/code-maven.com/examples//non-blocking-read-file.js
151
/Users/gabor/work/code-maven.com/examples//process_exit.js
18
/Users/gabor/work/code-maven.com/examples//raw_command_line_arguments.js
27
/Users/gabor/work/code-maven.com/examples//read_dir.js
324
/Users/gabor/work/code-maven.com/examples//stats.js
1382
Теперь переменная file
содержит имя файла, которое было у нее на момент инициализации функции, когда fs.stat()
приняла ее
в качестве аргумента.
Безимянные генераторы функций
В заключение давайте посмотрим решение без использования внешней функции generate_callback
.
Функция все еще здесь, но у нее просто нет имени. Вместо отдельного объявления мы ее включили в fs.stat()
.
Я не уверен, нравится ли мне это или вариант более длинный. Возможно, с функцией generate_callback
получается более читаемо.
examples/node/list_dir_noname.js
var fs = require('fs');
if (process.argv.length <= 2) {
console.log("Usage: " + __filename + " path/to/directory");
process.exit(-1);
}
var path = process.argv[2];
fs.readdir(path, function(err, items) {
for (var i=0; i<items.length; i++) {
var file = path + '/' + items[i];
console.log("Start: " + file);
fs.stat(file, function(f) {
return function(err, stats) {
console.log(f);
console.log(stats["size"]);
}
}(file));
}
});