Форум программистов, компьютерный форум, киберфорум
Наши страницы
Batch (CMD/BAT)
Войти
Регистрация
Восстановить пароль
 
 
Рейтинг 4.88/346: Рейтинг темы: голосов - 346, средняя оценка - 4.88
Dragokas
Эксперт WindowsАвтор FAQ
17025 / 7082 / 856
Регистрация: 25.12.2011
Сообщений: 10,906
Записей в блоге: 16
1

Наиболее частые ошибки, заметки особенностей программинга BAT файлов, баги интерпретатора*

10.01.2013, 15:41. Просмотров 62424. Ответов 107

Эта тема - ответвление Тонкости языка, редкие команды и сложные скрипты

Постим сюда детали, которые Вы получили опытным путем.
Которые считаете уникальными, или могут быть полезными при наборе кода BAT файлов, лучшего понимания принципов работы среды командной строки.
Пишем ошибки, которые иногда допускаете, а потом ломаете голову, почему не работает

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

Собрано по категориям:

Файловые операции
5) Использование рабочего каталога Bat файла в роле начального для выполнения команд в нем на ОС >= Vista ссылка
8) Как проверить - существует ли папка ссылка
10) Не использовать && после команды Del. ссылка
12) После перехода в другой каталог проверять успех операции ссылка
18) Листинг текущего каталога или корневого ссылка
24) Работа с файлами/папками, в именах которых есть буквы украинского алфавита. ссылка

Символы
1) Экранирование спецсимвола ссылка
13) Сохранение концевого пробела в переменную ссылка
17) Запятая и точка с запятой - разделители аргументов ссылка
30) Экранирование номера потока в перенаправлении вывода Echo ссылка

Переменные
2) Использование одноименных переменных без обнуления ссылка
3) Пренебрежение Setlocal ссылка
3.1) Не указав Setlocal EnableDelayedExpansion, используем знаки восклицания (!) для раскрытия значения переменных ссылка
6) Инициализация числового типа данных ссылка
14) Обход ошибки "Режим вывода команд на экран (ECHO) включен" ссылка
16) Пробелы тоже могут являться частью названия переменной ссылка
23) Для команды SET всегда заключайте в кавычки переменную и значение, если ним является изменяемое имя файла ссылка
28) Двойное раскрытие переменной. Первыми раскрываются проценты. ссылка
29) Конструкция вида Echo.!Var:~0,1! не работает. ссылка

Циклы
7) EOL в цикле FOR - правильный порядок модификаторов ссылка
9) Использовать UseBackQ при чтении содержимого файла, имя которого может меняться ссылка
25) Получение даты и времени файла через цикл и команду For без ключа /S (рекурсия) и подпрограммы ссылка

Кодировка
4) Сохранение BAT-файла с кодировкой перевода строк в UNIX-стандарте ссылка
26) Кодировка в консоли ссылка
27) Текстовой файл не читается циклом по неизвестной причине ссылка

Тесты, оптимизация и граничные возможности интерпретатора
15) Граничные значения для числового типа в CMD ссылка
19) Максимальная глубина рекурсии = 593*. ссылка
20) Максимальная длина значения строки ссылка
21) Оптимизация кода ссылка
22) Тест замедления работы операторов при перегрузке оперативной памяти ссылка

Другое
1) Указывая метку подпрограммы, можно через пробел указывать ее описание. Среда не будет "ругаться" ссылка
11) Внимательно выбирайте имя для BAT(CMD)-файла ссылка
0
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
10.01.2013, 15:41
Ответы с готовыми решениями:

Ошибки при запуске bat-файлов
У меня проблема, что при запуске cmd.exe, что при запуске любого *.bat-файла,...

Последовательный запуск нескольких BAT-файлов из основного BAT-файла
Доброго времени суток всем... Помогите кто понимает.. Есть bat... @echo off...

Запуск bat-файлов с параметрами, являющимися модификациями принятого запускающим bat-файлом параметра
Написать командный файл, который: • принимает в качестве параметра полное имя...

Как определить количество цветов в подгружаемом рисунке и наиболее частые цвета
Заранее благодарен...

Ошибки при создании заметки
Пару дней назад установил Друпал на Xampp, частенько на разных страницах пришет...

107
Dragokas
Эксперт WindowsАвтор FAQ
17025 / 7082 / 856
Регистрация: 25.12.2011
Сообщений: 10,906
Записей в блоге: 16
10.01.2013, 16:34  [ТС] 2
Начну,

Ошибки:
1) Экранирование спецсимвола
потерялся символ экрана ^ в циклах FOR, а также в запросах WMIC, где легко запутаться, например:
Bash
for /f %%A in ('dir /b "c:\temp"^|find /v /c ""') do Echo Всего файлов в папке temp: %%A
Бывает, что запрос в цикл подставляешь через другую переменную, тогда символ ^ нужно удваивать ^^, например:
Bash
1
2
set Query=dir /b "c:\temp"^^|find /v /c ""
for /f %%A in ('%Query%') do Echo Всего файлов в папке temp: %%A
1.1. Принцип работы:
Символ канала | как и другие спецсимволы, например поток < >
предваряют экраном ^, чтобы в данном контексте они передавались в качестве строкового типа данных, а не по-умолчанию выполняемой ними функции.
Если этого не делать, то в строке команды:
for /f %%A in ('dir /b "c:\temp"|find /v /c ""') do Echo Всего файлов в папке temp: %%A
сначала выполнится жирная часть, а результат будет передан через канал к Find.
Но, естественно этого не произойдет, т.к. мы получим синтаксическую ошибку в жирной части строки.
Поэтому пишем ^| и тогда часть
('dir /b "c:\temp"^|find /v /c ""')
воспримется циклом как единое целое.

