Na FreeHostingu Endora běží desítky tisíc webů. Přidejte se ještě dnes!

Vytvořit web zdarma

Na FreeHostingu Endora běží desítky tisíc webů. Přidejte se ještě dnes!

Vytvořit web zdarma

int21h

Rozbor příkazového řádku

Řeklo by se banální téma. To je ale jenom zdání. Stejně jako minule, když jsem psal o řetězcových typech, i tady záhy zjistíte, že problematika rozhodně není triviální.
Pascal má na rozbor příkazového řádku dvě funkce:
ParamCount:byte - vrátí počet argumentů
ParamStr(n:byte):string - vrátí N-tý argument příkazového řádku

Když není argument zadán, vrací funkce ParamStr prázdný řetězec. Volání ParamStr(0), vrátí jméno EXE souboru. Jestliže je EXE spuštěn z jiného adresáře než z aktuálního (např. voláte C:TPMUJPRG anebo je program v adresáři pokrytém proměnnou PATH), tak ho vrací i s cestou. Pokud jsme ve stejném adresáři jako EXE, tak ho vrátí bez cesty.
Takže jestliže chceme zjistit jméno spuštěného programu, tak je rozumné volat
prg:=FExpand(ParamStr(0))
Dále doporučuji vyseparovat adresář. K tomu nám slouží funkce FSplit - tedy:
FSplit(prg,cesta,jmeno,koncovka)
Tímto jsme zjistili adresář, ve kterém se nachází náš exáč. Znovu připomínám, že nemusí být totožný s aktuálním adresářem. Pokud váš program pracuje s jinými soubory, tak je nanejvýš vhodné udávat i zjištěnou cestu. Tedy nikoliv Assign(f,s) ale Assign(f,cesta+s) Takto uživateli umožníte spouštět váš program ze vzdáleného adresáře.
Zpět ale k parametrům příkazového řádku.
Funkce ParamCount a ParamStr mají dva zásadní nedostatky. Prvním je, že jako oddělovače jednotlivých argumentů chápe znak mezeru. Už ale neodliší, jestli mezi prvním a druhým argumentem je jedna mezera anebo deset mezer. Většinou je to samozřejmě jedno, ale v některých případech to může být toto chování na závadu. Pro tyto účely musíme získat přesnou kopii příkazového řádku. DOS na to poskytuje jeden anebo dva mechanizmy - podle verze.
První mechanismus funguje ve všech verzích DOSu - zkopírování řetězce z PSP (prefix segmentu programu)
PSP je tabulka v paměti, kterou DOS vytváří pro každý spuštěný program. Na offsetu 80h je uložena délka příkazového řádku. A od offsetu 81h do FFh jsou znaky příkazového řádku. Pozor - součástí příkazového řádku není jméno EXE. Příkazy přesměrování vstupu a výstupu se tu také neobjeví. Jinak je to přesná kopie včetně všech mezer.
(volání PROGRAM param1 param2 > vystup.txt vyprodukuje param1 param2)
Otázka tedy zní: "Jak se dostat k PSP?"
V turbo pascalu velice jednoduše. TP definuje proměnnou PrefixSeg, což je ukazatel právě na PSP.
Procedure VratPrRadku:string;
var s:^string;
begin
s:=Ptr(PrefixSeg,$80);
VratPrRadku:=s^;
end;  
Jednoduché že?

Možná jste si povšimli, že naznačuji, že ve Freepascalu je to poněkud složitější než v TP. Přesně tak. V prvé řadě Freepascal nedefinuje proměnnou PrefixSeg. Segmentovou adresu PSP můžeme nicméně zjistit funkcí INT21h/AH=62. Jenže bacha na jednu věc. Vy to nemůžete napsat přímo. NELZE udělat toto:

var PrefixSeg:word;
begin
asm
mov ah,62h;int 21h;mov PrefixSeg,bx
end;
...
end;  

