Программы
Расширенное описание пары "Вглубь Windows" (второе занятие)

Расширенное описание пары "Вглубь Windows" (второе занятие)

Оговорюсь заранее:
Каждая из заметок – моя шпаргалка на пару, чтобы не забыть, что по плану нужно рассказать (по сути – тезисы) + расшифровка, чтобы было понятно и неподготовленному читателю. Так что будьте аккуратны – с каждой заметкой вы становитесь чуть ближе к высшему образованию в областях "компьютерные науки" и "компьютерная безопасность".

Тезисы

  • переменные окружения: PATH, PATHEXT, TEMP, RANDOM
  • set, /a, /p
  • аргументы, %0, %1, …, %9, shift
  • if, exit для обработки справки
  • errorlevel, &&, ||, &, |
  • setlocal, endlocal
  • for (для перебора файлов, без него задачу №1 не решить)

Повторение предыдущих серий

  • Пишем пятиминутку по горячим клавишам windows
  • Вспоминаем изученные команды: echo, help, cd, dir, type, find, findstr, sort, more, cls
  • Как в программу передаются параметры? Что такое «ключи» в контексте вопроса про параметры?
  • Как посмотреть справку по конкретной команде? (/? и help [command])
  • Что такое pipe?

Разбираем домашние задания:

  • echo.
  • echo:
  • echo(
  • прочие символы

    echo “Bye-bye” pause >nul

Содержание

На этой паре мы продолжим изучение некоторых аспектов работы Windows, смотря на операционную систему из консоли. Переменные окружения в GUI.

У нас есть хорошая новость. В cmd можно объявлять свои переменные. Более того, в самой Windows есть свои переменные, с которыми в cmd можно вполне прозрачно работать. Где эти переменные можно увидеть? Нужно открыть свойства компьютера - Win+Pause (Break) (у кого-то Break, у кого-то Pause, а у некоторых совсем нет нужной клавиши, таким студентам можно открыть Проводник, доTABать до «Мой компьютер», потом Shift+F10, СтрелкаВверх, Enter). Далее в левом меню видим «Advanced System Settings», в открывшемся окне находим вкладку Advanced, а там внизу кнопку «Environment Variables...». Вот мы и добрались до переменных наших операционной системы. Обращаем внимание, что переменные разбиты на две части: сверху переменные нашего пользователя, а снизу — системные, существующие для всех пользователей.

Среди переменных можно увидеть TEMP и TMP, спрашиваем, для чего они, узнаём, что в значениях переменных пути до директорий с вре́менными файлами. Поговаривают, что в некоторых версиях XP можно было удалить эти две переменные, после чего система не могла загрузиться. Студенты при желании могут поэкспериментировать со своими родными и близкими, кто еще пользуется XP. Переменная WINDIR - что это? Путь до папки с Windows.

Все переменные окружения можно отредактировать, при необходимости можно добавить свои, например, добавим нашему пользователю переменную окружения под именем London и со значением the capital of Great Britain.

Наконец, можно обратить внимание на наиболее важные для нас переменные PATH и PATHEXT. Рассмотрение этих переменных поможет нам понять, почему мы могли на прошлой паре просто написать calc или notepad и при этом открывались калькулятор или блокнот, хотя они совсем не находились в нашей директории. Итак, что представляет из себя PATH? Это список директорий, разделённый точками с запятой. Среди директорий можно увидеть, например, C:\Windows\system32 (или %SystemRoot%\system32, или %Windir%\system32) и папки разных установленных на компьютере программ.

Что представляет из себя PATHEXT? Это просто список расширений файлов, разделённый опять же точкой с запятой. Есть ли что-то общее у этих расширений? Всё перечисленное — расширения «исполняемых» файлов, иными словами, расширения файлов программ, которые наш компьютер может «запустить» (разумеется, с учетом того, что студенты сами себе в PATHEXT ничего лишнего не добавили). PATH, PATHEXT и поиск программ/команд Рассмотренные переменные PATH и PATHEXT очень важны для системы. Именно сейчас мы сможем разобраться, почему операционная система так «легко» открывает калькулятор, блокнот и прочее, когда мы в консоли или после Win+R набираем calc или notepad. Что же происходит после слова calc в консоли и нажатия на Enter? Прежде чем рассказать алгоритм работы нужно сказать, что все команды в консоли разделяются на внешние и внутренние. Отличие их состоит в том, что внутренние команды — это команды самой консоли, для них не существует никакого исполняемого файла. Например, команды echo, dir, cd, type являются внутренними, а вот find, sort, calc и notepad — внешними. Теперь у нас достаточно знаний, чтобы описать, как же система определяет, что именно сделать при корректном запуске команды в консоли.

Если набранное является внутренней командой, то она и выполняется. Система проверяет текущую директорию, есть ли в ней запрошенный файл. Если система знает, как файл запускать, то выполняется запуск. Иначе же система пытается подставлять к набранному нами имени команды расширения из PATHEXT. Как только существующий файл найден, программа запускается, а поиск прекращается. Если ранее ничего найти не смогли, то последовательно начинаем перебирать все директории из PATH, применяя действия, описанные в предыдущем шаге (вначале поиск по имени файла, потом с подстановкой расширений)

Теперь немного поэкспериментируем, чтобы закрепить описанный выше алгоритм. После пары в нашей директории, посвященной ОС либо ничего нет, либо есть файл вроде hello.cmd.

Что произойдёт, если написать в консоли hello.cmd? Ответ — запустится наш hello.cmd, ибо нет такой внутренней команды, а сам файл есть в нашей директории и является исполняемым (расширение cmd есть в PATHEXT)

Что произойдёт, если написать в консоли hello? Ответ — система, проверит, что нет такой внутренней команды и нет файла с таким именем в нашей текущей директории. После этого система начнёт подставлять все расширения из PATHEXT и найдёт файл hello.cmd, после чего его и запустит.

Создаем файл hello.exe, после чего пишем hello. Что произойдёт? Ответ: система сообщит об ошибке, ибо не сможет корректно запустить hello.exe. Система не запустит hello.cmd, так как cmd в списке PATHEXT идет после exe.

Создаем файл hello.cmd.exe, после чего пишем hello.cmd. Что произойдёт? Ответ: система запустит hello.cmd, ибо перед подстановкой расширений проверит, существует ли файл с набранным нами именем.

Создаем файл secret.txt.cmd, выводящий на экран «Hello, secret!», и пишем secret.txt. Что произойдёт? Ответ: запустится наш cmd-файл, ибо система не нашла до этого ни внутренней команды, ни файла с именем secret.txt.

Создаем secret.txt с содержимым «секретный текстовый файл» и пишем secret.txt. Что произойдёт? Ответ: в блокноте откроется наш текстовый файл. Если у студентов будут вопросы, почему открывается блокнот и можно ли заменить это поведение, говорим, что этот момент подробнее разберем ближе к концу семестра.

Создаем файл с именем echo, внутри которого cmd-скрипт, выводящий «Privet!». Что произойдёт, если написать сейчас в консоли echo? Ответ: выведет «ECHO is on.», ибо echo является внутренней командой.

Переименуем echo в echo.cmd: ren echo echo.cmd. Что сейчас произойдёт, если написать в консоли echo? Ответ: то же самое, что и в прошлом варианте.

Как запустить наш файл с echo вместо внутренней команды, если вдруг очень надо? Ответ: нужно передать либо путь до программы (.\echo или .\echo.cmd — в обоих случаях обращаем внимание на наклон слешей, именно такой используется в Windows и именно с таким нормально работает в консоли Tab), ну и другим вариантом является команда cmd <echo.cmd — здесь содержимое файла echo.cmd оказывается потоком ввода при запуске cmd. Ну или самый простой вариант — echo.cmd.

Что происходит, если написать в консоли calc? Ответ: откроется калькулятор, ибо система не найдёт внутренней команды и файла в текущей директории, соответствующего заданного имени, после начнётся перебор всех директорий из PATH, благодаря чему в конце концов найдётся файл C:\Windows\System32\calc.exe.

Как сделать так, чтобы при набирании в консоли calc калькулятор не открылся? Ответ: создать в текущей директории, например, файл calc.cmd.

Итак, если мы хотим из любой директории запускать наши любимые полезные программы, то просто добавляем в PATH свои директории с нашими программами и радуемся. При установке же других программ (в том числе на других курсах) в инсталляторе важно не бездумно нажимать «Далее»-«Далее»-«Готово», а следить за тем, какие галочки в инсталляторе поставлены и какие НЕ поставлены. Часто при установке можно автоматически добавить нужные пути в PATH, чтобы установленные программы можно было запускать из консоли без указания полного пути. Это может сильно упростить жизнь в будущем, да и такие мелочи отличают опытного пользователя от домохозяйки с яндекс-баром и амиго.

Здесь важно ответить на все появившиеся вопросы студентов, чтобы действительно было понимание, как работает система при необходимости что-то запустить.

Если возникнут вопросы: содержимое PATH при запуске консоли берется из PATH системного, из PATH пользователя и из AUTOEXEC.BAT (если включено). По крайней мере на 10ке, пользовательское значение PATH приклеивается справа к системному.

Работа с переменными окружения из консоли

С запуском программ разобрались, теперь можно вернуться обратно к переменным и работе с ними из консоли. Если в консоли набрать команду set, то увидим все переменные окружение с их значениями, если набрать set lo, то увидим все переменные окружения, имена которых начинаются с “lo”.

Для получения значения переменной в коде нужно ее имя заключить в проценты:

echo %PATH%
echo %London%

Если кто-то не увидел у себя значение Лондона, значит, либо они не сохранили значение переменных после редактирования, либо консоль была открыта ДО внесения изменений. Вывод: все значения переменных присваиваются при открытии консоли.

Помимо рассмотренных ранее переменных окружения есть, например, переменная RANDOM, не отличающаяся постоянством, что нам, собственно, от нее и надо. Эта переменная принимает значения от 0 включительно до 32767 включительно.

Важный момент с переменными. Перед запуском команды вначале в строку подставляются ВСЕ значения переменных. Пример будет рассмотрен чуть позже.

Выводить значения переменных научились, осталось понять, как же значения переменных задавать. Для этого используется команда set.

set London=nash
echo %London%
echo %LONDON%

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

Ну и важно отметить, чтобы не ставили никаких пробелов как до, так и после знака “=”, ибо все поставленное будет использовано против вас и окажется в значении переменной или в ее имени!

Что же стало с нашим лондоном? Изменилось ли его значение в системе? Если откроют новую консоль, то увидят, что Лондон остался прежним. Вывод: команда set меняет переменные окружения только для текущей консоли. Можно обсудить со студентами, для чего сделано именно так. Если у кого-то возникнет вопрос, можно ли изменить из консоли переменные окружения для всех, то отвечаем “можно”, особо пытливым можно сказать про setx, место хранения самих переменных будет рассматривать ближе к концу семестра.

Если кто-то хочет удалить переменную, то достаточно написать set London=

Если у кого-то возникнет вопрос, почему “не работает” set res=1+2, то говорим, что в справке по set могут найти нужный ключ для подсчета арифметических выражений. Обработка переданных аргументов Работать с переменными окружения научились, теперь будем знакомиться с тем, как же программа взаимодействует с внешним миром и считывает переданные ей аргументы.

Создаем cmd-файл args.cmd со следующим содержимым:

@echo off
echo %1

Запускаем: args first second 3 4 В выводе видим только first. Как увидеть second? Добавить в программу

echo %2

Пробуем вывести %0. Видим там args. Если же запускать программу как args.cmd, то вместо args видим args.cmd. Вывод - в %0 лежит имя программы, как ее запустили.

Теперь запускаем без параметров. Видим “Echo is off.”. Почему? Вспоминаем, что раньше говорили, что перед выполнением какой-либо команды вначале подставляются ВСЕ имена переменных, %1 не существует при запуске без аргументов, поэтому запускается команда echo, которая при отсутствии аргументов выводит состояние режима echo.

Теперь пробуем передать нашей программе 12 аргументов и вывести их на экран:

args 4 3 2 1 5 6 7 8 9 10 11 12

В результате, на месте 10-12 аргументов студенты увидят 40, 41 и 42. Из-за чего это произошло? Максимальный номер аргумента, до которого можно сходу достучаться из программы по имени - %9, в итоге %10, %11 и %12 заменились на значение %1 и к ним приписались 0, 1 и 2 соответственно. Если же хотим получить значение остальных аргументов, то в этом нам поможет команда shift, позволяющая 2й аргумент превратить в 1й, 3й - во 2й, 4й - в 3й и т.д. Дописываем в программу args команду shift и снова выводим наши аргументы.

С аргументами познакомились, теперь ставим следующую цель: переписать Hello.cmd так, чтобы программа здоровалась с тем, кого ей передадут в качестве аргумента. Пример запуска:

> hello.cmd Sergey
Hello, Sergey!

В результате студенты напишут что-то такое:

@echo off
echo Hello, %1

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

В ответ студенты находят команду if, после чего вместе разбираемся с ней. У команды if есть несколько режимов работы: нам могут быть интересны проверка существования файла и сравнение строк. Предлагаем вначале сравнивать не со /?, а с какой-нибудь текстовой строкой, например, если первый аргумент help, то выводим нашу справку, иначе работаем, как договаривались ранее. Как написать сравнение строк? В справке по if вокруг строк нет никаких кавычек, да и при echo мы их раньше не использовали. Поэтому второй строкой в программе пишем:

if %1==help (
   echo The program says hello to given username
   echo.
   echo %0 username
   exit /b
)

Первой строкой справки описали программу, потом привели пример запуска/ Вместо имени программы использовали рассмотренный ранее %0, чтобы избежать возможных несостыковок из-за переименования файла. Описание exit /? смотрим в справке команды exit - после выполнения данной строки консоль, в которой была запущена наша программа, не будет закрыта, и это здорово!

Запускаем. Все, вроде, работает. Но если попробуем запустить совсем без аргументов, то в консоли не увидим ничего хорошего. Нам говорят, что echo было неожиданным. Почему? Вспоминаем, что перед запуском команды вначале подставляются все переменные. Если никакие аргументы не были переданы, то вместо %1 будет пустая строка, из-за чего команда выглядит как if ==help … - корректно ли это? Нет, вот и cmd думает также. Что сделать. чтобы система вела себя корректно в таких случаях? Даем студентам придумать свои варианты. После чего говорим, что важно сделать так, чтобы слева и справа от знаков равно в любом случае что-то было, а значит, мы можем сделать вид, что имеем дело с нормальным, то есть привычным языком программирования и просто заключить и левую, и правую части в кавычки. Меняем слово help на /? и тестируем получившуюся программу.

Далее говорим, что мы как-то неправильно здороваемся, если программу запустили без параметров. Предлагаем доработать программу так, чтобы в случае запуска без аргументов мы спрашивали имя пользователя, после чего здоровались с введенным результатом. Справиться с этой задачей может set с ключом /p и еще одна проверка.

Итоговая программа:

if “%1”==”/?” (
   echo The program says hello to given username
   echo.
   echo %0 [username]
   exit /b
)
if “%1”==”” (
   set /p username=Who are you?
) else (
   set username=%1
)
echo hello, %username%

Если у кого-то возникнет вопрос, как писать комментарии, то можно сказать про два вида однострочных комментариев: слово REM в начале строки (от слова remark) или двойное двоеточие. не забываем, что мы имеем дело с командами, поэтому нельзя так просто взять и использовать в качестве комментариев привычные слэши.

Для тех, кто быстро все сделает, можно предложить усложнение к задаче: поздороваться со всеми переданными аргументами (самостоятельно познакомятся с goto). После обработки справки у них должно получиться что-то вроде:

:while
   if NOT “%1”==”” (
      echo Hello, %1
      shift
      goto:while
   ) else (
      exit /b
   )

Вопрос группе: если препод дал старосте 5 яблок, то сколько яблок сейчас у старосты? Ответ: сколько угодно, ибо неизвестно, сколько яблок у старосты было. Примерно этот же принцип работает и в консоли. Набираем echo %USERNAME% и видим, что последний пользователь, с которым мы здоровались, до сих пор здесь. Вывод: после работы программы переменные сами не исчезают и гипотетически могут попортить жизнь программе, которая будет работать после нас. А значит, во имя порядка полезно все используемые нами переменные затирать в конце работы программы. Да, мы можем для каждой переменной написать set variablename=, но если переменных много, то это будет не очень удобно, поэтому умные люди в данном случае обычно пишут в начале программы (после @echo off) команду setlocal, а в конце - endlocal. После этого все переменные, объявленные внутри нашего cmd-скрипта, будут самостоятельно исчезать после работы нашей программы.

Первую серьезную и полезную программу на cmd написали. Двигаемся дальше. Показываем студентам следующую программу wall-e.cmd:

@echo off
cd musor
del /f /q *
cd ..
echo ta-da

Что плохого в этой программе? Построчно - переходим в папку musor, молча удаляем все файлы в ней (* означает все), переходим “обратно”, радуемся. Что произойдет, если папки musor не существует? В консоль выведется ошибка, но программа продолжит работать: спокойно удалит все файлы из нашей текущей директории и после порадуется. Вывод: нужно как-то обрабатывать ошибки.

Такая возможность у нас есть. Существует специальная переменная под названием ERRORLEVEL, означает она уровень ошибки последней выполненной команды. В этом определении важно каждое слово. Из этого определения, правда, есть исключения, о которых лучше умолчать, ибо наверняка никому не пригодится и студентов грузить этим не надо. Например, echo чаще всего сохраняет старое значение %errorlevel% (выполните два раза подряд вывод errorlevel после ошибки)

Набираем в консоли cd musor, пытаясь перейти в несуществующую директорию. После выводим %errorlevel% и видим, что он не равен нулю. Переходим в существующую директорию, можно, например, cd .., после видим, что в errorlevel лежит 0. Вывод: 0 означает отсутствие ошибок при выполнении последней команды, а не 0 - какую-то ошибку. Соответственно, наша программа перед выполнением дальнейших важных команд по удалению файлов должна была убедиться, что никаких ошибок не произошло. Например, так:

if %errorlevel%==0 (
   del …
   …
)

Кавычки в коде выше можно было не ставить, так как в errorlevel всегда что-то лежит (желающие могут поэкспериментировать и убедиться).

Наша программа может состоять из нескольких критических шагов, при невыполнении которых нужно сказать что-то важное и завершить работу программы. Соответственно может возникнуть необходимость выполнять один и тот же код. Копипаст - не очень хорошее дело, и справиться с ним нам помогут метки и конструкция goto. Как это работает? Метки представляют из себя именованные строчки кода, к которым можно перейти после любой строки кода. Для создания метки в начале строки пишем двоеточие, а потом имя метки. Например,

:help
   echo The program says hello to given username
   echo.
   echo %0 [username]
   exit /b

И при проверке условия, пришел ли в качестве параметра /? вместо кучи echo будет одна команда goto:help. Но нужно не забывать, что строки программы исполняются последовательно, поэтому при размещении метки со справкой в конце программы нужно позаботиться и о том, чтобы при обычной работе программы мы в конце своей работы не вывели справку. Как бороться с этой проблемой? Перед меткой :help написать exit /b или goto:eof (от End Of File). В этот момент можно вместе поправить, например, hello.cmd.

Вообще рассматриваемый нами cmd - не очень удобный язык, соответственно, если нужно написать что-то крупное, то о программировании на cmd вы подумаете в последнюю очередь. Но если нужно сделать что-то маленькое, состоящее из 2-3 команд или из одной строки, то почему бы и не открыть консоль?!

Если в первой теме не рассмотрели пайп и команды-фильтры, то говорим, что сейчас научим любую сложную штуку писать на cmd одной строкой.

Выписываем студентам следующие 4 сочетания символов и спрашиваем, что они означают:

&
&&
|
||

Рассматриваем все по порядку. Одиночный амперсанд нужен для последовательного запуска нескольких команд. Например, если мы хотим отформатировать наш жесткий диск и сказать “привет, мир”, то просто пишем одну команду: format c:\ & echo Hello, world! Если заподозрят подвох, то можно дважды вызвать echo: echo Privet, Sasha & echo Privet, Masha. В результате, увидим две строки. Даже если в первой команде опечатаемся, все равно отработают обе команды: ceho Privet, Sasha & echo Privet, Masha. Команды, написанные через амперсанд - все равно, что команды, написанные в разных строках.

cmd1 && cmd2. Два амперсанда - логическое И, cmd2 будет выполнена после cmd1 только в том случае, когда cmd1 выполнилась без ошибок, то есть errorlevel после нее равен 0. Примеры: 
ceho Privet, Sasha && echo Privet
echo Privet, Sasha && echo Privet


cmd1 || cmd2. Два пайпа - логическое ИЛИ, cmd2 будет выполнена после cmd1 только в том случае, когда cmd1 выполнилась с ошибками, то есть errorlevel после нее не равен 0.
cd musor || echo Bad directory
cd .. || echo Bad Directory

Описание одного пайпа есть в теме 1, не будет лишним об этом еще раз вспомнить.

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

Команда for. Да, при помощи этой команды можно писать привычные циклы, но мы это делать, конечно же, не будем. Консоль нам нужна будет в основном для запуска каких-то других программ. Иногда появляются задачи, когда нужно что-то однотипное сделать для кучи разных файлов. Например, написать что-то на всех наших фотографиях, исправить кодировку в куче файлов и т.п. Здесь-то нам и пригодится команда for. Практически все, нужное нам, находится на первой же странице ее справки.

Пишем в консоли:

for %f in (*) do @echo %f

Переводим написанное на русский: для переменной %f, бегущей по списку всех файлов в нашей текущей директории, выведи на экран имя файла. При этом саму команду выводить на экран не надо. Запускаем, убеждаемся, что так оно и работает.

Вопрос: как пробежаться только по текстовым файлам?

for %f in (*.txt) do @echo %f

А по файлам, в имени которых есть слово linux?

for %f in (*linux*) do @echo %f

Важно! Если поместить данную команду в cmd-файл и попробовать ее запустить, то ничего хорошего не увидим. В командном файле нужно перед именем переменной ставить два знака процента. Ну и если команд в цикле много, то можем поступить по аналогии с if и разнести все команды по нескольким строкам, заключив все в круглые скобки:

for %%f in (*.txt) do (
   echo %%f
   echo ===
   type %%f
)

ДЗ

Если не успели написать/дописать hello.cmd, здоровающуюся с аргументом, то даем задание разобраться с if, set /p, exit /b и написать нужную нам программу

Установить дома линукс. Можно на виртуальную машину (видео на английском - https://www.youtube.com/watch?v=9DpFhTeuI)