Строка команд внутри скобок ('...') после ключевого слова IN
Bash
dir /b "c:\temp"|find /v /c ""
выполняется отдельно до начала итераций в цикле, но уже после просмотра модификаторов (UseBackQ, Tokens, Delims и EOL)
for "UseBackQ Tokens=1,2 Delims== EOL=" %%A in ...
После ее IN ('...') выполнения полученный результат (потоки StdOut и StdError - информация, которая обычно выводится на экран консоли) передаются для дальнейшего синтаксического разбора циклом по указанным модификаторами правилам.
При этом один из потоков (или оба?) можно занулить, чтобы их результат не попал под разбор циклом.
Пример: зануляем поток ошибок (StdErr) команды, обрабатываемой циклом:
for /f %%A in ('dir /b "c:\temp"^|2^>nul find /v /c ""') do Echo Всего файлов в папке temp: %%A
или так:
for /f %%A in ('dir /b "c:\temp"^|find /v /c "" 2^>nul') do Echo Всего файлов в папке temp: %%A
2
Dragokas
Эксперт WindowsАвтор FAQ
17025 / 7082 / 856
Регистрация: 25.12.2011
Сообщений: 10,906
Записей в блоге: 16
10.01.2013, 16:34  [ТС] 3
Ошибки:
2) Использование одноименных переменных без обнуления
например, в подпрограмме (м.б. даже чужую скопировали) и своей программе.

3) Пренебрежение Setlocal
Команда Setlocal локализует все используемые в конкретном бат-файле переменные так, что после выхода из него, либо принудительного задания команды EndLocal,
все переменные будут обнулены автоматически.
Если команду Setlocal опустить, при повторном запуске программы в той же сессии интерпретатора
все переменные остаются не обнулены.
Последствия: например, неправильно будет работать счетчик (если Вы его не обнуляете принудительно до момента использования), либо команда If Defined будет показывать не ожидаемый Вами результат и пр.

3) Не указав Setlocal EnableDelayedExpansion, используем знаки восклицания (!) для раскрытия значения переменных

Заметки:
1) Указывая метку подпрограммы, можно через пробел указывать ее описание. Среда не будет "ругаться" (смотрим строку № 5)
Bash
1
2
3
4
5
6
7
8
9
Call :Label "myfile" "myvariable"
Echo %myvariable%
Pause& Exit
 
:Label %1-имя файла %2-Переменная
::Наверху сделали описания передаваемых в эту подпрограмму параметров 
::(для удобства, чтоб не забыть какая что означает)
::Дальше выполняем какие-нибуть каманды...
Exit /B
Ошибки:
4) Сохранение BAT-файла с кодировкой перевода строк в UNIX-стандарте
(в конце строки стоит 1 управляющий символ 0x0A (LF) вместо двух - 0x0D, 0x0A (CrLf)

В таком случае от среды можно получить разные невменяемые ошибки, наподобие:
Цитата Сообщение от cmd
Не удается найти указанную метку пакетного файла ...
Хотя вот она метка перед носом - никаких ошибок в правописании (приложил файл-пример, чтобы Вы сами убедились).
1
Вложения
Тип файла: zip Не могу найти метку.zip (2.1 Кб, 58 просмотров)
Dragokas
Эксперт WindowsАвтор FAQ
17025 / 7082 / 856
Регистрация: 25.12.2011
Сообщений: 10,906
Записей в блоге: 16
10.01.2013, 17:44  [ТС] 4
Критические ошибки:
5) Использование рабочего каталога Bat файла в роле начального для выполнения команд в нем на ОС >= Vista

Любые команды в bat/cmd файле типа:
Bash
1
2
3
4
Del *.* [ключи]
Rmdir *.* [ключи]
Cacls *.* [ключи]
Xcalcs *.* [ключи]
намертво "снесут" жизненно важные файлы ОС, если Вы запустите такой батник из любой папки
правой кнопкой мыши "От имени Администратора", задав командам ключи массовой обработки без подтверждения,
и без принудительного указания начальной папки, как в примере.

Дело в том, что в Windows Vista, Windows 7 (и, вероятно, выше) при запуске батника с повышенными правами, рабочий каталог файла
автоматически сменяется на %windir%\system32 (т.е. папка, где находится CMD.exe). В этом можно убедится запустив файл с кодом:
Bash
1
2
echo Рабочий каталог = %cd%
pause& exit
Поэтому, если Вам требуется использовать в скрипте каталог, где находится бат-файл
в качестве начального или относительного пути вне зависимости от прав запуска, обязательно используйте нижеуказанную конструкцию:

Например, удаление всех папок в каталоге с батником (с подтверждением действий):
Bash
Rmdir "%~dp0*.*"
Удаление всех файлов, из папки Test, которая находится в каталоге с батником (с подтверждением, не рекурсивно):
Bash
Del "%~dp0Test\*.*" /p
В этом варианте используется аргумент %0, который означает - полный путь к бат-файлу.
При этом модификатор ~dp обозначает вывод только диска и пути к файлу (без имени).
О правильности работы можно убедиться запустив с повышенными правами такой код:
Bash
1
2
echo Рабочий каталог = %~dp0
pause& exit
4
Eva Rosalene
T for Trans-
4051 / 1590 / 303
Регистрация: 06.01.2013
Сообщений: 4,182
Завершенные тесты: 2
10.01.2013, 18:49 5
Цитата Сообщение от Dragokas Посмотреть сообщение
обязательно используйте нижеуказанную конструкцию:
А ещё лучше:
Bash
1
cd "%~dp0"
1
gimntut
879 / 182 / 15
Регистрация: 18.07.2011
Сообщений: 257
11.01.2013, 00:27 6
Цитата Сообщение от FraidZZ Посмотреть сообщение
А ещё лучше:
Bash
1
cd "%~dp0"
А это ещё одна популярная ошибка!
Эта команда не работает, если батник запущен из сетевой папки.
Поэтому лучше делать так, как написал Dragokas.

А теперь моя любимая ошибка (делаю её много лет каждый день):
Bash
1
start "C:\Полный путь к программе содержащей\пробелы.exe"
Тоже самое, но правильно:
Bash
1
start "" "C:\Полный путь к программе содержащей\пробелы.exe"
9
Dragokas
Эксперт WindowsАвтор FAQ
17025 / 7082 / 856
Регистрация: 25.12.2011
Сообщений: 10,906
Записей в блоге: 16
11.01.2013, 03:16  [ТС] 7
Замечу, что при этом
Bash
1
Start C:\Полный_путь_к_программе_НЕ_содержащей\пробелы.exe
без кавычек запустится нормально,
но это частный случай, и так делать не нужно. Правильный способ показан gimntut.
0
Eva Rosalene
T for Trans-
4051 / 1590 / 303
Регистрация: 06.01.2013
Сообщений: 4,182
Завершенные тесты: 2
11.01.2013, 13:41 8
Цитата Сообщение от gimntut Посмотреть сообщение
А это ещё одна популярная ошибка!
Эта команда не работает, если батник запущен из сетевой папки.
Спасибо за информацию. После этих строк пошарил в инете и понял, что cd сменится на C:\Windows. Глупая смерть.
0
Dragokas
Эксперт WindowsАвтор FAQ
17025 / 7082 / 856
Регистрация: 25.12.2011
Сообщений: 10,906
Записей в блоге: 16
18.01.2013, 16:48  [ТС] 9
Заметки
6) Инициализация числового типа данных
Переменную числового типа можно инициализировать изначально как строковой тип.
Это будет полезно, когда мы не знаем заранее, попадет ли в нее вообще значение.
Например,
Bash
1
2
3
4
::%1 - Некое значение извне (возможно пустое)
::n - Оперируемая переменная
Set n=%~1
set /A n+=1
Не выдаст ошибку. Переменная n просто уничтожится.
Bash
1
2
3
4
::%1 - Некое значение извне (возможно пустое)
::n - Оперируемая переменная
Set /A n=%~1
set /A n+=1
Приведет к синтаксической ошибке в строке № 3.

7) EOL в цикле FOR - правильный порядок модификаторов
EOL - это модификатор цикла, который определяет знак комментария.
Если такой знак встречается первым в строке разбираемого файла, цикл пропускает данную строку.
Код
;Эта строка будет опущена при разборе циклом for /f %%a in (file.txt) do echo.%%a
А; эта будет напечатана
По-умолчанию в EOL установлен знак - точка с запятой ( ; )
Чтобы установить пустой знак (без пропуска комментариев), EOL должен обязательно стоять последним среди модификаторов:
Bash
for "UseBackq delims= eol=" %%a in ("file.txt") do Echo.%%a
Иначе, в отличие от модификатора delims, EOL примет за знак комментария символ пробела.
Bash
1
2
::EOL = символ пробела. Строки, начинающиеся с пробела, будут пропущены!!!
for "UseBackq eol= delims=" %%a in ("file.txt") do Echo.%%a
8) Как проверить - существует ли папка (именно папка, а не файл).
Добавляем в конец имени бекслеш (\)
Bash
if exist "c:\folder\" echo Папка folder существует& exit /B
if exist "c:\folder" echo Файл folder существует& exit /B
echo Ни папка, ни файл под именем folder не существуют
Тоже самое касается копирования файлов в папку.
Если такая папка не существует файл будет скопирован в предыдущий каталог с новым именем (предполагаемым названием этой папки).
Мало того теперь и папку нельзя будет создать (ведь в одном каталоге не могут существовать папка и файл с одинаковым именем!)
Чтобы обойти эту случайную (досадную) ошибку, дописываем в конец целевого каталога бекслеш (\)
Bash
copy "file.txt" "c:\folder\"
if %errorlevel% neq 0 echo Возникла ошибка
Конечно, лучше заранее побеспокоится, чтобы создать каталог, или использовать команду XCOPY,
но ведь разные ситуации бывают, не правда ли?

9) Использовать UseBackQ при чтении содержимого файла, имя которого может меняться
Есть 2 варианта опций цикла FOR, которые обеспечивают чтение содержимого файла построчно:
Bash
for /f "delims=" %%a in (file.txt) do echo %%a
Bash
for /f "UseBackQ delims=" %%a in ("file.txt") do echo %%a
В этом варианте все равнозначно.
Но, если вместо file.txt подставить переменную, имя которой (или путь) будут содержать пробелы (а мы заранее можем и не знать), 1-я конструкция не подойдет и будет сначала разбивать строку по пробелам, а уж затем искать данный файл.
Поэтому конструкция с модификатором UseBackQ здесь наиболее приемлема.