Uvědomme si, že DPMI program není ve všech aspektech pravý DOSový program. DPMI program není spouštěn DOSem, nýbrž DPMI správcem. Ten pracuje jinak a PSP má jinou. Takže?
Ve čvrtém čísle jsem psal o volání funkcí z DPMI prostředí. Píšu tam, že pokud je návrat funkce uložen v bufferu, je místo přímého volání nutno použít emulaci pomocí služeb DPMI, a že pokud je návrat funkce jenom v registrech, tak přímo volat lze. Nu - tak tohle je zrovna výjimka. Návratová hodnota je jenom v registru, ale funkci volat přímo z výše popsaného důvodu nelze. DPMI nám ale pomůže a službu emuluje.
Pro FP bude tedy celá funkce vypadat takto:
{v uses musi byt uvedena jednotka Go32}
Procedure VratPrRadku:string;
var t:string;
    w:word;
    r:registers;
begin
r.ah:=$62;MsDos(r);w:=r.bx;       {ziskani segmentu PSP}
DOSmemGet(w,$80,t,$ff-$80+1);     {kopirovani z PSP do programu}
VratPrRadku:=t;
end;  

Výborně. Právě jsme ovládli první metodu. Psal jsem ale, že metody máme dvě. Jaká je druhá metoda?
Otázka v prvé řadě stojí: "Proč se zabývat nějakou druhou metodou?"
PSP totiž udrží jenom řetězec do délky 127 znaků. Pokud očekáváme delší, tak máme smůlu.
Právě proto některé DOSy (MS-DOS 7.10, FreeDOS, různé nestandardní shelly...) mají proměnnou prostředí CMDLINE. Sem DOS zkopíruje celou příkazovou řádku, a to i pokud je delší než 127 znaků. Narozdíl od metody s PSP je tu obsaženo i jméno spuštěného EXE. Jestliže chceme dosáhnout shodného chování s předchozí metodou, musíme jméno EXE odstranit. Jak odstranit jméno EXE souboru?
Kdo tvrdí, že stačí najít první mezeru a všechno až do této mezery smazat, NEMÁ PRAVDU. Uvědomte si, že názvy souborů v LFN (dlouhá jména souborů) mohou obsahovat i mezeru. V takovém případě je celý název souboru uzavřen do uvozovek.
Zkrátka: tady máte kompletní verzi funkce VratPrRadku, která zná obě metody a pracuje v TP i FP.
Function VratPrRadku:string;
Var s:pstring;
    t:string;
    w:word;
    r:registers;
    b:byte;
begin
t:=GetEnv('CMDLINE');
if t<>'' then           {existuje promenna CMDLINE?}
   begin
   if t[1]='"' then     {jmeno souboru je v LFN a je v nem mezera?}
      begin
      delete(t,1,1);    {odstran prvni uvozovky}
      b:=Pos('"',t);    {najdi dalsi}
      delete(t,1,b);    {a vsechno az k nim smaz}
      end
      else begin
      b:=Pos(' ',t);    {najdi prvni mezeru}
      delete(t,1,b);    {vsechno az k ni smaz}
      end;
   VratPrRadku:=t;
   end
   else begin
   {$IFDEF FPC}
   r.ah:=$62;MsDos(r);w:=r.bx;       {a mame segment PSP}
   DOSmemGet(w,$80,t,$ff-$80+1);
   VratPrRadku:=t;
   {$ELSE}
   s:=Ptr(PrefixSeg,$80);
   VratPrRadku:=s^;
   {$ENDIF}
   end;
end;  

