Командные параметры и переменные среды
В случае если команда не передавалась бы интерпретатору DOS, а выполнялась нами самостоятельно, то оказалось бы: чтобы запустить любую программу из-под shell.com, потребовалось бы предварительно переходить в каталог с этой программой или вводить ее с полным путем. Дело в том, что COMMAND.COM при запуске файла ищет его по очереди в каждом из каталогов, указанных в переменной среды PATH. DOS создает копию всех переменных среды (так называемое окружение DOS) для каждого запускаемого процесса. Сегментный адрес копии окружения для текущего процесса располагается в PSP по смещению 2Ch. В этом сегменте записаны все переменные подряд в форме ASCIZ-строк вида "COMSPEC=C:/WINDOWS/COMMAND.COM",0. По окончании последней строки стоит дополнительный нулевой байт, затем слово (обычно 1) — количество дополнительных строк окружения, а потом — дополнительные строки. Первая дополнительная строка — всегда полный путь и имя текущей программы — также в форме ASCIZ-строки. При запуске новой программы с помощью функции 4Bh можно создать полностью новое окружение и передать его сегментный адрес запускаемой программе в блоке ЕРВ или просто указать 0, позволив DOS скопировать окружение текущей программы.
Кроме того, в предыдущем примере мы передавали запускаемой программе (command.com) параметры (/с команда), но пока не объяснили, как программа может определить, что за параметры были переданы ей при старте. При запуске программы DOS помещает всю командную строку (включая последний символ 0Dh) в блок PSP запущенной программы по смещению 81h и ее длину в байт 80h (таким образом, длина командной строки не может быть больше 7Eh (126) символов). Под Windows 95 и 4DOS, если командная строка превышает эти размеры, байт PSP:0080h (длина) устанавливается в 7Fh, в последний байт PSP (PSP:00FFh) записывается 0Dh, первые 126 байт командной строки размещаются в PSP, а вся строка целиком — в переменной среды CMDLINE.
; cat.asm ; копирует объединенное содержимое всех файлов, указанных в командной строке, ; в стандартный вывод. Можно как указывать список файлов, так и использовать ; маски (символы "*" и "?") в одном или нескольких параметрах, ; например: ; cat header *.txt footer > all-texts помещает содержимое файла ; header, всех файлов с расширением .txt в текущем каталоге и файла ; footer - в файл all-texts ; длинные имена файлов не используются, ошибки игнорируются ; .model tiny .code org 80h ; по смещению 80h от начала PSP находятся: cmd_length db ? ; длина командной строки cmd_line db ? ; и сама командная строка org 100h ; начало СОМ-программы - 100h от начала PSP start: cld ; для команд строковой обработки mov bp,sp ; сохранить текущую вершину стека в ВР mov cl,cmd_length cmp cl,1 ; если командная строка пуста - jle show_usage ; вывести информацию о программе и выйти
mov ah,1Ah ; функция DOS 1Ah mov dx,offset DTA int 21h ; переместить DTA (по умолчанию она совпадает ; с командной строкой PSP)
; преобразовать список параметров в PSP:81h следующим образом: ; каждый параметр заканчивается нулем (ASCIZ-строка) ; адреса всех параметров помещаются в стек в порядке обнаружения ; в переменную argc записывается число параметров
mov cx,-1 ; для команд работы со строками mov di,offset cmd_line ; начало командной строки в ES:DI
find_param: mov al,' ' ; искать первый символ, repz scasb ; не являющийся пробелом dec di ; DI - адрес начала очередного параметра push di ; поместить его в стек inc word ptr argc ; и увеличить argc на один mov si,di ; SI = DI для следующей команды lodsb
scan_params: lodsb ; прочитать следующий символ из параметра, cmp al,0Dh ; если это 0Dh - это был последний параметр je params_ended ; и он кончился, cmp al,20h ; если это 20h - этот параметр кончился, jne scan_params ; но могут быть еще
dec si ; SI - первый байт после конца параметра mov byte ptr [si],0 ; записать в него 0 mov di,si ; DI = SI для команды scasb inc di ; DI - следующий после нуля символ jmp short find_param ; продолжить разбор командной строки
params_ended: dec si ; SI - первый байт после конца последнего mov byte ptr [si],0 ; параметра - записать в него 0
; каждый параметр воспринимается как файл или маска для поиска файлов, ; все найденные файлы выводятся на stdout. Если параметр - не имя файла, ; то ошибка игнорируется
mov si,word ptr argc ; SI - число оставшихся параметров
next_file_from_param: dec bp dec bp ; BP - адрес следующего адреса параметра dec si ; уменьшить число оставшихся параметров, js no_more_params ; если оно стало отрицательным - все mov dx,word ptr [bp] ; DS:DX - адрес очередного параметра mov ah,4Eh ; Функция DOS 4Eh mov cx,0100111b ; искать все файлы, кроме каталогов ; и меток тома int 21h ; найти первый файл, jc next_file_from_param ; если произошла ошибка - файла нет call output_found ; вывести найденный файл на stdout
find_next: mov ah,4Fh ; Функция DOS 4Fh mov dx,offset DTA ; адрес нашей области DTA int 21h ; найти следующий файл, jc next_file_from_param ; если ошибка - файлы кончились call output_found ; вывести найденный файл на stdout jmp short find_next ; продолжить поиск файлов
no_more_params: mov ax,word ptr argc shl ax,1 add sp,ax ; удалить из стека 2 * argc байт ; (то есть весь список адресов ; параметров командной строки) ret ; конец программы
; процедура show_usage ; выводит информацию о программе
show_usage: mov ah,9 ; Функция DOS 09h mov dx,offset usage int 21h ; вывести строку на экран ret ; выход из процедуры
; процедура output_found ; выводит в stdout файл, имя которого находится в области DTA
output_found: mov dx,offset DTA+1Eh ; адрес ASCIZ-строки с именем файла mov ax,3D00h ; Функция DOS 3Dh int 21h ; открыть файл (al = 0 - только на чтение), jc skip_file ; если ошибка - не трогать этот файл mov bx,ax ; идентификатор файла - в ВХ mov di,1 ; DI будет хранить идентификатор STDOUT
do_output: mov cx,1024 ; размер блока для чтения файла mov dx,offset DTA+45 ; буфер для чтения/записи располагается ; за концом DTA mov ah,3Fh ; Функция DOS 3Fh int 21h ; прочитать 1024 из файла, jc file_done ; если ошибка - закрыть файл mov cx,ax ; число реально прочитанных байт в СХ, jcxz file_done ; если это не ноль - закрыть файл mov ah,40h ; Функция DOS 40h xchg bx,di ; BX = 1 - устройство STDOUT int 21h ; вывод прочитанного числа байт в STDOUT xchg di,bx ; вернуть идентификатор файла в ВХ, jc file_done ; если ошибка - закрыть файл jmp short do_output ; продолжить вывод файла
file_done: mov ah,3Eh ; Функция DOS 3Eh int 21h ; закрыть файл
skip_file: ret ; конец процедуры output_found
usage db "cat.com v1.0",0Dh,0Ah db "объединяет и выводит файлы на stdout",0Dh,0Ah db "использование: cat имя_файла, ...",0Dh,0Ah db "(имя файла может содержать маски * и ?)",0Dh,0Ah,'$' argc dw 0 ; число параметров (должен быть 0 при старте программы!)
DTA: ; область DTA начинается сразу за концом файла, ; а сразу за областью DTA начинается ; 1024-байтный буфер для чтения файла end start
Размер блока для чтения файла можно значительно увеличить, но в этом случае почти наверняка потребуется проследить за объемом памяти, доступным для программы.
Содержание раздела