Рекомендации
10) Не использовать && после команды Del.
Del возвращает ErrorLevel 0 при возникновении ошибок вида "Отказано в доступе".
Используйте вместо этого конструкцию If not exist
Bash
::Так не рекомендую
del file.txt&& ren file2.txt file.txt
::Так следует делать
del file.txt
if not exist file.txt (ren file2.txt file.txt) else (echo файл file.txt занят другой программой)
11) Внимательно выбирайте имя для BAT(CMD)-файла
Имя не должно носить название распространенных программ и тем более внутренних команд CMD.
Иначе рано или сразу Вы получите зацикливание (вызов батником самого себя).
Вырезка из темы: Неоднозначный ответ ping
Цитата Сообщение от gimntut Посмотреть сообщение
Порядок исполняемых расширений задаётся переменной PATHEXT
Его можно менять и расширять.
Сначала ищется подходящий исполняемый в текущей папке, а потом уже ищет по переменной Path.
Поэтому до .\ping.bat доходит раньше, чем до %SYSTEMROOT%\SYSTEM32\ping.exe.
Поэтому если хотим, чтобы запускался именно ping.exe, то так и нужно писать

12) После перехода в другой каталог проверять успех операции
Иначе может получиться, что Вы работаете совершенно с другой папкой.
Причины могут быть разные, например, доступ к папке запрещен, не хватает прав, или папка не существует...
Bash
::Переход в другой каталог с одновременной сменой диска (если такова требуется)
chdir /d "d:\test"
::Проверка, достигли ли цели
if %errorlevel%==0 Echo Выполняем нужные действия
::Или, как вариант, проверяем где сейчас находимся
if "%cd%"=="d:\test" Echo Продолжаем банкет :^)

Заметки
13) Сохранение концевого пробела в переменную
Bash
1
2
3
4
::Вариант 1
Set "st=Строка с концевым пробелом "
::Вариант 2
(Set st=Строка с концевым пробелом )
14) Обход ошибки "Режим вывода команд на экран (ECHO) включен"
Если есть вероятность попадания под Echo переменной без значения,
сразу после Echo ставим точку:
Bash
Echo.%var%
Будет выведена пустая строка.

15) Граничные значения для числового типа в CMD
Числовой тип в CMD может принимать целые значения в пределах от -2147483647 до 2147483647.
Тем не менее, код возврата может принимать значение на 1 (единичку) меньше минимума. Проверим?
Bash
Call :ErrorLevelMinimal
Echo %ErrorLevel%
pause&goto :eof
:ErrorLevelMinimal
exit /b -2147483648

Ошибки
16) Пробелы тоже могут являться частью названия переменной
Частая ошибка новичков
Bash
1
2
3
4
5
6
7
8
9
10
::Неправильное присвоение
Set n =9
::Вот так результат не получим!!!
Echo %n%
::Хотя вытащить из нее значение все равно можно
Echo %n %
::А сейчас верный вариант
Set n=9
::Получаем результат, как обычно
Echo %n%


Добавлено через 15 часов 20 минут
Интересное обсуждение Открывающаяся скобка в командном процессоре
Подключайтесь!
8
Dragokas
Эксперт WindowsАвтор FAQ
17025 / 7082 / 856
Регистрация: 25.12.2011
Сообщений: 10,906
Записей в блоге: 16
23.01.2013, 01:57  [ТС] 10
Ошибки
17) Запятая и точка с запятой - разделители аргументов
Точка с запятой ( ; ) и запятая ( , ) являются наравне с пробелом - разделителями аргументов.
Помните об этом, когда передаете параметры BAT-файлу или подпрограмме.
Например, время %time% в формате 23:59:59,99
Bash
1
2
3
4
5
6
7
8
9
@echo off
Call :CheckTime %Time%;SomeWord
pause
exit /b
 
:CheckTime
echo HH:MM:SS=%1
echo ms=%2
echo word=%3
Удаление нескольких файлов в разных директориях одной строкой:
Bash
1
Del c:\temp\1.txt d:\test.bat
Добавлено через 1 час 5 минут
18) Листинг текущего каталога или корневого
Если указать
Bash
Dir C:
без закрывающего бэкслеша,
команда выведет листинг текущего каталога, а не корневого.

Вот так верно:
Bash
dir c:\
2
Dragokas
Эксперт WindowsАвтор FAQ
17025 / 7082 / 856
Регистрация: 25.12.2011
Сообщений: 10,906
Записей в блоге: 16
25.01.2013, 22:31  [ТС] 11
19) Максимальная глубина рекурсии = 593*.
Это означает, что подпрограмма может вызвать себя рекурсивно 592 раза (+1 - вызов из основного тела скрипта).
После этого завершается с критическим падением.

Bash
1
2
3
4
5
6
7
8
@echo off
SetLocal
Call :OutStack
 
:OutStack
Set /A n+=1
Echo %n%
Call :OutStack
* Параметр получен на моей машине и может отличаться на компьютерах с другой аппаратно-программной начинкой.
Эта цифра будет также зависеть от:
1) кол-ва и объема данных, передаваемых по каждой цепочке рекурсии
2) размера стэка? - что на него влияет и как настроить - тут я уже не знаю
0
Миниатюры
Наиболее частые ошибки, заметки особенностей программинга BAT файлов, баги интерпретатора*  
Dragokas
Эксперт WindowsАвтор FAQ
17025 / 7082 / 856
Регистрация: 25.12.2011
Сообщений: 10,906
Записей в блоге: 16
26.01.2013, 02:48  [ТС] 12
20) Просто эксперименты предельных возможностей переменной
(или бенчмаркинг языка интерпретатора и стэка, называйте, как хотите)