Báječné. Právě jsme vyřešili problém vícečetných mezer v řádku. Zbývá druhý problém. Už jsme ho naťukli. Názvy souborů s mezerou se uzavírají do uvozovek a pak jsou chápány jako jediný argument.
V DOSu je například přípustný tento příkaz: md "Moje fotky" - vytvoří se adresář "Moje fotky". Rovněž například síťová utilita send z Netware má tento tvar: send "nejaky klidne dosti dlouhy text"
Toto pascalovské ParamCount/ParamStr neznají.
Postup je tedy nabíledni: pomocí funkce VratPrRadku zjistit příkazovou řádku a pak ji vlastními silami analyzovat. Myslím, že byste to vymysleli sami, ale pro vaše pohodlí vám dám tyto funkce už hotové:
function LFNParamCount(s:string):integer;
   { Replaces standard "ParamCount" function }
   var b,o:boolean;
       c,x:byte;
       t:string;


   begin
   if s='' then begin LFNParamCount:=0;Exit;end;
   b:=false;
   o:=false;
   t:='';
   x:=1;
   for c:=1 to Length(s) do
       if s[c]='"' then begin if b=false then inc(x);b:=not b;end else
       if b=false then
          if s[c]=' ' then o:=false
                      else if o=false then
                              begin
                              inc(x);
                              o:=true;
                              end;


   if b=true then begin LFNParamCount:=-1;Exit;end;
   LFNParamCount:=x-1;
   end;


function LFNParamStr(s:string;n:byte):string;
   function Mid (S:string; B,E:longint):string;
   { Vraci cast podretezce S pocinaje B-tym znakem a E-tym konce}
   var i, N:longint;
       Pom :string;
   begin
   N:=Length(S);
   Pom:='';
   if B>0 then
      for i:=B to E do
          if i<=N then Pom:=Pom+S[i];
   Mid:=Pom;
   end;


   function Search (Text:ansistring;S:string;Pos:longint):longint;
   { Funkce hleda string S v textu Text od pozice Pos (vcetne)}
   var i, D, N: longint;
   begin {Search}
   D:=Length(S);       {delka hledaneho retezce}
   N:=Length(Text);    {delka textu}
   i:=Pos;
   while i<=N-D+1 do
      begin {while}
      if  S = Mid (Text, i, i + D - 1) then begin Search:=i;exit;end;
      inc (i);
      end;  {while}
   Search:=0;
   end; {Search}


   var b,o:boolean;
       c,d:byte;
       x:shortint;
       t:string;
   begin
   if s='' then begin LFNParamStr:='';Exit;end;


   b:=false;
   o:=false;
   t:='';
   x:=0;
   for c:=1 to Length(s) do
       if s[c]='"' then
          begin
          if b=false then
             begin
             inc(x);
             if x=n then
                begin
                d:=Search(s,'"',c+1);
                if d=0 then begin LFNParamStr:='';Exit;end;
                LFNParamStr:=Mid(s,c+1,d-1);
                Exit;
                end;
             end;
          b:=not b;
          end else
          if b=false then
             if s[c]=' ' then o:=false
             else if o=false then
                     begin
                     inc(x);
                     if x=n then
                        begin
                        d:=Search(s,' ',c);
                        if d=0 then d:=Length(s)+1;
                        LFNParamStr:=Mid(s,c,d-1);
                        Exit;
                        end;
                     o:=true;
                     end;
   LFNParamStr:='';
   end;  

Funkce LFNParamStr by se zřejmě dala zjednodušit, ale dávám vám ji prostě tak, jak jsem ji napsal.
Na závěr několik rad starého dosařského pardála:
1) určitě máte nějakou svoji unitku, kterou používáte prakticky ve všech svých programech. Doporučuji vám včlenit do ní uvedené funkce a v inicializační části jednotky volat funkci VratPrRadku a zjištěný řetězec uložit do nějaké globální proměnné jednotky (deklarované v sekci interface).
2) rovněž si do proměnných uložte adresář s EXE a jméno EXE bez koncovky.
3) jestliže váš program přijímá jako argument specifikaci souboru ve hvězdičkové konvenci (PROGRAM *.txt), tak je dobré, aby podporoval i fajllisty (PROGRAM @filelist.lst) Jestli jste maximalisti, tak to navíc udělejte tak, aby rovněž ve fajllistech mohly být uvedeny masky ve hvězdičkové konvenci.

A to je vše.
DOS-u-akbar!
2006-12-06 | Laaca