Журнал LinuxFormat - перейти на главную

LXF89:Mono

Материал из Linuxformat
Версия от 00:43, 16 марта 2008; Interlace (обсуждение | вклад)

(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск

Содержание

Mono: Работаем с файлами

Подогрев ваш интерес пространствами имен и объектно-ориентированным программированием, Пол Хадсон покажет, как пишется полезный код...


Вы когда-нибудь смотрели мультсериал Thundercats [Громо-Коты, – прим. пер.]? В детстве я считал его гениальным: вы действительно могли ощущать «колдовство и рык звериный», глядя в телевизор, благодаря крутой анимации, симпатичным персонажам и замечательным сценариям. Сейчас, однако, я понимаю, что он был не лишен шаблонности. Среди ГромоКотов были такие персонажи, как Лева-O, Тигра и Пантро, и они боролись против обезьяноподобного индивида по имени Обезмен, шакаловидного Шаклмена и коршунообразного Коршмена. Их родная страна называлась Громада. Машина Пантро называлась Громокар. Уловили закономерность?

Такая предсказуемость может показаться чересчур лобовой, зато дети легко улавливают, что происходит, и легко это запоминают, чтобы пересказать сюжет друзьям. Теперь, повзрослев, я уяснил две вещи. Во-первых, я не стану космонавтом. Я бы и пошел, но вряд ли туда возьмут «очкарика». Во вторых, лучший способ что-то выучить – сделать это запоминающимся.

К примеру, я использую PHP уже много лет и никак не могу запомнить, принимает ли функция strpos() (поиск вхождения одной строки в другую) параметры как $иголка, $стог_сена или как $стог_сена, $иголка.

Проблема PHP в том, что функция strpos() принимает параметры как $стог_сена, $иголка, в то время как функция in_array() (она отвечает на вопрос, встречается ли элемент в массиве) принимает параметры как $иголка, $стог_сена. Вдобавок strpos() пишется в одно слово, а str_replace() содержит разделяющий две части знак подчеркивания. По своей природе, PHP не очень запоминающийся язык программирования. Программистам PHP не быть ГромоКотами.

К счастью, вы не учитесь программировать на PHP, а работаете на платформе Mono с С#, а C# – язык более новый и менее набитый ляпсусами, чем PHP – не имеет таких проблем. Собственно говоря, некоторые части C# так прямолинейны, что вы можете вставлять имена методов по догадке. На нашем втором уроке мы рассмотрим управление файлами. Вы напишете программу, которая пройдется по файловой системе и составит указатель по всем имеющимся файлам, а вы потом сможете его распечатать. Не волнуйтесь, если это звучит сложно – C#, Mono и .NET сделают всю трудную работу за вас.

Ощутите колдовство

Начнем с простейшего чтения и записи файлов. Вообще-то точнее будет сказать – простейшего ввода-вывода: в терминах программирования, «писать» означает «вывести» или «отобразить» (то, что делает команда write). Запустите MonoDevelop (который мы установили в прошлый раз) и создайте консольный проект C# (File > New Project > C# > Console Project). Это не временный проект, поэтому дайте ему такое имя, чтобы не стыдно было выложить его на SourceForge. Я выбрал имя Snarf, потому что оно означает «брать» (эта программа будет читать содержимое кучи файлов), а также хорошо сочетается с темой ГромоКотов.

(thumbnail)
Велите MonoDevelop компилировать программы в каталог вашего проекта, чтобы программа могла читать кэш файлов.

Нам понадобится кое-что из новой функциональности .NET 2.0, но в большинстве версий MonoDevelop по умолчанию используется .NET 1.1. Чтобы поправить дело, нажмите Project > Options, затем выберите Runtime Options из списка Categories в появившемся окне. Справа помещен выбор среды исполнения, варианты: 1.1 и 2.0, вот и выберите 2.0. Пока вы еще в этом окне, откройте категорию Configurations > Debug, выберите Output, затем измените Output Path, удалив раздел /bin/Debug. Теперь MonoDevelop будет сохранять исполняемый файл в корневом каталоге вашего проекта. Нажмите OK, чтобы запомнить изменения.

И чтение, и запись файлов осуществляются с помощью библиотеки System.IO (сокращение от Input/Output – ввод/вывод, т.е. чтение и запись), поэтому нужно поместить ‘using System.IO’ вверху вашего главного файла проекта. Измените class MainClass на class Snarf и удалите строку Console.WriteLine(), оставив метод Main() пустым.

Первое, что мы сделаем – считаем содержимое одного файла. Содержимое файлов – по крайней мере, тех, что нас интересуют – простой текст, а значит, его можно легко сохранить как данные строкового типа. Создайте файл с именем myfile.txt в корневом каталоге вашего проекта (там, где MonoDevelop будет сохранять вашу программу), и введите любой текст.

Встает вопрос: как прочесть содержимое файла в строку? А попробуйте так:

string myfile = File.ReadAllText(“myfile.txt);

Вот и все – все, что требуется, чтобы прочитать файл в строку средствами Mono. Для ее вывода можете использовать метод Console.Write(), рассмотренный на прошлом уроке:

Console.Write(myfile);

Нажмите F5, программа скомпилируется и запустится, и вы должны увидеть содержимое вашего файла в панели Application Output [Вывод приложения] внизу MonoDevelop.

Алло, оператор?

Пойдем дальше: заставим нашу программу записывать изменения обратно в файл. Добавьте следующие строчки после вызова Console.Write():

myfile += “\nМои крылья как щит... ой, это из другого мультика.”;
File.WriteAllText(“myfile.txt”, myfile);
(thumbnail)
Левая панель MonoDevelop показывает файл решения (по умолчанию) или онлайн-справку. Учтите: некоторые страницы помощи устарели!

В этом коде += является оператором: это такой символ, который выполняет операцию (лихо закручено, а?). Из школьной арифметики вы знаете операторы +, *, -, /, они выполняются над двумя числами, по-научному – операндами. А оператор = берет значение одного операнда и присваивает его другому. Так, выражение a=10 означает, что переменная а принимает значение 10.

Сейчас ваш труд окупится. Если а равно 10, то как прибавить к нему еще 10? Ага-ага…

a = a + 10;

Работает! Согласно принципу «бритвы Оккама» («при прочих равных условиях, лучшим объяснением будет простейшее»), этот код вообще является наилучшим. Принцип, однако, имеет малоизвестное добавление, под названием «поправка Оккама»: в простейшем решении непременно кроются недостатки. В нашем случае, недостаток тот, что для набора строки требуется 11 нажатий клавиш, а C# позволяет сделать то же самое за 8:

a += 10;

Здесь применен оператор +=, дитя любви операторов + (сложения) и = (присваивания): он прибавляет то, что справа от него, к тому, что слева. Класс! Теперь, с новообретенными знаниями, вы поймете, что код нашей программы добавляет строку (Мои крылья как щит... – ну, вы знаете, откуда это) к уже существующей строке. Языку C# хватает ума различить, когда += используется над числами (для сложения двух чисел), а когда – над строками (для конкатенации двух строк). Мы начинаем новую строку с \n, это информирует Mono о необходимости добавить символ новой строки в существующем файле.

Метод WriteAllText() относится к методу ReadAllText() как Wilykit к Wilykat: дайте ему имя файла в качестве первого параметра и текст для записи в качестве второго, а он выполнит всю работу.

Пора вдарить по газам Громокара и ввести конструкции посерьезнее: рассмотрим условные выражения и циклы. Условные выражения нужны, чтобы выполнять действия только при выполнении (истинности) определенного нами условия. Если условное выражение ложно – небо не голубое, возраст пользователя не 26, или что мы там проверяем – то код исполняться не будет. Например:

string Name = “Cheetara”;
if (Name == “Snarf”) {
    Console.WriteLine(“Snarf snarf!);
}


Данный код ничего не напечатает: хотя переменная Name существует, ее содержимое не Snarf, а Cheetara. Заметим, что == просто еще один оператор, означающий «равняется». Он отличается от оператора присваивания = (см. врезку «Когда = ведет к ошибке»).

Знакомство с циклом

Циклы позволяют выполнять определенный блок кода несколько раз. Например:

Console.Write(“Громо... “);
Console.Write(“Громо... “);
Console.Write(“Громо... “);
Console.Write(“Громокот! Хо!\n“);

Строка «Громо» не раз повторяется, поэтому для ее многократного вывода на экран мы можем запихать ее вовнутрь так называемого «цикла for»:

for (int i = 1; i <= 3; ++i) {
    Console.WriteLine(“Громо... “);
}
Console.WriteLine(“Громокот! Хо!\n”);

Согласен, в этом примере оба варианта кода имеют те же четыре строчки; ну, а если пришлось бы выполнить операцию 100 раз? Или 100000? Заметим, что ++ это сокращение C# для выражения +=1 – оно просто прибавляет единицу к выражению. C# предусматривает несколько разных циклов, и for – один из них.

Идем дальше

Сейчас мы расширим нашу программу таким образом, что она будет считывать все файлы в каталоге, и если у файла расширение txt – распечатывать его содержимое. Тут нужны и цикл, и условное выражение – о меч Завета, помоги мне постичь непостижимое!

string[] files = Directory.GetFiles(/home/paul”);
foreach(string file in files) {
    if (file.EndsWith(“.txt)) {
        Console.Write(File.ReadAllText(file));
    }
}
(thumbnail)
Так будет выглядеть наш кэш файлов: в каждой строке по одному имени txt-файла.

Здесь показано аж 5 нововведений, поэтому позвольте мне разбить все по этапам:

  • Если мы передадим Directory.GetFiles() каталог в качестве единственного параметра, то он вернет нам массив строк (string[], помните?), содержащий все имена файлов этого каталога.
  • Элементы почти всех массивов можно перебрать с помощью цикла foreach: он извлекает каждый элемент массива, а мы присваиваем значение элемента переменной. В нашем примере, мы заставляем Mono присваивать каждое имя файла строке ‘file’.
  • У каждой строки есть метод EndsWith(), который возвращает true, если строка заканчивается подстрокой, которую мы передали ей в качестве параметра. Если метод возвращает true, то мы выполняем код внутри фигурных скобок (Console.Write(...)).
  • Вместо того, чтобы присвоить возвращаемое значение File.ReadAllText() другой строке, мы сразу же передаем его в метод Console.Write. Так делать можно, и это помогает сделать код чуть короче.
  • Отметим, что C# различает File (особый класс, позволяющий читать и записывать файлы) и file, строковую переменную, которую мы создали. Все переменные в C# чувствительны к регистру.


Замените содержимое метода Main() новым кодом, подставьте вместо /home/paul ваш собственный каталог с текстовыми .txt-файлами. Нажмите F5: вы увидите, что все работает, но это скорее код Громомальчика, чем Громомужа.

Вы когда-либо слышали фразу «быстро, качественно, дешево – выбери любые два»? Что ж, раз Linux связан с Open Source, «дешевизна» присутствует по определению. Но вот чудо: Mono позволяет еще и сделать «быстро» и «качественно»! Немного поколдуем, чтоб сделать наш код более быстрым и более функциональным.

Колдовство сидит в методе Directory.GetFiles(). Сейчас мы передаем ему один параметр, то есть каталог, где хотим искать файлы. Но в C# методы могут выполнять разные действия в зависимости от числа передаваемых параметров. Мы можем ускорить работу нашего кода, задав второй параметр метода GetFiles(), который позволит нам определить фильтр поиска для наших файлов. А именно, вместо того, чтобы использовать file.EndsWith(“.txt”), используем второй параметр метода Directory.GetFiles, то есть *.txt. Тогда массив строк files будет содержать только файлы, заканчивающиеся на .txt. Можно заставить наш код делать даже больше, определив еще один параметр метода Directory.GetFiles, а именно SearchOption.AllDirectories. Параметр заставляет Mono искать не только в указанном каталоге, но и во всех его подкаталогах.

Поэтому новый супер-пупер метод Main() будет выглядеть следующим образом:

string[] files = Directory.GetFiles(/home/paul”, “*.txt”, SearchOption.AllDirectories);
foreach (string file in files) {
    Console.Write(File.ReadAllText(file));
}

Эффектный финал

Места в статье уже не хватает – поэтому пора просить древних духов C# превратить этот загнивший код во что-то действительно работающее! Мы уже видели, как можно получить все имена файлов в данном каталоге (и его подкаталогах), и знаем, как читать и записывать файлы. Теперь нам необходимо, чтобы программа выполняла две вещи:

  • Если параметры не указаны, то просканировать файловую систему и сохранить список в файле. То есть кэше файлов.
  • Если параметр указан, то использовать его для поиска подходящих файлов, а затем вывести их содержимое на печать.

Наша программа будет делать нечто очень похожее на работу Linux-команды updatedb, используемую для генерации кэша поиска для locate. Но updatedb не выводит содержимого найденных файлов, значит, наша программа хоть чуть-чуть, да получше!

Вы уже знаете большую часть кода, которая необходима для выполнения этой работы, поэтому давайте сосредотчимся и начнем кодировать. Замените ваш метод Main() следующим кодом:

if (args.Length == 0) {
    string[] files = Directory.GetFiles(/home/paul”, “*.txt”, SearchOption.AllDirectories);
    File.WriteAllLines(“filecache.snarf”, files);
} else {
    string[] cache = File.ReadAllLines(“filecache.snarf);
    foreach(string file in cache) {
        if (file.Contains(args[0])) {
            Console.Write(File.ReadAllText(file));
        }
    }
}

Этот код содержит нечто совершенно новое: args.Length. В прошлый раз мы видели, что C# передает аргументы в метод Main() через массив строк ‘args’ (помните, что массив – это группа объектов одного типа). args.Length используется, чтобы узнать размер массива (т.е. сколько параметров было передано). Если он равен 0, то есть параметры не передавались, то нам необходимо создать кэш имен файлов.

Метод WriteAllLines() очень похож на метод WriteAllText(), за исключением того, что он принимает в качестве второго параметра не одну строку, а массив строк. Нам это очень полезно, так как Directory.GetFiles() возвращает массив строк, поэтому мы можем просто передать его в WriteAllLines(), нам не понадобится осуществлять построчную запись.

И опять мы встречаем нечто новое: else. Вы уже знакомы с if, условным оператором, выполняющим код при истинности условия. А что если условие ложно? Здесь приходит на помощь else. Например, следующий код выведет «Ты Громокот!»:

string Home = “Третья планета”;
if (Home == “Средиземье”) {
    Console.WriteLine(“Ты хоббит!);
} else {
    Console.WriteLine(“Ты Громокот!);
}

В нашей программе Snarf выражение else означает «если число передаваемых параметров не 0», то есть параметры были переданы. В этом случае вызывается метод ReadAllLines(), который считывает каждую строку текстового файла в строковый массив.

Наконец, мы переходим к главному блоку кода: мы просматриваем каждую строку в кэше файлов и проверяем ее на соответствие передаваемому параметру. Соответствие осуществляется с помощью специального метода Contains(), выполняемого над строками: передаем в качестве параметра подстроку, а метод возвращает true, если она содержится в строке. Переменная args[0], как вы узнали из прошлого раза, содержит первый параметр, переданный в командной строке. Если имя файла соответствует параметру, то мы считываем текст файла и выводим его на экран. Между прочим, кроме EndsWith() и Contains(), к строкам применимы методы Replace() (заменить одну подстроку другой), ToUpper() (преобразовать строку в верхний регистр) и Trim() (чтобы удалить пробелы, символы табуляции и символы новой строки, расположенные в начале и в конце строки).

Вот и все: наш проект закончен. Snarf роется в файловой системе, выводит на экран все файлы, соответствующие запросу, а Третья планета в очередной раз спасена от Mымм-Ра – и все благодаря Mono! На диске к журналу вы найдете полный код Snarf, а также дополнительные материалы. Обратите внимание на сообщения, которые выводятся при создании кэша файлов; numfilesfound – это переменная, которая увеличивается каждый раз при нахождении подходящего файла, чтоб мы могли вывести сообщение, если ни одного файла не обнаружится; а также, в случае успеха будет напечатаны имена всех файлов.

Персональные инструменты
купить
подписаться
Яндекс.Метрика