Максимальная длина значения переменной по результатам тестов составляет 8185 символов.
Присвоение такого значения другой переменной возможно только через знаки ( ! )
% раскрывает переменные со значением макс. длинной 8183 знака.
Иначе возникает критическая ошибка "Слишком длинная входная строка".
При этом ком. интерпретатор иногда начинает вести себя нестабильно уже при длине строки > 8174 знака.
Оператор IF может нормально сравнивать операнды с переменными длиной 8181 знак (+ макс. 11 знаков рядом с ней опционально), иначе возвращает FALSE.

Сам тест

Bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@echo off
SetLocal EnableDelayedExpansion
 
set len=8181
 
for /L %%C in (1,1,%len%) do (
  set testparam=!testparam!1
)
 
for /L %%C in (1,1,%len%) do (
  set ParamOld=!Param!
  set Param=!Param!1
  rem Не использую кавычки
  if defined ParamOld if !Param!==!ParamOld! (
    Echo Max=%%C
    Goto :break
  )
)
:break
if -----------!testparam!==-----------!Param! echo yes
pause
::Echo %Param%>param.txt
 
::@Echo on
Call :var_count Param count
 
echo Len=%count%
 
pause
goto :eof
 
:var_count
pause
set var=!Param!
pause
:count--
 
set var=!var:~1!
 
set /a count+=1
 
if not defined var (set %2=%count%& exit /b) else (goto :count--)


20.1) Максимальная длинна команды в командной строке составляет 8191 символ

Для Microsoft Windows 2000, Windows NT 4.0 (и ранее?) этот показатель составляет 2047 символ.

Источник: http://support.microsoft.com/kb/830473

20.2) Максимальная длина строки, которую можно присвоить переменной через поток - 1023
При вводе из консоли - 1021. См. тему: Найти сумму цифр введенного числа

20.3) Ограничение длинны вывода команды FIND - 4095 символов.
См. Найти строку с кавычками
0
Dragokas
Эксперт WindowsАвтор FAQ
17025 / 7082 / 856
Регистрация: 25.12.2011
Сообщений: 10,906
Записей в блоге: 16
26.01.2013, 02:51  [ТС] 13
21) Оптимизация кода

Тест скорости выполнения идентичных по функционалу операторов.

Все тесты проводятся в 10 млн. итераций.
Конфигурация стенда

Microsoft Windows 7 Максимальная Service Pack 1
Процессор: Intel Core i5-2500K ~4000 MHZ.
Total RAM: 6111 MB (63% free)
Системный раздел C: размер 33 GB (29%) Свободно 114 GB. SSD OCZ Agility 3


if "%Variable%"=="" vs if "%Variable%" equ ""
ОператорСкорость (с)
==42,8
equ43,1
Одинаково, как и ожидалось.

if Defined Variable vs if not Defined Variable
ОператорСкорость (с)
Defined28,79
not Defined28,82
Т.е. переставлять операторы между ELSE ради оптимизации через экономию логического оператора NOT не стоит.

if Defined Variable vs if "%Variable%" neq ""
ОператорСкорость (с)
Defined23,77
neq ""25,57
Небольшой + в сторону Defined.

if not Defined Variable vs if "%Variable%"==""
ОператорСкорость (с)
not Defined23,75
==""24,72
Схожий тест. Добавился оператор Not. Результаты почти одинаковы с предыдущим тестом. Разница только из-за разной степени загрузки процессора.

А теперь внимание. Конструкция Defined чаcто используется в циклах, чтобы определить, имеет ли значение составная переменная.
Я специально до этого момента не использовал отложенное раскрытие переменных через знак ( ! ), чтобы не исказить результаты теста.

Тест скорости раскрытия переменных
if "%Variable%"=="" vs if "!Variable!"==""
ОператорСкорость (с)
%%25,79
!!39,73
Раскрытие через % аж на 35% быстрее. Часто мне задают вопрос, почему я не всегда использую !! для раскрытия переменных, ведь так быстрее и проще.
А вот именно из-за оптимизации. Кроме того, ставя %% и потом анализируя логику работы программы, я понимаю, что если поставил %% под циклом,
значит значение этой переменной по заданной мною логике не изменяется, а если и изменяться, то не используется в данном контексте (внутри текущих скобок по разработанному алгоритму).

Не менее интересный тест. Последовательное раскрытие дважды %% и дважды !!.
if "%Variable%"=="%Variable%" vs if "!Variable!"=="!Variable!"
ОператорСкорость (с)
%%==%%25,65
!!==!!53,83
А теперь взгляните на предыдущий тест. 1 раз и 2 раза раскрытие подряд % (25,65 и 25,79) почти без разницы.
А вот с !! беда (53,83 и 39,73). То есть процедура отложенного расширения переменных кушает процессорное время очень сильно.

Тест погрешности методики тестирования (расходы на выполнение оператора Rem)
== vs neq
ОператорСкорость (с)
==42,90
neq43,68
True показало большее время, т.е. погрешности вообще нет.

Методика тестирования

Bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@echo off
Setlocal EnableDelayedExpansion
 
pause
 
Set STime=%time%
 
::if not defined SomeParam cd.
 
for /L %%A in (1,1,10000000) do (
  if not defined SomeParam rem
)
 
Set ETime=%time%
Call :TimeElapsed "%STime%" "%ETime%" ret
Echo Прошло: %ret% с.
 
