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

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

    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.

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

    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 32

      +3
      C ума сойти %)

      Открыл для себя новый язык программирования :)
        –1
        гениально
          +4
          А я то думал что хорошо знаю консоль Windows :)
            +3
            Спасибо за статью. Проделана отличная и емкая работа.
              +1
              Черт, написать свою статью проще чем разбираться с чужой. Автору — плюс, а я читаю статью
                0
                Кстати, перенесите в блог Ненормальное программирование
                  +6
                  Теперь не удивлюсь если завтра кто-нибудь напишет о Создании классов в bat файлах. :)
                    0
                    Ладно, я нормальный тетрис постараюсь на wsh сделать
                      +3
                      завтра будет изложено применение ООП в батниках…
                        0
                        О! Я же говорил, не удивлюсь:-D Публикуйте, с удовольствием прочту.
                          –1
                          я удивлюсь. вот фанаты то! Вопрос «зачем» я полагаю не уместным, верно ведь? или тут нечто большее чем любовь к искусству?
                            0
                            Ну например нужно прочитать конфиг вида

                            [Section1]
                            key1=value1

                            [Section2]
                            key2=value2

                            который будет использовать наш батник для себя. Без массивов как-то не то, согласитесь?)
                            +2
                            кстати, гугль подсказал: dirk.rave.org/chap9.txt
                          0
                          круто! Слов нету! Т.е. как я понимаю сервером и троянцем на .bat уже никого не удивишь =((
                            0
                            bat штука мощная) Особенно ей резервирование данных с помощью xcopy удобно делать, и прикрутить допсофтинку для работы по расписанию)…
                              0
                              А зачем допсофтинка? at к Вашим услугам. Проверяем в самом батнике, есть ли этот батник в расписании at, если нет — добавляем.
                                0
                                как это сделать — подскажите плз)
                                  0
                                  В батнике анализируем вывод команды at:
                                  1)
                                  The service has not been started.
                                  Нам говорят, что служба выключена. Включаем командой
                                  sc start schedule

                                  2)
                                  There are no entries in the list.
                                  OR
                                  Status ID Day Time Command Line
                                  — 1 Today 14:35 PM notepad
                                  То есть, если список пуст или же в нем нет нашего батника, то добавляем задачу при помощи команды, например,
                                  at 00:00 my.bat
                                  (выполняется каждый раз, поэтому каждый день будет добавлять задание при условии, что комп в это время не выключен; можно попробовать другие опции, занести, например, на каждый день недели, тогда будет еще лучше).
                                  3) Если задача уже есть в списке, то все ОК, едем дальше.

                                  Вот как-то так.
                                    0
                                    Во втором, точнее так:
                                    Status ID   Day                     Time          Command Line
                                    -------------------------------------------------------------------------------
                                            1   Today                   14:35 PM      notepad
                                      0
                                      спс. отправляюсь на эксперименты
                                –2
                                Попробуйте кроссплатформенный TCL или newLISP, предназначенные (кроме прочего) для написания скриптов. Зачем извращаться, пробуя что-то сделать на .bat? TCL весит 10МБ, newLISP — 1.5МБ.
                                  +4
                                  А по-моему вы ни разу не гик и не извращенец.
                                    0
                                    А я и не утверждал, что я извращенец.

                                    А на счёт гикнутости Вы многого не знаете. ;)
                                      +1
                                      Таки нет, я — извращенец.

                                      Есть два ноута. На одном старая видео карта, у второго сгорела лампа подсветки. А поиграться в Spellforce 2 хочется :) Что делать? Соединяем их по ehternet. Настраиваем сеть (на втором — вслепую, консоль позволяет) На втором запускаем TurboVNC и Spellfiorce 2 через wine через VirtualGL. На первом — VNC клиент. Используем клавиатуру и мышь второго и дисплей первого.
                                        0
                                        И как скорость обновления кадров в секунду? Когда я пытался сделать что-то подобное — там кадра 2 было в секунду.
                                          0
                                          15-20. Для стратегии достаточно. Графические глюки только wine'овские. VirtualGL новых не добавил.
                                    0
                                    Может быть в качестве проверки на число использовать findstr в режиме регулярных выражений?
                                      0
                                      Спасибо, не знал этой команды. Конечно же так лучше. Правда, переписывать, исправлять и дополнять нет желания — все равно никому это не надо)
                                      0
                                      Здравствуйте!

                                      Наткнулся на эту статью и грандиозность решения заставляет меня спросить: под какой лицензией ЭТО распространяется и могу ли я это использовать в своём проекте?
                                        0
                                        Распространяется под лицензией WTFPL, так что на здоровье :)
                                          0
                                          Спасибо вам большое за работу! Это реально просто потрясающе. Жалко только что комментарии не на буржуйском :)
                                        0
                                        И ещё, надо сказать, что значения не могут быть пустыми, это особенность команды set

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

                                        Only users with full accounts can post comments. Log in, please.