Гость |
VBA: функции для работы с файлами
Содержание
|
Одной из важнейших составляющих любого языка программирования является способность считывать и записывать информацию из файлов. Стандартно это называется функциями низкого уровня (Low Level) и в полном объеме представлено в VBA. Кстати, именно эта особенность и является основой опасности макросов.
С другой стороны, в свете изучаемого курса, нельзя не отметить, что обсуждаемые возможности относятся далеко не к начальному уровню. Без них не удастся обойтись при автоматизации взаимодействия со сложной информацией из разнородных источников.
Доступ к этим материалам предоставляется только зарегистрированным пользователям!
P.S.! Завершение переработки материалов разработчика приводит к совершенно неутешительным выводам. Материала по этой теме всегда было очень мало, да и качество оставляло желать лучшего. Теперь же он стал «обструганным» под непонятный стандарт, разорванным в несвязанное словоизвержение и напичканным примерами, половина из которых только запутывает ситуацию, а вторая — ничего не иллюстрирует. Ну, или почти так.
Работа с файлами, дисками и папками, путь
Логика изложения основана на том, что можно сделать с существующими объектами, а уже потом — как создать новый. Поскольку многие команды распространяются не только на работу с каталогами, но и на файлы, полное изложение приводится именно здесь.
Все, перечисленное здесь, не имеет прямого отношения к низкоуровневым функциям, но без этой информации работать нельзя в принципе.
Первым является вопрос о том, где мы сейчас находимся. Совсем неочевиден не только ответ, но и то, что сам вопрос будет своевременно задан. Данный тезис становится все более актуальным с каждым годом и мне не хотелось бы, чтобы мои ученики бились об него лбом.
CurDir
Определение текущей папки.
Синтаксис
CurDir [(диск)]
Из синтаксиса можно понять (или нет) достаточно многое.
Доступ к этим материалам предоставляется только зарегистрированным пользователям!
Перечисленное не дает однозначного ответа на возникающие вопросы, но является основой для таковых. Частичные решения есть в разделе Примеры.
ChDrive
Инструкция для смены текущего диска (ChDrive) работает довольно примитивно. Так, вариант
ChDrive "D"
определит текущим диском диск «D:». Из примера видно, что приводится только литерал, без двоеточия.
ChDir
Изменяет текущий каталог или текущую папку
Синтаксис
Chdir <путь>
Внимание! Изменяется текущая папка на указываемом диске, а не сам диск. Для смены диска потребуется команда ChDrive.
В значении пути можно использовать обозначения относительного перемещения. Например,
Доступ к этим материалам предоставляется только зарегистрированным пользователям!
Dir
Функция возвращает строку (String) с именем обнаруженных на диске файла, папки или подпапки. Если объект файловой системы обнаружен не будет, возвратится пустая строка.
Синтаксис
Dir [(<путь>, [<атрибуты>])]
В имени можно использовать знаки подстановки маски файла (* и ?).
Передача в качестве аргумента пустой строки возвращает список всех файлов и папок.
Доступ к этим материалам предоставляется только зарегистрированным пользователям!
Атрибуты (копия из справки)
Константа | Значение | Описание |
vbNormal | 0 | Файлы без атрибутов (Значение по умолчанию.) |
vbReadOnly | 1 | В дополнение к файлам без атрибутов определяет файлы, доступные только для чтения |
vbHidden | 2 | В дополнение к файлам без атрибутов определяет скрытые файлы |
vbSystem | 4 | В дополнение к файлам без атрибутов определяет системные файлы |
vbVolume | 8 | Определяет метку тома, то есть имя логического диска (не букву!). Если указан какой-либо другой атрибут, параметр vbVolume игнорируется |
vbDirectory | 16 | В дополнение к файлам без атрибутов определяет каталоги (папки) |
Из контекста описания понятно, что атрибуты могут суммироваться.
Доступ к этим материалам предоставляется только зарегистрированным пользователям!
Name
Оператор Name переименовывает файл или папку и/или перемещает их в новое место.
Синтаксис
Name <oldpathname> As <newpathname>
При этом
- Обрабатываются только уже существующие объекты.
- Файл может быть перемещен на другой диск.
- Файлы, открытые низкоуровневыми процедурами, надо предварительно закрыть.
- Перемещение папок и подпапок возможно только в пределах логического диска.
MkDir
Создает новый каталог или папку.
Синтаксис
Mkdir <путь>
Если диск не указан, новые каталог или папка создаются на текущем диске. Имя присваивается с учетом регистра символов.
Доступ к этим материалам предоставляется только зарегистрированным пользователям!
RmDir
Удаляет существующую директорию или папку.
Синтаксис
RmDir <путь>
Невозможно удалить папку, содержащую файлы, так что следует вначале удалить их, используя команду Kill. Также этой командой нельзя удалить файл.
Kill
Удаляет файлы с диска.
Синтаксис
Kill <путь>
В качестве пути указывается строка, содержащая имя одного или нескольких файлов, предполагаемых к удалению. Строка может содержать любой фрагмент пути, начиная от логического диска, то есть относительный или абсолютный.
В имени можно использовать знаки подстановки маски файла (* и ?).
Так как команда удаляет только файлы, для удаления папок следует воспользоваться командой RmDir.
SetAttr
Устанавливает атрибуты файла.
Синтаксис
SetAttr <имя файла>, <атрибуты>
Оба параметра являются обязательными. Имя файла может содержать путь.
Нельзя изменять атрибуты у открытых файлов!
Атрибуты:
Константа | Значение | Описание |
vbNormal | 0 | Без атрибутов (по умолчанию) |
vbReadOnly | 1 | Только для чтения (Read-only) |
vbHidden | 2 | Скрытый файл (Hidden) |
vbSystem | 4 | Системный файл (System) |
vbArchive | 32 | Файл изменен после последнего резервного копирования |
Атрибуты при указании могут суммироваться.
Доступ к этим материалам предоставляется только зарегистрированным пользователям!
Низкоуровневые функции
Рассуждая здраво, практически невозможно сформировать однозначную последовательность изложения, так как тогда мы упремся в проблему яйца и курицы. Даже понимание того, что прежде чем считывать файл, он должен быть создан не очень помогает: чтение выполняется намного чаще.
Идентификатор файла
Основным моментом для работы с обсуждаемым функционалом является то, что каждому файлу, над которым производятся действия, обязан быть присвоен целочисленный идентификатор (FileNumber) типа Byte. Таким образом, может быть открыто до 511 файлов (1–511). По умолчанию используется лишь 255. Все обращения к файлам (чтение и запись) производятся только(!) через идентификатор, предваряемый знаком диеза, например, #1.
Его должен задать программист, но делать это вручную допустимо только для простых ситуаций. Лучше воспользоваться следующей функцией
FreeFile [(RangeNumber)]
где RangeNumber — 0 (1–255) или 2 (256–511).
Простейший вариант обращения (запроса) к функции выглядит так:
MyFileNum = FreeFile
Далее по тексту будет использоваться именно выделенная красным переменная, содержащая получаемый ID (для упрощения записи). Если не будет заявлено другое или использовано явное указание.
И еще раз, для более четкого понимания. В небольших проектах и программах будет более чем оправдано непосредственное указание идентификатора в командах: #1, #2...
Команда Open #
Открывает файл для операций ввода/вывода (input/output, I/O).
Синтаксис
Open <имя файла> For <mode> [Access <вид доступа>] [<lock>] As [#]FileNumber [Len = <RecLength>]
Здесь имя файла определяет имя, которое может содержать путь;
mode указывает режим открытия файла: Append (добавление), Binary (двоичный), Input (чтение), Output (запись) либо Random (случайный) (см. ниже);
Access определяет вид доступа: Read (чтение), Write (запись), Read Write (чтение и запись);
lock ключевое слово, определяющее доступность файла для других процессов: Shared, Lock Read, Lock Write или Lock Read Write;
[#]FileNumber заменяется на числовой идентификатор файла, используемый для последующей работы функций.
Len определяет размер записи (≤32767 (байт)), необходимый для внесения информации в файл с помощью Put #.
Если указываемый на запись файл отсутствует, то он будет создан. Естественно, попытка чтения несуществующего файла закончится ошибкой (см. Dir).
Описание команды упрощено!
Команда Close #
После завершения работы файл должен быть закрыт. Иначе какой-либо доступ к нему может быть заблокирован (в примитивном понимании и без дополнительного обсуждения). Для этого используется следующая команда:
Close [FileNumberList]
В качестве параметра передается номер файла, номера файлов через запятую или ничего. В последнем случае закроются все открытые файлы.
Примеры
Close ' Закрыть все открытые файлы Close #1 ' Закрыть файл с идентификатором #1 Close #1, #7 ' Закрыть два файла (с идентификаторами #1 и #7) Close #ProjID ' Закрыть файл с идентификатором, сохраненным в переменной памяти ProjID
См. также Reset.
Reset
Закрывает все файлы, открытые с помощью оператора Open, записывая содержимое их буферов на диск.
По существу, дублирует команду Close без атрибутов.
Чтение и запись
Как уже сказано выше, команда Open # позволяет открыть файл для использования в разных режимах, которые можно разделить на три группы.
- Последовательный (Sequential) доступ (режимы Input, Output и Append) используется для записи в текстовые файлы, например, журналы ошибок и отчеты.
- Прямой или произвольный доступ (Random) используется для считывания и записи данных в файле без его закрытия. Файлы с прямым доступом сохраняются данные в записях, что ускоряет нахождение требуемой информации.
- Двоичный доступ (режим Binary) используется для считывания или записи любого байта в файле, например, при сохранении или отображении растрового рисунка.
Сводка стандартных операторов, используемых для чтения и записи в разных режимах.
Тип доступа | Запись данных | Чтение данных |
Последовательный (Sequential) | Print #, Write # | Input # |
Произвольный (Random) | Put | Get |
Двоичный (Binary) | Put | Get |
Write #
Записывает данные в последовательный файл в структурированном виде.
Синтаксис
Write #FileNumber, [OutputList]
Необязательный параметр OutputList представляет одно или несколько числовых, или строковых выражений через запятую, которые нужно записать в файл. В переводе на обыденный язык, каждая строка подобного результирующего файла является перечнем значений с разделителем, каждое из которых записано по строгим правилам. Фактически мы получаем известный большинству CSV-файл. Чтобы не возникало неожиданностей при считывании, следует очень четко придерживаться правил.
Отсутствие OutputList (запятая в команде остается!) приводит к записи пустой строки.
Некоторые важные правила и особенности:
- В качестве разделителя в outputlist можно использовать пробел, точку с запятой или запятую. По очень многим соображениям настоятельно рекомендую использовать только точку с запятой.
- Числовые данные всегда записываются с помощью точки в качестве разделителя целой и дробной части.
- Вся информация записывается в особом виде, зависящем от типа данных и знание об этом создает довольно серьезную путаницу. Важно четко разделить информацию и понимать, что OutputList записывается в формате VBA. Никакие последующие «причуды форматирования» нас не касаются. Знать их нужно, чтобы находить и исправлять ошибки, либо для вывода информации, условно говоря, вручную. То есть без помощи, которую нам предоставляет функция Write #.
- Из предыдущего тезиса неизбежно вытекает, что некоторые данные проще выводить, предварительно записав их значение в переменную. Например, дату или код ошибки. Естественным образом это складывается для информации, получаемой посредством вычислений.
- Сохранение (запись) данных:
Строка — заключается в кавычки
Число — цифры без кавычек
Логические — #FALSE# и #TRUE#
Null — #FALSE# и #TRUE#
Дата — в универсальном формате даты
Ошибка — #ERROR ErrorCode#, где ErrorCode является соответствующим номером
- Нельзя использовать строки, уже содержащие в себе кавычки.
- В конец файла (после записи последнего символа) будет вставлен символ новой строки (Chr(13) + Chr(10)).
Примеры (все данные записываются в ранее открытый файл с идентификатором #1)
Write #1, 'Запись пустой строки Write #1, False; True 'Запись истинного и ложного значений Write #1, Tr(1); Tr(2); Tr(3) 'Запись значений трех последовательных элементов массива Write #1, Now; 128; True; "Текст"; MyVar 'Запись разнородных значений
Данные, записанные с помощью Write #, обычно считываются из файла с помощью оператора Input #. Именно во взаимодействии этой пары и заключен весь смысл.
В подобном файле крайне удобно хранить настройки для работы проекта, которые пользователь может изменить в соответствии со своими потребностями. Для передачи подобных макросов на сторону следует предусматривать (встраивать) возможности восстановления файлов, если пользователь их удалит или отредактирует вручную.
С другой стороны, набор множества таких строк представляет собой своего рода таблицу базы данных.
Print #
Записывает отформатированные данные в последовательный файл.
Синтаксис
Print #FileNumber, [OutputList]
OutputList — выражение или список выражений, которые необходимо вывести.
Его синтаксис
[{Spc(n) | Tab [(n)]}] [выражение] [позиция]
См. Spc #, Tab #, а также Width #. Выражение может быть числовым или строковым, «позиция» определяет столбец, где будет начало вывода.
С учетом специфического применения оператора, детали его действия опущены.
Spc
Функция используется с оператором Print # или методом Print для позиционирования выходных данных. Аналог Space().
Синтаксис
Spc(n)
Параметр n указывает, сколько пробелов будет возвращено. При переполнении строки, излишек переходит на следующую.
Если не умничать, то функция будет работать только с моноширинным шрифтом.
Пример
Print #1, "Последующий текст начнется через 10 пробелов"; Spc(10); "вот он!"
Tab #
Используется с оператором Print # или методом Print для позиционирования выходных данных.
Синтаксис
Tab[(n)]
Вставляет табулятор. Если параметр не указан, печать смещается к следующей позиции (шаг равен 14 символам). Наличие параметра перемещает к позиции с указанным номером. А если она уже была, то на эту позицию, но следующей строки.
Ширина (длина строки) определяется значением Width #.
Width #
Оператор Width # назначает ширину строки вывода для файла, открытого с помощью оператора Open #.
Синтаксис
Width #FileNumber, width
Если значение width равно 0, длина строки является неограниченной. Оно используется по умолчанию.
Input #
Читает строку со структурированными данными из открытого последовательного файла и сохраняет эти данные в переменных.
Синтаксис
Input #FileNumber, VarList
Обычно данная инструкция считывает файлы, созданные посредством Write #. Использовать ее следует только с файлами, открытыми в режиме Input или Binary.
Параметр VarList представляет собой перечисленный список переменных, в которые сохраняются считываемые данные.
Примеры
Input #2, Var1, Var2, Var3
Line Input #
Считывает одну строку из открытого последовательного файла и присваивает ее переменной типа String или Variant.
Синтаксис
Line Input #FileNumber, VarName
Оператор следует отнести к самому востребованному варианту считывания данных из файла. Считанная строка может быть подвергнута любой обработке.
Get #
Считывает данные из открытого файла на диске в переменную.
Синтаксис
Get [#]FileNumber, [RecNumber], VarName
Необязательный параметр RecNumber типа Variant (Long) указывает на номер записи (для файлов, открытых в режиме Random) или байтовое число (для файлов, открытых в режиме Binary), с которого начинается считывание. При его отсутствии, запятая сохраняется Результат сохраняется в переменную VarName.
Длинное и глупое описание работы функции можно свести к следующему.
- Читаем описание Put #.
- Если вы не знаете длину записи (Len), то не сможете правильно открыть файл и, как следствие, правильно прочитать его. Единственным адекватным выходом будет анализ исходного файла «глазами».
Данные, для считывания с помощью Get, обычно записываются в файл с помощью Put #.
Примеры
Get #4,,BufferVar
Put #
Записывает данные из переменной в файл на диске.
Синтаксис
Put [#]FileNumber, [RecNumber], VarName
Необязательный параметр RecNumber типа Variant (Long) указывает на номер записи (для файлов, открытых в режиме Random) или байтовое число (для файлов, открытых в режиме Binary), с которого начинается запись. Если его не использовать, то производится запись следующего номера, после последнего, определенного обращением к любому из операторов Get #, Put или Seek #.
Особенности для файлов, открытых в режиме Random:
- Параметр Len, задаваемый при открытии файла, играет ключевую роль. Во-первых, длина записи не может его превышать. Во-вторых, если запись меньше, то она дополняется пробелами до размера Len.
- Если записываемая переменная является строкой переменной длины, оператор Put записывает 2-байтовый дескриптор c длиной строки, а затем переменную. То есть длина записи, указанная предложением Len в операторе Open, должна быть по крайней мере на 2 больше, чем фактическая длина строки записываемых данных.
- Если записываемая переменная имеет числовой подтип Variant, оператор Put записывает дополнительно 2 байта, указывающие на тип. См. п.2.
- Если записываемая переменная имеет строковый подтип Variant, оператор Put записывает дополнительно 2 байта, указывающие на тип и еще 2 — на длину. Соответственно, значение Len должно быть увеличено на 4.
- Если записываемая переменная является динамическим массивом пользовательского типа, Put записывает дескриптор, длина которого равна 2 плюс в 8 раз больше числа измерений, то есть 2 + 8 * число измерений. То есть, для одномерного массива — 10 (2+8*1), для двумерного — 18 (2+8*2) и т.д. Плюс размер массива, описать который нормально сотрудники Microsoft не удосужились.
- Если записываемая переменная является массивом фиксированного размера, оператор Put записывает только данные, без дескриптора.
- При любом другом типе переменной все также, как и в п.6.
Особенности для файлов, открытых в режиме Binary (в дополнение к особенностям Random):
- Параметр Len, задаваемый при открытии, не действует. Значения переменных записываются последовательно, без заполнителя.
- Для любого другого массива, чем массив типа, определяемого пользователем, оператор Put записывает только данные, без дескриптора.
- Оператор Put записывает строки переменной длины, не являющиеся элементами типов, определяемых пользователем, с числом байтов равных числу знаков в строке.
Данные, записанные с помощью Put, обычно считываются из файла с помощью Get #.
Seek #
Задает положение следующей операции чтения/записи в файле, открытом с помощью оператора Open #.
Синтаксис
Seek [#]FileNumber, position
Параметр position указывает начальную точку чтения и должен располагаться в диапазоне 1—2 147 483 647.
Его работа тесно связана с использованием Get # и Put #.
По существу, оператор назван неудачно, так как ничего не ищет, а просто перемещается по команде.
Примеры
Содержание
Создание файла
Проверка существования файла
Запись информации в новый файл
Добавление информации в существующий файл
Построчное считывание файла
Назначение текущей папки
Здесь не стоит разговаривать о микроскопических программах, хотя из приводимых примеров можно извлекать блоки и использовать их в качестве заготовок.
Также не будет полноценных примеров для бездумного копирования.
Многие ситуации содержат схожие требования, но идеальное решение должно состоять из множества функций. Сейчас их делать не очень хочется, но это запланировано в перспективе.
В любом случае, все примеры полностью работоспособны.
Принципиально важен факт документирования функции с точки зрения возвращаемых значений. Равно как и описание передаваемых параметров.
Для большинства представляемых примеров используются следующие положения, если не указано иное.
- Путь должен содержать литерал диска (C:...).
- Путь не должен заканчиваться косой чертой: «C:\a\b».
- Если путь и файл передаются отдельно, то имя файла не должно содержать пути.
- Все параметры являются обязательными и не могут быть пустыми. Последнее обстоятельство не проверяется.
Создание файла
Данное действие специально вынесено в отдельный заголовок, так как будет базовым для примеров, когда файла ещё нет. Оно имеет множество особенностей, включая фатальные для хранящейся информации.
Доступ к этим материалам предоставляется только зарегистрированным пользователям!
Ещё один момент. Добавим программу (фрагмент), которая будет запускать процесс.
Доступ к этим материалам предоставляется только зарегистрированным пользователям!
Проверка существования файла
Имеет прикладное назначение не только в случае использования низкоуровневых функций. По существу, нужно в любом программном проекте в качестве условия его запуска.
Доступ к этим материалам предоставляется только зарегистрированным пользователям!
Запись информации в новый файл
Разумеется, что в этой ситуации файл нужно не открыть, а создать.
Доступ к этим материалам предоставляется только зарегистрированным пользователям!
....
Добавление информации в существующий файл
Казалось бы... Но первым действием должна быть проверка его наличия, если это не было сделано в программе ранее.
Доступ к этим материалам предоставляется только зарегистрированным пользователям!
....
Построчное считывание файла
Доступ к этим материалам предоставляется только зарегистрированным пользователям!
....
Назначение текущей папки
Если работа с проектами так или иначе связана с файлами, хранящимися в одной папке, самым простым вариантом будет назначение ее текущей.
Представленный пример имеет формат глобальной функции (доступной для любого проекта), производящей множество проверок.
Доступ к размещенным в этом месте материалам ограничен и предоставляется следующим категориям:
1. Студент I/II курса ВХК РАН. 2. Бывший студент ВХК РАН. 3. Подготовка к ОГЭ. 4. Подготовка к ЕГЭ. 5. VIP-пользователь. 6. Благотворитель.
В принципе, возможна как оптимизация программы, так и ее модернизация или приспособление для других нужд.