Форум программистов, компьютерный форум, киберфорум
Наши страницы

Batch (CMD/BAT)

Войти
Регистрация
Восстановить пароль
 
 
Dragokas
Эксперт WindowsАвтор FAQ
16614 / 6999 / 848
Регистрация: 25.12.2011
Сообщений: 10,795
Записей в блоге: 16
#1

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

10.01.2013, 15:41. Просмотров 60164. Ответов 106

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

Постим сюда детали, которые Вы получили опытным путем.
Которые считаете уникальными, или могут быть полезными при наборе кода 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/BAT):

Ошибки при запуске bat-файлов - CMD/BAT
У меня проблема, что при запуске cmd.exe, что при запуске любого *.bat-файла, появляется такое же сообщение "c:/programПрограмма не...

Последовательный запуск нескольких BAT-файлов из основного BAT-файла - CMD/BAT
Доброго времени суток всем... Помогите кто понимает.. Есть bat... @echo off start "" "D:\$\Pale Moon\1\Palemoon-Portable"...

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

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

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

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

106
Dragokas
Эксперт WindowsАвтор FAQ
16614 / 6999 / 848
Регистрация: 25.12.2011
Сообщений: 10,795
Записей в блоге: 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
16614 / 6999 / 848
Регистрация: 25.12.2011
Сообщений: 10,795
Записей в блоге: 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 Кб, 57 просмотров)
Dragokas
Эксперт WindowsАвтор FAQ
16614 / 6999 / 848
Регистрация: 25.12.2011
Сообщений: 10,795
Записей в блоге: 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
Male-to-Female
4049 / 1586 / 242
Регистрация: 06.01.2013
Сообщений: 4,177
Завершенные тесты: 1
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
16614 / 6999 / 848
Регистрация: 25.12.2011
Сообщений: 10,795
Записей в блоге: 16
11.01.2013, 03:16  [ТС] #7
Замечу, что при этом
Bash
1
Start C:\Полный_путь_к_программе_НЕ_содержащей\пробелы.exe
без кавычек запустится нормально,
но это частный случай, и так делать не нужно. Правильный способ показан gimntut.
0
Eva Rosalene
Male-to-Female
4049 / 1586 / 242
Регистрация: 06.01.2013
Сообщений: 4,177
Завершенные тесты: 1
11.01.2013, 13:41 #8
Цитата Сообщение от gimntut Посмотреть сообщение
А это ещё одна популярная ошибка!
Эта команда не работает, если батник запущен из сетевой папки.
Спасибо за информацию. После этих строк пошарил в инете и понял, что cd сменится на C:\Windows. Глупая смерть.
0
Dragokas
Эксперт WindowsАвтор FAQ
16614 / 6999 / 848
Регистрация: 25.12.2011
Сообщений: 10,795
Записей в блоге: 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
16614 / 6999 / 848
Регистрация: 25.12.2011
Сообщений: 10,795
Записей в блоге: 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
16614 / 6999 / 848
Регистрация: 25.12.2011
Сообщений: 10,795
Записей в блоге: 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
16614 / 6999 / 848
Регистрация: 25.12.2011
Сообщений: 10,795
Записей в блоге: 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
16614 / 6999 / 848
Регистрация: 25.12.2011
Сообщений: 10,795
Записей в блоге: 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
16614 / 6999 / 848
Регистрация: 25.12.2011
Сообщений: 10,795
Записей в блоге: 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
16614 / 6999 / 848
Регистрация: 25.12.2011
Сообщений: 10,795
Записей в блоге: 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 файлов, баги интерпретатора*  
28.01.2013, 02:49
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
28.01.2013, 02:49
Привет! Вот еще темы с ответами:

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

Частые BSOD и разные ошибки - BSOD
Здравствуйте, помогите с проблемой: уже на протяжении где-то 2 месяцев постоянно выпадает BSOD (раньше никогда такого не было), ошибки в...

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

Частые системные ошибки windows 8 - BSOD
Что то уж слишком часто начали появляться системные ошибки, почти все разные, ноут HP ENVY M6, ему всего 2 месяца. Подскажите в чем может...


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

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

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