Прочитав пост Свой сапер на своих батниках и пробежавшись по коду, наткнулся на комментарий, что в батниках нет массивов (кстати, в последней версии Сапера, о котором идёт речь в том посте, нет этого комментария). Так вот, если в батнике необходимо использовать некоторый набор значений, то логично объявить такое кол-во переменных, которое требуется и работать с ними. Собственно данный сценарий эту задачу помогает решить проще. Вот несколько примеров:
Обращение с массивами происходит с помощью вызовов
Для создания массивов есть несколько методов. Основные это create, new и load.
Метод create создает массив заданного размера. Также можно задать начальные значения, которые по умолчанию равны нулю. И ещё, надо сказать, что значения не могут быть пустыми, это особенность команды set.
Но куда более удобно создавать массив сразу с заданием значений. Для этого служит команда new:
Можно загрузить значения из внешнего файла. Хочется заметить, что исходный файл должен быть в кодировке 1251 и текущая кодовая страница не влияет на это:
Доступ к каждому элементу возможен по индексу. Надо сказать пару слов о процедурах, возвращающих значение. Т.к. в батниках нет возможности вернуть значение из процедуры (будем так называть то, что вызывается через call), то приходится делать финт ушами: все процедуры, от которых ожидается результат, записывают его в переменную _l (эль маленькая, вроде как от слова last. Хотя потом я стал склоняться к тому, что result логичней). Имя переменной можно, как правило, переопределить, передав её имя последним параметром.
При отладке часто необходим быстрый вывод значений. В этом может помочь echo (алиас к get+echo) и dump (распечатка массива).
Распечатка выглядит так:
Вывод элементов списком с помощью list позволяет сохранить его в файл. К сожалению, у меня не получилось перенаправить его на другую команду. Например
Для получения количество элементов используется команда count (она по сути просто читает его из переменной
Команда
Пока я реализовал задание значения элементу по индексу, удаление элементов, копирование массивов, удаление массивов, добавление элементов.
И ещё надо сказать о наборах. Некоторым процедурам можно передавать в качестве параметров наборы. Эти наборы потом передаются команде for на обработку, поэтому они должны быть написаны так, как этот for хочет. А он не требователен: элементы набора разделяются запятой (,) или пробелом ( ), а строковые надо заключать в кавычки. Но т.к. сами параметры тоже заключаются в двойные кавычки, то в наборах должны быть одинарные ('). Смотрите пример add.
Это на мой взгляд самые интересные операции. Их можно было отнести к манипуляциям, но я решил выделить их отдельно.
Сохранение в файл можно реализовать, как уже говорилось выше, воспользовавшись перенаправлением вывода list. Но если нужно сохранить в файл в кодировке cp1251, то лучше воспользоваться командой save:
Выполняется командой sort. Можно было реализовать сортировку обычным пузырьком, но я решил всё же воспользоваться помощью команды sort. Но в этом подходе есть ряд минусов: создаётся два файла во временном каталоге (скинутый массив и отсортированный, которые затем, правда, всё же удаляются) и числа сортируются как строки. Плюсов правда тоже два: так, как мне кажется, всё же быстрее, хоть я и не гнался за скоростью (и это видно по операции del с набором индексов), и простота, и это меня и склонило окончательно к выбору этого метода.
Используется команда each и здесь есть два подхода. Первый это передача непосредственно команды. В этом случае можно использовать подстановки _i_ для индекса текущего элемента и _val_ для его значения. Чтобы скрипт понял, что вы передаете команду, укажите последним параметром x (икс)
Второй подход это указание метки, которая будет вызываться для каждого элемента:
Для реализации некоторых операции используются некоторые отдельные процедуры (вызываются не через :array, вот что имеется ввиду). Они могут тоже показаться интересными:
Можно посмотреть код на Google Docs или pastebin.com.
Спасибо, вот теперь могу заниматься чем-нибудь действительно полезным.
echo Создание массива А: call :array new A "'one','two','three','foo','bar'" echo Получение третьего элемента в result: call :array get A 3 result echo %result% echo Изменение нулевого элемента call :array set A 0 "первый элемент"
Принцип
Обращение с массивами происходит с помощью вызовов
call :array
и передачи соответствующих параметров. Первый параметр это действие, а второй имя массива. Ещё за этими двумя могут следовать дополнительно как обязательные, так необязательные параметры. Каждый массив представляет собой набор переменных вида array_[имя массива]_[индекс элемента]
и одной переменной, содержащей размер — array_[имя массива]_count
. Предполагается, что эти переменные вручную не изменяются.Создание массивов
Для создания массивов есть несколько методов. Основные это create, new и load.
Метод create создает массив заданного размера. Также можно задать начальные значения, которые по умолчанию равны нулю. И ещё, надо сказать, что значения не могут быть пустыми, это особенность команды set.
rem call :array call :array create имя_массива [размер=0] [начальные значения=0]
rem Создание пустого массива
call :array create myArray
rem Создание массива, размером 5. При этом все элименты равны нулю - 0
call :array create myArray 5
rem Создание массива, размером 5 и все элименты равны строке "my value"
rem Значения будут без кавычек
call :array create myArray 5 "my value"
Но куда более удобно создавать массив сразу с заданием значений. Для этого служит команда new:
rem call :array new имя_массива "знач1, знач2, 'строка' ..."
rem Создание массива A со значениями 90, 56, 'два слова'
call :array new A "90 56 'два слова'"
Можно загрузить значения из внешнего файла. Хочется заметить, что исходный файл должен быть в кодировке 1251 и текущая кодовая страница не влияет на это:
rem call :array load имя_массива имя_файла
rem Чтение значений из файла records.txt в Table. Каждая строка - одно значение. Пустые строки пропускаются
call :array load Table records.txt
Доступ к элементам
Доступ к каждому элементу возможен по индексу. Надо сказать пару слов о процедурах, возвращающих значение. Т.к. в батниках нет возможности вернуть значение из процедуры (будем так называть то, что вызывается через call), то приходится делать финт ушами: все процедуры, от которых ожидается результат, записывают его в переменную _l (эль маленькая, вроде как от слова last. Хотя потом я стал склоняться к тому, что result логичней). Имя переменной можно, как правило, переопределить, передав её имя последним параметром.
rem call :array get имя_массива [index=0] [имя_целевой_переменной=_l]
rem Получение первого элемента массива myArray. Результат будет содержаться в _l
call :array get myArray
echo Первый элемент - %_l%
rem Получение второго с конца элемента массива myArray в переменную ok
call :array get myArray -2 ok
echo Вот результат: %ok%
При отладке часто необходим быстрый вывод значений. В этом может помочь echo (алиас к get+echo) и dump (распечатка массива).
rem Вывод последнего элемента myArray
call :array echo myArray -1
rem Распечатка всего массива
call :array dump myArray
Распечатка выглядит так:
array[4]: myArray
[0] первый элемент
[1] второй
[2] 3й
[3] последний
Вывод элементов списком с помощью list позволяет сохранить его в файл. К сожалению, у меня не получилось перенаправить его на другую команду. Например
call :array list myArray | sort
приводит к ошибке «Недопустимая попытка перехода на метку пакетного файла извне этого файла.»call :array list myArray > output.txt
Для получения количество элементов используется команда count (она по сути просто читает его из переменной
array_[имя массива]_count
, но с дополнительными проверками на существование и проч.:call :array count myArray len
echo Массив содержит %len% элементов.
Команда
find
возвращает индекс первого найденного элемента или -1 если совпадения отсутствуют. Здесь появляются так называемые флаги. Это параметры, которые могут быть, а могут и не быть. Они обычно идут последними, но иногда после них можно указать имя переменной для результата, и может возникнуть вопрос как быть тогда. В этом случае, если флаг не нужен, а задать переменную нужно, на позиции влага просто указывайте любое другое значение, например решётку #, или минус -. Ну, в примерах покажу.rem call :array find имя_массива искомый_элемент N [имя_целевой_переменной=_l]
rem Здесь N - просто замена флагу I. Если не указываете имя целевой переменной, то и N можно не ставить.
rem sebastNum будет содержать номер элемента, который равен Себастьяну с учетом регистра.
call :array find myArr "Себастьян Перейро" N sebastNum
rem и без учёта:
call :array find myArr "Себастьян Перейро" I sebastNum
rem и с учётом в переменную по умолчанию _l:
call :array find myArr "Себастьян Перейро"
Манипуляции с массивами
Пока я реализовал задание значения элементу по индексу, удаление элементов, копирование массивов, удаление массивов, добавление элементов.
И ещё надо сказать о наборах. Некоторым процедурам можно передавать в качестве параметров наборы. Эти наборы потом передаются команде for на обработку, поэтому они должны быть написаны так, как этот for хочет. А он не требователен: элементы набора разделяются запятой (,) или пробелом ( ), а строковые надо заключать в кавычки. Но т.к. сами параметры тоже заключаются в двойные кавычки, то в наборах должны быть одинарные ('). Смотрите пример add.
rem Задание значения
rem call :array set имя_массива индекс значение
call :array set myArray 5 'пятый элемент'
rem Копирование массива
rem call :array copy имя_копируемого имя_конечного
call :array copy myArray finalArray
rem Добавление элемента в конец массива
rem call :array add имя_массива значение
rem call :array add myArray 2012
rem Добавление элементов в конец массива. Здесь указывается набор, и чтобы значть, что это набор, нужно указать параметр ! - восклицательный знак
rem call :add имя_массива набор !
call :array add myArray "'строка один' 120 454.34 'sd'" !
rem Можно передать набор через expand и не указывать флаг "!"
call :array expand myArray "'строка один' 120 454.34 'sd'"
rem Удаление элементов из массива
rem call :array del имя_массива [индекс_элемента|набор элементов=удаление всего массива]
rem Если третьим параметром (первый это del) указать число, то будет удалёт эл-т с таким индексом
rem удаляет из массива myArr 9й элемент
call :array del myArr 9
rem Если передать набор, то будут удалены элементы с индексами из набора
rem удаляет из массива myArr элементы с индексами 4, 6 и 8
call :array del myArr "4,6,8"
rem А если не указывать третий парамет, то будет удален массив полностью
call :array del myArr
Дополнительные возможности
Это на мой взгляд самые интересные операции. Их можно было отнести к манипуляциям, но я решил выделить их отдельно.
Сохранение в файл в кодировке cp1251
Сохранение в файл можно реализовать, как уже говорилось выше, воспользовавшись перенаправлением вывода list. Но если нужно сохранить в файл в кодировке cp1251, то лучше воспользоваться командой save:
rem call :array save имя_массива имя_файла
call :array save results Результаты.txt
Сортировка массива
Выполняется командой sort. Можно было реализовать сортировку обычным пузырьком, но я решил всё же воспользоваться помощью команды sort. Но в этом подходе есть ряд минусов: создаётся два файла во временном каталоге (скинутый массив и отсортированный, которые затем, правда, всё же удаляются) и числа сортируются как строки. Плюсов правда тоже два: так, как мне кажется, всё же быстрее, хоть я и не гнался за скоростью (и это видно по операции del с набором индексов), и простота, и это меня и склонило окончательно к выбору этого метода.
rem call :array sort имя_массива [R - для сортировки в обратном порядке]
rem массив myArray будет отсортирован в обратном порядке
call :array sort myArray R
Применение процедуры к каждому элементу
Используется команда each и здесь есть два подхода. Первый это передача непосредственно команды. В этом случае можно использовать подстановки _i_ для индекса текущего элемента и _val_ для его значения. Чтобы скрипт понял, что вы передаете команду, укажите последним параметром x (икс)
call :array new B "1 4 1 6 7 2"
set sum=0
call :array each B "set /a sum+=_val_" x
rem sum будет содержать сумму всех элементов массива (двадцать одно)
Второй подход это указание метки, которая будет вызываться для каждого элемента:
call :имя_указанной_метки значение индекс
. Если первый способ отличается простотой, то этот предоставляет более гибкие возможности. Например:rem Задаём массив
call :array new someArray "1902 2007 2012 1954 1945 1989"
rem Задаём начальные данные
set pos=0
call :array get someArray %pos% val
rem Вызываем для каждого элемента процедуру findMax
call :array each someArray findMax
rem Выводим результат
echo Максимальный год %val%. Позиция %pos%
goto :eof
:findMax
rem Здесь %~1 - значение (без кавычек, за счёт ~)~, %2 - индекс.
if %~1 GTR %val% (
set val=%~1
set pos=%2
)
goto :eof
Вспомогательные процедуры
Для реализации некоторых операции используются некоторые отдельные процедуры (вызываются не через :array, вот что имеется ввиду). Они могут тоже показаться интересными:
:set имя_конечной_переменной имя_исходной
— задает конечной переменной значение исходной. Особенность в то, что при вызове этой процедуры, можно имена переменных составлять из кусочков (как позже заметил, можно вызвать напрямую set:call set result value
:is_number значение [имя_целевой_переменной=_l]
— проверяет, является ли значение числом (целым, но можно расширить и сделать проверку чисел с точкой, но мне не нужно было). Работает в лоб: вырезает все цифры и сравнивает с пустой сторкой.:str_replace имя_исходной_переменной искомая_строка подстановка [имя_целевой_переменной=имя_исходной_переменной]
— замена подстроки в строке
Скачать
Можно посмотреть код на Google Docs или pastebin.com.
Спасибо, вот теперь могу заниматься чем-нибудь действительно полезным.