Содержимое директории на Node.js
По аналогии с командой dir в MS Windows (а точнее в DOS) или с командой ls в Unix/Linux, мы напишем скрипт на Node.js, реализующий такое же поведение. Он будет получать имя директории и возвращать содержимое директории с некоторой информацией о каждом элементе в этой директории.
Мы уже знаем как получить информацию о файле или директории из inode, таким образом, мы можем просто вызвать fs.stat для каждого элемента в директории.
Этот скрипт получает путь к директории в командной строке (обязательный параметр), а затем перечисляет содержимое этой директории (без рекурсии).
examples/node/read_dir.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) { 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)); } });
Published on 2015-03-28