pause
 
Set STime=%time%
 
for /L %%A in (1,1,10000000) do (
  if "!SomeParam!"=="" rem
)
 
Set ETime=%time%
Call :TimeElapsed "%STime%" "%ETime%" ret
Echo Прошло: %ret% с.
 
pause
goto :eof
 
:TimeElapsed %1-StartTime %2-EndTime %3-var_result
Call :TimeToMSec "%~1" TimeS_ms
Call :TimeToMSec "%~2" TimeE_ms
Set /A diff=TimeE_ms-TimeS_ms
Set /A diffSS=diff/100
Set /A diffms=%diff% %% 100
Set %3=%diffSS%,%diffms%
Exit /B
 
:TimeToMSec %1-Time 2-var_mSec
For /F "Tokens=1-4 Delims=,:" %%A in ("%~1") do (
  Set /A HH=%%A
  Set MM=1%%B& Set /A MM=!MM!-100
  Set SS=1%%C& Set /A SS=!SS!-100
  Set mS=1%%D& Set /A mS=!mS!-100
)
Set /A %~2=(HH*60*60+MM*60+SS)*100+mS
Exit /B


Новые тесты:
Двойное раскрытие переменных.
10 тыс. итераций.
Bash
set param=!SomeParam%n%!
::против
call set param=%%SomeParam%n%%%
ОператорСкорость (с)
!%%!0,21
call set12,72
~60 раз дольше через Call.
Но проблема в том, что первым вариантом не всегда можно воспользоваться, например, когда переменная n также изменяется в цикле.
Тогда Set param=!SomeParam!n!! мы уже не сможем написать. Такой вариант раскроется неверно. А %% раскрываются раньше !!
Так что же пользоваться тормозным способом скажете.
Давайте посмотрим, какие возможности нам дает переход к подпрограмме по Call

Тест скорости циклических переходов к метке для двойного раскрытия переменной (часть имени которой тоже меняется в цикле):

Ядро кода

Без таймера и изменения части имени переменной под циклом

1) Через метку:

Bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@echo off
set SomeParam1=0123456789
set n=1
 
for /L %%A in (1,1,10000) do (
  call :label param SomeParam !n!
)
 
pause
goto :eof
 
:label
set %1=!%2%3!
exit /b
2) Быстрый код с предыдущего теста:
Bash
1
2
3
4
5
6
7
@echo off
set SomeParam1=0123456789
set n=1
 
for /L %%A in (1,1,10000) do (
  set param=!SomeParam%n%!
)


Результаты:
ОператорСкорость (с)
Just !%%!0,21
Call :Label8,9
Call set12,72
Не намного, но уже лучше. Вызов по метке в ~42 р. медленнее кода № 1, но и на ~40% быстрее от варианта с Call Set.
И не забываем, что при этом получаем дополнительный функционал: изменение части имени оперируемой переменной в этом же цикле.

Еще схожий тест - скорость получения полного имени файла в подпрограмме Получить полный путь к папке, заданной двумя точками

Заказываем еще тесты!
7
Dragokas
Эксперт WindowsАвтор FAQ
17025 / 7082 / 856
Регистрация: 25.12.2011
Сообщений: 10,906
Записей в блоге: 16
26.01.2013, 02:52  [ТС] 14
22) Тест замедления работы операторов при перегрузке оперативной памяти

В этом тесте мы попытаемся максимально нагрузить оперативную память записью максимального количества переменных длинной в 5 КБ.
При этом будем измерять время, которое понадобилось для выполнения операторов присвоения с шагом в заполнении 200 переменных.

Чтобы заполнить 1 GB ОЗУ понадобится использовать 209715,2 переменных.
И в идеале время на инициализацию и присвоение каждым 200-м переменным значения в 5 КБ должно быть одинаковым, но что мы видим в результатах - см. скрин.

Получается, что уже при превышении границы в 3 MB операторы начинают работать в 10 раз медленнее.

Методика теста
Bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@echo off
Setlocal EnableDelayedExpansion
 
::Making 5 KB String
For /L %%C in (1,1,5120) do set String5000=!String5000!1
 
Echo %String5000%
Echo 5 KB String is Ready...
pause>nul
Echo Begin Memory OverLoading :^)
Echo.
 
Set STime=%time%
Set FirstTime=%Stime%
Set /A Step=200
Set /A nStart=1
Set /A nEnd=nStart + Step - 1
Echo # of Variables Memory Loading  Cmd Memory  Time Elapsed    Total Time
Echo ============== ==============  ==========  ============    ==========
 
::Loading 1 GB of Memory = 1024*1024/5=209715,2 variables
For /L %%C in (1,1,209715) do (
  Set %%C=%String5000%
  if %%C==!nEnd! (
    Set /A ML=5*%%C/1024
    For /f "tokens=4* delims=," %%A in (
      'tasklist /FI "IMAGENAME eq cmd.exe" /FO CSV /NH') do (
        Set CmdMemory=%%~B
    )
    Call :TimeElapsed "!STime!" "!time!" ret
    Call :TimeElapsed "%FirstTime%" "!time!" retAll
    Echo .!nStart! - !nEnd! !ML! MB     !CmdMemory! !ret! sec.  !retAll! sec.
    Set /A nStart+=Step
    Set /A nEnd+=Step
    Set Stime=!time!
  )
)
 
pause
goto :eof
 
