Pull to refresh

Работа с массивами в bat

Reading time 6 min
Views 54K
Прочитав пост Свой сапер на своих батниках и пробежавшись по коду, наткнулся на комментарий, что в батниках нет массивов (кстати, в последней версии Сапера, о котором идёт речь в том посте, нет этого комментария). Так вот, если в батнике необходимо использовать некоторый набор значений, то логично объявить такое кол-во переменных, которое требуется и работать с ними. Собственно данный сценарий эту задачу помогает решить проще. Вот несколько примеров:

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.

Спасибо, вот теперь могу заниматься чем-нибудь действительно полезным.

Tags:
Hubs:
+31
Comments 32
Comments Comments 32

Articles