:TimeElapsed %1-StartTime %2-EndTime %3-var_result
  Call :TimeToMSec "%~1" TimeS_ms
  Call :TimeToMSec "%~2" TimeE_ms
  Set /A diff=TimeE_ms-TimeS_ms
  Set /A diffSS=diff/100
  Set /A diffms=%diff% %% 100
  if "%diffms:~1%"=="" Set diffms=0%diffms%
  Set %3=%diffSS%,%diffms%
Exit /B
 
:TimeToMSec %1-Time 2-var_mSec
  For /F "Tokens=1-4 Delims=,:" %%A in ("%~1") do (
    Set /A HH=%%A
    Set MM=1%%B& Set /A MM=!MM!-100
    Set SS=1%%C& Set /A SS=!SS!-100
    Set mS=1%%D& Set /A mS=!mS!-100
  )
  Set /A %~2=(HH*60*60+MM*60+SS)*100+mS
Exit /B
0
Миниатюры
Наиболее частые ошибки, заметки особенностей программинга BAT файлов, баги интерпретатора*  
Dragokas
Эксперт WindowsАвтор FAQ
17025 / 7082 / 856
Регистрация: 25.12.2011
Сообщений: 10,906
Записей в блоге: 16
28.01.2013, 02:49  [ТС] 15
Ошибки синтаксиса:
23) Для команды SET всегда заключайте в кавычки переменную и значение, если ним является изменяемое имя файла

Правило касается команды Set, которая находится внутри скобок.
Пример правильного синтаксиса:
Bash
1
(Set "File=%SomeFile%")
Исключение из правил составляет переменная цикла, например Set file=%%A

Демонстрация ошибки:
Bash
1
2
3
4
5
6
7
8
Call :Sub "myFile(1).txt"
goto :eof
 
:Sub
pause
if ""=="" (Set NewFileName=%~1)
pause
Exit /B
Вызовет синтаксическую ошибку с прерыванием работы пакетного файла.

Чтобы увидеть ошибку, отладку можно осуществить, как обычно, запустив сначала CMD, а уже в нем BAT-файл.
Также замечу, что особенностью командного процессора является отображение на экране критических ошибок не после, а до вывода строки команды, ее вызвавшей.
1
Миниатюры
Наиболее частые ошибки, заметки особенностей программинга BAT файлов, баги интерпретатора*  
Dragokas
Эксперт WindowsАвтор FAQ
17025 / 7082 / 856
Регистрация: 25.12.2011
Сообщений: 10,906
Записей в блоге: 16
28.01.2013, 03:13  [ТС] 16
Заметки
24) Работа с файлами/папками, в именах которых есть буквы украинского алфавита.

При попытке произвести одиночные файловые операции с конкретным файлом (папкой),
в имени которых есть, к примеру, буква " і " (украинская) получим некритическую ошибку синтаксиса команды.

Для исправления ошибки требуется подставить в эту команду имя файла в соответствующей ему исходной кодировке, а именно CodePage 1251

Bash
1
chcp 1251>nul
При этом, установка данной команды непосредственно перед операцией копирования ничего не даст.
Ее нужно использовать перед командой получения списка файлов.
Чаще всего, это цикл FOR.

Bash
1
2
chcp 1251>nul
for /f "delims=" %%A in ('dir /b /s /a:-d "%From%\%Ext%"') do (
Есть одно НО! - После изменения кодировки консоли, все сообщения от внутренних команд
будут выводиться на экран крякозябрами, ровно как и кириллические имена файлов.

Как мы знаем, команда получения файлов dir отрабатывает сразу, а не при каждой итерации цикла. Это означает, что внутри цикла мы можем смело вернуть исходную кодировку.
При этом имена файлов мы будем получать уже в CHCP-1251.

Bash
1
2
3
4
5
6
7
chcp 1251>nul
 
for /f "delims=" %%A in ('dir /b /s /a:-d "%From%\%Ext%"') do (
  chcp 866>nul
  Echo %%A
  copy /y "%%A" "C:\TEMP\%%~nxA"
)
Как видим, chcp 866 постоянно крутится в цикле, что не очень хорошо сказывается на скорости работы.

Из этого есть 2 выхода:

1) Использовать флаг и выполнить chcp 866 всего 1 раз, и затем сразу сбросить флаг.

Bash
1
2
3
4
5
6
7
8
9
SetLocal
chcp 1251>nul
Set LanguageFlag=true
 
for /f "delims=" %%A in ('dir /b /s /a:-d "%From%\%Ext%"') do (
  if defined LanguageFlag (chcp 866>nul& Set LanguageFlag=)
  Echo %%A
  copy /y "%%A" "C:\TEMP\%%~nxA"
)
При этом в цикле все равно будет крутиться 1 лишний оператор - IF, но он на много "легче", чем CHCP.

2) Предварительно записать имена файлов во временный файл. Вернуть кодировку по-умолчанию. Циклом читать данные из сформированного файла.
Bash
1
2
3
4
5
6
7
8
chcp 1251>nul
dir /b /s /a:-d "%From%\%Ext%">%temp%\tempfile.tmp
 
for /f "UseBackQ delims=" %%A in ("%temp%\tempfile.tmp") do (
  Echo %%A
  copy /y "%%A" "C:\TEMP\%%~nxA"
)
del "%temp%\tempfile.tmp"
Надеюсь статейка окажется полезной
2
Миниатюры
Наиболее частые ошибки, заметки особенностей программинга BAT файлов, баги интерпретатора*   Наиболее частые ошибки, заметки особенностей программинга BAT файлов, баги интерпретатора*   Наиболее частые ошибки, заметки особенностей программинга BAT файлов, баги интерпретатора*  

Badger
91 / 91 / 4
Регистрация: 25.06.2012
Сообщений: 278
28.01.2013, 10:48 17
Цитата Сообщение от Dragokas Посмотреть сообщение
Вернуть кодировку по-умолчанию.
Т.е. cp866? если да, то в коде видимо забыли добавить.
0
Dragokas
Эксперт WindowsАвтор FAQ
17025 / 7082 / 856
Регистрация: 25.12.2011
Сообщений: 10,906
Записей в блоге: 16
28.01.2013, 17:25  [ТС] 18
Badger, сразу после слов - код (строка № 4).

Но Вы правы, за циклом тоже нужно продублировать на случай, если цикл не выполнится ни разу.
0
Dragokas
Эксперт WindowsАвтор FAQ
17025 / 7082 / 856
Регистрация: 25.12.2011
Сообщений: 10,906
Записей в блоге: 16
29.01.2013, 20:05  [ТС] 19
Заметка
25) Получение даты и времени файла через цикл и команду For без ключа /S (рекурсия) и подпрограммы

В справке к команде For у нас указаны такие основные модификаторы:
%~I - из переменной %I удаляются обрамляющие кавычки (")
%~fI - переменная %I расширяется до полного имени файла
%~dI - из переменной %I выделяется только имя диска
%~pI - из переменной %I выделяется только путь к файлу
%~nI - из переменной %I выделяется только имя файла
%~xI - из переменной %I выделяется расширение имени файла
%~sI - полученный путь содержит только короткие имена
%~aI - переменная %I расширяется до атрибутов файла
%~tI - переменная %I расширяется до даты /времени файла
%~zI - переменная %I расширяется до размера файла
Обратим внимание на последние три.
Для их использования необходимо, чтобы в переменную цикла попал полный путь к файлу, иначе получим пустую строку.
Такой вариант выдаст пустую строку вместо даты/времени, т.к. Dir вернет только имена файлов:
Bash
1
2
3
4
5
6
@echo off
set folder=l:\bash
for /f "delims=" %%A in ('dir "%folder%\*.*" /b /a-d') do (
  Echo %%A
  Echo.%%~tA
)
Такой вариант выдаст полный путь к файлу, а также дату/время, но при этом поиск будет вестись рекурсивно: (добавлен ключ /S)
Bash
1
2
3
4
5
6
@echo off
set folder=l:\bash
for /f "delims=" %%A in ('dir "%folder%\*.*" /b /s /a-d') do (
  Echo %%A
  Echo.%%~tA
)
А если нам не нужно рекурсивно?

Есть 2 варианта:
1) Подставив к переменной цикла часть с полным путем к файлу:
1.1. с использованием подпрограммы:
Кликните здесь для просмотра всего текста
Bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@echo off
set folder=l:\bash
for /f "delims=" %%A in ('dir "%folder%" /b /a-d') do (
  Call :TakeDate "%folder%\%%A"
)
pause
goto :eof
 
:TakeDate
for /f "tokens=1,2" %%A in ("%~t1") do (
  Echo =================================
  Echo Full FileName  = %~1
  Echo Short FileName = %~nx1
  Echo Date=%%A
  Echo Time=%%B
)
Exit /B


1.2. Вложенным циклом (более быстрый вариант от FraidZZ):

Кликните здесь для просмотра всего текста
Bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@echo off
set folder=C:\MyMaps
for /f "delims=" %%A in ('dir "%folder%" /b /a-d') do (
  echo ====================
  echo Full file name = %%~dpnxA
  echo Short file name = %%~nxA
  pushd "%folder%"
  for %%b IN ("%%~nxA") DO (
   for /f "usebackq tokens=1*" %%c IN ('%%~tb') DO (echo.DATE = %%~c&echo.TIME = %%~d)
  )
  popd
)
pause
goto :eof
 
:TakeDate
for /f "tokens=1,2" %%A in ("%~t1") do (
  Echo =================================
  Echo Full FileName  = %~1
  Echo Short FileName = %~nx1
  Echo Date=%%A
  Echo Time=%%B
)
Exit /B


2) установить рабочий каталог = каталогу, где ведется поиск файла.

Для этого, предварительно временно перейдем в этот каталог (pushd) и вернемся в исходный (popd) после завершения операции:
Bash
1
2
3
4
5
6
7
@echo off
set folder=l:\bash
pushd "%folder%"
for /f "delims=" %%A in ('dir "%folder%\*.cmd" /b /a-d') do (
  Echo.%%~tA
)
popd
2
sov44
1758 / 742 / 128
Регистрация: 09.04.2011
Сообщений: 1,313
29.01.2013, 21:28 20
Альтернативный вариант получения свойств файла в заданной директории
Bash
1
2
3
4
5
@echo off
for %%A in ("c:\2\*") do (
  Echo %%A
  Echo.%%~tA %%~zA
)
Добавлено через 18 минут
Upd. Принцип тот-же, о чём и писал Dragokas - свойства файла можно получить только тогда, когда в цикле указан полный путь до этого файла
2
29.01.2013, 21:28
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
29.01.2013, 21:28

Частые ошибки
Есть проблема и уверен что системная, а не программная. Проблема в том что во...

Частые ошибки winapi
Подскажите, что я делаю не так. Программа запускается без ошибок, но кнопку,...

Частые ошибки в работе компьютера
Доброго времени суток. Перейду сразу к делу: После включения компьютера никакая...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru