0887 - MS-Dos - Od środka cz.4

W cyklu o MS-DOS publikujemy informacje pozwalające efektywnie korzystać z usług tego systemu. W tym odcinku omówione zostaną wywołania funkcji uniwersalnych, związanych z obsługą plików dyskowych. Istnieją one wprawdzie we wszystkich wersjach systemu, jednak umożliwiają dostęp tylko do plików znajdujących się aktualnie wybranym katalogu.

Blok sterujący pliku (FCB - ang. File Control Block) jest 44-bajtową strukturą opisującą używany plik (Rys.5, Mikroklan 7/87). Programista ma nie tylko dostęp do FCB, ale i niemal pełną nad nim kontrolę. Zarządzanie plikami za pomocą bloków sterujących jest charakterystyczne dla funkcji uniwersalnych (przejętych z systemu CP/M). Funkcje rozszerzone (przejęte z systemu UNIX) nie korzystają z FCB, używając zamiast niego numeru identyfikacyjnego pliku. Czynności, które przy pracy z funkcjami uniwersalnymi musi wykonywać programista, w przypadku funkcji rozszerzonych wykonuje system operacyjny.

FCB składa się z dwóch części : części głównej (37 bajtów) i położonego przed nią rozszerzenia (7-bajtów). Przy opisie FCB operuje się przesunięciami liczonymi względem adresu FCB, czyli początkowego adresu części głównej. Przesunięcia bajtów tworzących rozszerzenie FCB są więc ujemne. Rozszerzenie wykorzystywane jest tylko wtedy, gdy plik ma niekonwencjonalne atrybuty, takie jak plik systemowy, czy ukryty. Jednakże zawsze musimy rezerwować miejsce na te 7 bajtów, bowiem niezależnie od tego czy rozszerzenie jest wykorzystane czy nie, informacja o tym zawarta jest w nim samym (przesunięcie -7). Wartość FFh świadczy o obecności rozszerzenia, wartość 00h - o jego braku. W blokach rozszerzonych, bajt o przesunięciu -1 zawiera bity atrybutów pliku.

Blok sterujący może być otwarty lub nie. Blok nie otwarty jest niepełny, składa się z bajtów o przesunięciu od 0 (lub -7 w przypadku bloku rozszerzonego) do 0Fh i zawiera informacje wpisywane przez programistę przez otwarciem pliku (funkcja 0Fh). Po otwarciu pliku blok sterujący zajmuje bajty aż do przesunięcia 36 (24h) włącznie. System operacyjny po załadowaniu programu do pamięci automatycznie generuje dwa nie rozszerzone i nie otwarte bloki sterujące, zajmujące po 16 bajtów - przesunięcia 5Ch ... 6Bh i 6Ch ... 7Bh względem początku PSP (Rys.4, Mikroklan 7/87). Odpowiadają one plikom, których nazwy podano za nazwą programu w poleceniu ładowania (dzieje się tak tylko wtedy, gdy nazwy te rzeczywiście wystąpiły). Taki mechanizm umożliwia łatwy dostęp do plików, których nazwy są parametrami poleceń zapamiętanych na dysku.

Po otwarciu pierwszego z dwóch plików odpowiadający mu FCB ulegnie powiększeniu (bajty o przesunięciu 5Ch ... 81h względem początku PSP) i tym samym zniszczy informacje znajdujące się w drugim FCB. Aby można było korzystać z informacji o drugim pliku, należy więc przed otwarciem pierwszego pliku przepisać drugi FCB w inne miejsce pamięci. Podobnie, przy operacji swobodnego dostępu do pierwszego pliku, obszar, w który należy wpisać numer rekordu (przesunięcie 21h ... 24h względem pierwszego FCB, czyli 7Eh ... 81h względem początku PSP), pokryje się częściowo z obszarem wybranym przez system na bufor transmisji dyskowych DTA (ang. Disk Transfer Area), o przesunięciu 80h ... 100h względem początku PSP. Przed operacją swobodnego dostępu do pierwszego pliku, należy więc zmienić położenie bufora DTA.

Zestawienie funkcji uniwersalnych

Funkcja 0Dh - zeruje napęd dysków i opróżnia wewnątrzsystemowe bufory pamięciowe plików, ale nie zamyka plików. Dlatego prawidłowe długości plików zostaną zapisane w katalogu na dysku tylko wtedy, gdy najpierw dla każdego otwartego i zmienianego pliku zostanie wywołana funkcja 10h lub 3Eh (zamknięcie pliku).

Funkcja 0Eh - wybiera aktualny napęd dysków (ang. default drive), to znaczy określa literę, którą system przyjmuje jako symbol napędu dysków, jeśli nazwa pliku zostanie podana bez symbolu napędu. Numer napędu należy podać w rejestrze DL, przy czym 0 odpowiada napędowi A:, 1 - napędowi B:, itd. Po zakończeniu realizacji funkcji w rejestrze AL zapisana jest liczba przyłączonych napędów dysków.

Funkcja 0Fh - otwiera plik wskazany przez FCB. Adres FCB należy podać w rejestrach DS (segment) i DX (przemieszczenie w ramach segmentu). Plik musi już istnieć. W poszczególnych bajtach FCB muszą być podane następujące informacje :

  • -7h : FFH, gdy FCB jest rozszerzony
  • -6h ... -2h : wszystkie równe 0
  • -1h : atrybuty pliku (gdy pod -7h jest FFh)
  • 0h : "specjalny" numer napędu (0 - napęd aktualny, 1 - napęd A, 2 - napęd B, itd.)
  • 1h ... 8h : nazwa pliku lub urządzenia
  • 9h ... Bh : rozszerzenie nazwy pliku (jeśli występuje).

Wynik operacji przekazywany jest do rejestru AL: 0 oznacza pomyślne otwarcie pliku, zaś FFh, że operacja nie została wykonana poprawnie. W przypadku otwarcia pliku system operacyjny zmienia następujące pola FCB :

  • 00h : konkretny numer napędu
  • 0Eh ... 0Fh : długość rekordu = 80h
  • 10h ... 13h : wielkość pliku w bajtach
  • 14h ... 15h : data ostatniej zmiany w pliku
  • 16h ... 1Fh : informacje dla DOS.

Podaną w zerowym bajcie FCB liczbę 0 (napęd aktualny) system zamienia na konkretną liczbę odpowiadającą napędowi aktualnemu (1 - napęd A:, 2 - napęd B:, itd.). Ponadto otwarcie pliku powoduje wpisanie długości rekordu równej 128 (80h) bajtów. Przed dostępem do pliku można tę wielkość zmienić.

Funkcja 10h - zamyka plik o FCB wskazywanym przez rejestry DS:DX. FCB musi zawierać te same informacje, co w przypadku otwierania pliku. Wynik operacji (0 - sukces, FFh - błąd realizacji) wpisywany jest do rejestru AL. DOS porównuje dane określające plik w skorowidzu na dysku i w FCB - w przypadku ich niezgodności (świadczy to o zmianie dyskietek w napędzie) nie zamyka pliku. Jeśli nie zmieniono dyskietki i system odnalazł plik, aktualizowana jest odpowiednia pozycja w katalogu dysku.

Funkcja 11h - poszukuje pierwszej nazwy pliku, która pasuje do podanego wzorca. Rejestry DS:DX wskazują na FCB, w którym w odpowiednich polach należy określić nazwę pliku i jej rozszerzenie. Można przy tym używać symboli wieloznacznych (ang. wild card). Gwiazdka zastępuje dowolny ciąg znaków, a znak zapytania dowolny pojedynczy znak. W miejsce podanej nazwy wieloznacznej wpisywana jest odnaleziona, pasująca do niej nazwa pliku, zaś w rejestrze AL umieszczona jest wartość 0 (jeśli nazwa nie zostanie odnaleziona system wpisuje do rejestru AL wartość FFh).

W zarezerwowanych dla siebie polach FCB system umieszcza informacje, które może wykorzystać w przypadku wywołania funkcji 12h. Informacje te są jednak niszczone przez każde inne wywołanie funkcji korzystającej z FCB. Gdy wywołania funkcji 11h i 12h są rozdzielone wywołaniami innych funkcji, należy przepisać zawartość FCB w inne miejsce pamięci.

W przypadku rozszerzonego FCB można określić atrybuty poszukiwanego pliku. Poszukiwania przeprowadzane są wtedy zgodnie z następującymi rezultatami : jeśli bajt atrybutów zawiera kombinację bitów oznaczających plik ukryty, systemowy lub będący katalogiem, uwzględniane są pliki zwykłe o podanych atrybutach. Jeśli bajt atrybutów określa etykietę dysku (ang. volume label), poszukiwana jest tylko etykieta. W wersjach systemu wcześniejszych od 2.0 nie wolno poszukiwać pozycji będących skorowidzami lub etykietami. We wszystkich wersjach systemu bity atrybutów oznaczające plik archiwalny lub plik tylko do odczytu nie mogą stanowić kryterium poszukiwań.

Funkcja 12h - poszukuje następnej nazwy pliku zgodnej ze wzorcem podanym wcześniej przy wywołaniu funkcji 11h. Przy poszukiwaniu należy najpierw wywołać funkcję 11h, a następnie wielokrotnie wywoływać funkcję 12h.

Funkcja 13h - usuwa z katalogu plik określony w FCB wskazywanym przez DS:DX. Po powrocie z wywołania funkcji wynik operacji znajduje się w rejestrze AL : 0 oznacza, że plik o podanej nazwie istniał i został usunięty, zaś FFh - że nie było takiego pliku.

Funkcja 14h - odczytuje w sposób sekwencyjny rekord z pliku. Przed wywołaniem rejestry DS:DX wskazują FCB, w którym oprócz nazwy i rozszerzenia pliku musi być wpisany aktualny numer bloku (pozycje CH i DH) oraz aktualny numer rekordu w ramach bloku (pozycja 20h). Wpisany musi też być rozmiar rekordu (porównaj opis funkcji 0Fh). System po każdym wywołaniu funkcji 14h uaktualnia w FCB numer bloku i numer rekordu w bloku tak, by wskazywały one na następny rekord w pliku. Kolejne wywołania funkcji 14h będą więc powodowały odczytywanie kolejnych rekordów pliku, czyli dostęp sekwencyjny.

W przypadku pomyślnego odczytu system umieszcza w rejestrze AL wartość 0 oraz wpisuje odczytane dane do bufora DTA (porównaj opis funkcji 1Ah). Inna wartość w rejestrze AL świadczy, że operacja nie została wykonana:

  1. system napotkał koniec pliku; nie ma już więcej danych do odczytu,
  2. dane mogłyby być odczytane, ale obszar DTA jest zbyt mały (wykracza poza segment),
  3. po wprowadzeniu pewnej liczby bajtów wykryty został znacznik końca pliku; pozostała część bufora wypełniona jest zerami.

Funkcja 15h - zapisuje rekord do pliku w sposób sekwencyjny. Jest to odwrotność funkcji 14h. Parametry wejściowe powinny być identyczne jak dla funkcji 14h, a ponadto w DTA muszą się znajdować dane przeznaczone do zapisania na dysku. Numer bloku i rekordu w bloku też jest tu uaktualniany. Liczba przekazywana w rejestrze AL po zakończeniu operacji oznacza : 0 - zapis bez błędu, 1 - brak miejsca na dysku, 2 - za mały obszar DTA.

Funkcja 15h zapisuje rekord tylko logicznie. System buforuje bowiem dane przesyłane na dysk i dokonuje fizycznej transmisji dopiero w chwili zapełnienia wewnętrznego bufora o wielkości odpowiadającej sektorowi na dysku. W przypadku nienormalnego zakończenia programu przed zamknięciem pliku można utracić dane - wówczas nie tylko nie będą one uwzględnione w długości pliku zapisanej w katalogu, ale na dodatek danych wchodzących w skład ostatnio tworzonego sektora może w ogóle nie być na dysku.

Uwaga : system nie pozwoli wprawdzie bez użycia rozszerzonego FCB skasować pliku z atrybutem "tylko do odczytu", nie uchroni jednak pliku przed zapisem do niego rekordu przy użyciu nierozszerzonego FCB.

Funkcja 16h - tworzy i otwiera plik. Najpierw sprawdzany jest katalog. Jeśli plik o nazwie i rozszerzeniu podanych w FCB wskazywanym przez rejestry DS:DX już istnieje, to jest on otwierany, jeśli zaś jeszcze nie istnieje, to znajdowana jest wolna pozycja w katalogu, plik jest tworzony i otwierany. W obu przypadkach długość pliku w FCB jest ustawiana na 0. Jest to równoznaczne z utratą wszelkich zapisanych dotychczas w pliku informacji. Dlatego funkcja ta wywoływana jest na ogół przed zapisem danych do nowego pliku. Przed odczytem danych z istniejącego pliku używa się funkcji 0Fh, która ustawia rzeczywistą długość pliku w FCB.

Funkcja 17h - zmienia nazwę pliku. Dotychczasowe parametry pliku powinny być podane w FCB wskazywanym przez DS:DX (numer napędu, nazwa i rozszerzenie). Nową nazwę należy wpisać począwszy od adresu o przemieszczeniu 10h względem początku FCB (tam gdzie system zwykle wpisuje wielkość pliku). Po powrocie z wywołania liczba w rejestrze AL oznacza : 0 - operacja wykonana prawidłowo, FFh - nie znaleziono pliku do przemianowania lub użyta nowa nazwa już istnieje. Jeśli nowa nazwa zawiera symbole wieloznaczne (* lub ?), to w ich miejsce podstawiane są odpowiednie znaki ze starej nazwy.

Funkcja 18h - (podobnie jak funkcje od 1Dh do 20h) jest używana przez DOS dla jego własnych, wewnętrznych potrzeb (oficjalnie nie istnieje). Chociaż znane są pewne cechy tych funkcji, to ich wykorzystywanie jest ryzykowne - w przyszłych wersjach systemu funkcji tych może nie być.

Funkcja 19h - podaje numer napędu będącego w danej chwili napędem aktualnym. Numer podawany jest w rejestrze AL : 0 oznacza napęd A:, 1 - napęd B:, itd.

Funkcja 1Ah - ustala adres bufora transmisji dyskowych (DTA). Adres ten podaje się w rejestrach DS:DX. System po inicjalizacji przeznacza na ten bufor 128 bajtów pamięci począwszy od adresu o przemieszczeniu 80h względem początku PSP; za pomocą funkcji 1Ah można jednak zmieniać adres bufora.

Funkcja 1Bh - podaje informacje o sposobie organizacji informacji zapisanych na dyskietce znajdujących się w aktualnym napędzie (funkcja 1Ch podaje te same informacje dla dowolnego napędu). Po powrocie z wywołania funkcji dostępne są następujące informacje :

  • DS:BX - wskaźnik do bajtu identyfikującego format zapisu (bajtu identyfikacyjnego FAT - tabeli alokacji plików, która zostanie omówiona w następnym odcinku),
  • CX - wielkość sektora (w bajtach, np. 512),
  • AL - liczba sektorów przypadająca na jednostkę alokacji czyli grono (ang. cluster); dla dyskietek jednostronnych wynosi ona 1, dla dwustronnych - 2, dla 10-megabajtowego sztywnego dysku - 8,
  • DX - liczba jednostek alokacji na całym dysku.

Uwaga : wywołanie tej funkcji powoduje zmianę wartości rejestru DS (gdyż bajt identyfikacyjny FAT na pewno nie znajduje się w realizowanym programie). Dlatego przed jej wywołaniem na ogół należy wykonać rozkaz PUSH DS, a po powrocie - POP DS.

Funkcja 1Ch - podaje te same informacje, co funkcja 1Bh, ale dla napędu o numerze specjalnym podanym w DL (0 oznacza napęd aktualny, 1 - napęd A:, 2 - napęd B:, itd.).

Funkcja 21h - czyta wskazany rekord z pliku (dostęp swobodny). Plik określany jest w FCB wskazywanym przez DS:DX, zaś numer rekordu należy umieścić w czterobajtowym polu o przemieszczeniu 21h względem początku FCB (numer rekordu zapisuje się w postaci liczby 32-bitowej), przy czym bajty zapisywane są w kolejności od najmniej do najbardziej znaczącego. Po powrocie z wywołania funkcji numer ten nie ulega zmianie, więc przed każdym wywołaniem funkcji 21h należy nadawać mu odpowiednią wartość. O wyniku operacji świadczy liczba w rejestrze AL, która ma takie samo znaczenie, jak przy odczycie sekwencyjnym (patrz opis funkcji 14h). Dane wpisywane są do bufora DTA.

Funkcja 22h - zapisuje wskazany rekord do pliku (dostęp swobodny). Wymaga podania takich samych informacji jak funkcja 21h. O wyniku operacji świadczy zawartość rejestru AL, która ma takie samo znaczenie jak w przypadku funkcji 15h. Dane pobierane są z bufora DTA. Podobnie jak przy zapisie sekwencyjnym (funkcja 15h), system nie zabezpiecza przed zapisem pliku tylko do odczytu otworzonego z użyciem nierozszerzonego FCB.

Funkcja 23h - podaje wielkość pliku określonego w FCB wskazywanym przez rejestry DS:DX. Przed wywołaniem plik ten nie może być otwarty, zaś w FCB musi być wpisana wielkość rekordu (w bajtach) i nazwa. Można używać nazw wieloznacznych i rozszerzonego FCB - znaleziony będzie wtedy pierwszy plik zgodny ze wzorcem i atrybutami, podobnie jak w funkcji 11h. Jeśli operacja zostanie prawidłowo wykonana, rejestr AL, będzie zawierał 0, zaś do FCB zostanie wpisana wielkość pliku (wyrażona w rekordach). Jeśli podany zbiór nie zostanie znaleziony, a AL umieszczona będzie wartość FFh.

Funkcja 24h - wpisuje do FCB numer rekordu przy dostępie swobodnym obliczony na podstawie numeru bloku i numeru rekordu w ramach bloku stosowanych przy dostępie sekwencyjnym. Przed wywołaniem funkcji rejestry DS:DX muszą wskazywać na FCB otwartego pliku.


Działanie omówionych funkcji ilustrują dwa krótkie programy. Pierwszy usuwa wszystkie pliki typu .BAK, a więc wykonuje to samo, co można by uzyskać wydając polecenie DEL *.BAK. Drugi program nadaje się do praktycznego wykorzystania : przekształca plik tekstowy uzyskany w czasie pracy z procesorem tekstu WordStar w zwykły plik ASCII i pozwala obejrzeć go (jak również dowolny inny plik ASCII) na ekranie.

 NAME  DELETE_BAK

 ; Program przeszukuje aktualny skorowidz i usuwa wszystkie
 ; pliki z rozszerzeniem .BAK

                               ; funkcje DOS 
 OUT_STRING     EQU  09h       ; wyświetlenie ciągu znaków 
 SEARCH_FIRST   EQU  11h       ; poszukiwanie pierwszego pliku
 SEARCH_NEXT    EQU  12h       ; poszukiwanie następnego pliku
 DELETE         EQU  13h       ; usunięcie pliku
 SET_DTA        EQU  1Ah       ; inicjacja nowego bufora dyskowego
 TERMINATE      EQU  4Ch       ; zakończenie programu

 DOS            EQU  21h       ; przerwanie wywołań funkcji

                     ; kody znaków
 CR  EQU  0Dh        ; return (enter)
 LF  EQU  0Ah        ; nowy wiersz

 STACK SEGMENT STACK
   DW  20  DUP (?)
 STACK ENDS

 CODE SEGMENT 'CODE'
 ASSUME CS:CODE, DS:CODE, ES:CODE, SS:STACK

 DELETED      DB  '.BAK zostal usuniety',CR,LF,'$'
 NOT_DELETED  DB  '.BAK nie został usuniety',CR,LF,'$'

 DTA   DB  128 DUP (?)       ; nowy zbiór dyskowy (DTA)
 EFCB  DB  7 DUP (0)         ; rozszerzone FCB
 FCB   DB  0,'????????BAK'   ; nieotwarty FCB ze wzorcem
       DB  25 DUP (?)        ; pozostała część FCB

 START:
   MOV   AX, CODE
   MOV   DS, AX               ; inicjacja segmentu danych
   MOV   ES, AX               ; inicjacja segmentu dodatkowego
   MOV   DX, OFFSET DTA
   MOV   AH, SET_DTA
   INT   DOS                  ; inicjacja nowego bufora dyskowego
   MOV   AH, SEARCH_FIRST     ; poszukiwanie pierwszego pliku .BAK

 LOOP:
   MOV   DX, OFFSET FCB
   INT   DOS
   OR    AL, AL               ; czy znaleziono plik
   JNZ   FINISHED
   CALL  ERASE                ; usunięcie pliku
   MOV   AH, SEARCH_NEXT      ; poszukiwanie następnego pliku
   JMP   LOOP

 FINISHED:
   MOV   AH, TERMINATE
   INT   DOS                  ; zakończenie programu

 ERASE PROC
   MOV   DX, OFFSET DTA
   MOV   AH, DELETE
   INT   DOS                  ; usunięcie pliku
   PUSH  AX                   ; ochrona kodu błędu
   MOV   AL, ' '
   MOV   DI, OFFSET DTA + 1
   MOV   CX, 9
   MOV   DX, DI               ; adres wyprowadzania
   REPNE SCASB                ; poszuk. pierw. spacji w nazwie pliku
   MOV   BYTE PTR [DI-1],'$'  ; ogranicznik wyświetl. ciągu znaków
   MOV   AH, OUT_STRING
   INT   DOS                  ; wyświetlenie nazwy usuwanego pliku
   POP   AX                   ; odzyskanie kodu błędu
   OR    AL, AL               ; czy plik został usunięty?
   JZ    SUCCES
   MOV   DX, OFFSET NOT_DELETED  ; plik nie został usunięty
   JMP   INFORM

   SUCCES:
     MOV   DX, OFFSET DELETED

   INFORM:
     MOV   AH, OUT_STRING
     INT   DOS                   ; wyświetlenie komunikatu
   
   RET
 ERASE ENDP

 CODE ENDS
 END START

--- ^ --- ^ ----

 NAME WS_ASCII

 ; program czyta plik tekstowy WordStar-a, wyświetla go na ekranie
 ; w postaci znaków ASCII i ewentualnie zapisuje w postaci ASCII
 ; w nowym pliku na dysku, wywoływany jest poleceniem :
 ; wsas plik1 albo wsas plik1 plik2
 ; gdzie plik1 oznacza plik ZRÓDŁOWY WordStar-a, a plik2 ewentualny
 ; DOCELOWY plik ASCII. Plik1 może też być zwykłym plikiem ASCII - 
 ; wówczas program pozwala na obejrzenie go i ewentualne skopiowanie 
 ; do pliku2.

                             ; funkcje DOS:
 CHAR_OUT    EQU  02h        ; wysłanie znaku na ekran
 CLOSE       EQU  10h        ; zamknięcie pliku
 CREATE      EQU  16h        ; utworzenie i otwarcie pliku
 OPEN        EQU  0Fh        ; otwarcie pliku
 READ        EQU  14h        ; odczyt sekwencyjny pliku
 WRITE       EQU  15h        ; zapis sekwencyjny pliku
 TERMINATE   EQU  4Ch        ; zakończenie programu
 SET_DTA     EQU  1Ah        ; utworzenie bufora dyskowego
 STRING_OUT  EQU  09h        ; wyświetlenie ciągu znaków
 DOS         EQU  21h        ; przerwanie wywołań funkcji
 CONTROL_Z   EQU  1Ah        ; znak końca pliku

                          ; kody błędów
 EOF          EQU  01h    ; napotkanie końca pliku
 DTA_SMALL    EQU  02h    ; za mały bufor DTA
 PARTIAL_REC  EQU  03h    ; ostatni odczytany rekord nie jest pełny

                           ; przesunięcia w FCB lub PSP
 CURRENT_RECORD  EQU  20h  ; numer rekordu przy dostępie sekwencyjnym
 ORYGINAL_FCB1   EQU  5Ch  ; przesunięcie FCB pliku1 w PSP
 ORYGINAL_FCB2   EQU  6Ch  ; przesunięcie FCB pliku2 w PSP

 STACK SEGMENT STACK 
   DB 20 DUP (?)
 STACK ENDS

 DATA SEGMENT 'DATA'
   BUFFER  DB  128 DUP (?)  ; bufor dyskowy
   EFCB1   DB    7 DUP (0)  ; rozszerzenie FCB pliku1
   FCB1    DB   37 DUP (?)  ; FCB pliku1
   EFCB2   DB    7 DUP (0)  ; rozszerzenie FCB pliku2
   FCB2    DB   37 DUP (?)  ; FCB pliku2
 DATA ENDS

 CODE SEGMENT 'CODE'
 ASSUME CS:CODE, DS:DATA, ES:DATA, SS:STACK

 START:
   MOV    AX, DATA
   MOV    ES, AX                ; inicjacja segmentu dodatkowego
   MOV    DI, OFFSET FCB1       ; adres docelowy FCB pliku
   MOV    SI, ORIGINAL_FCB1     ; początek systemowego FCB pliku1
   MOV    CX, 12                ; długość nieotwartego FCB
   REPNE  MOVSB                 ; przepisanie nieotwartego FCB pliku1

   MOV    BYTE PTR FCB1[CURRENT_RECORD], 0

   MOV    DI, OFFSET FCB2       ; adres docelowy FCB pliku2
   MOV    SI, ORIGINAL_FCB2     ; początek systemowego FCB PLIKU2
   MOV    CX, 12
   REPNE  MOVSB                 ; przepisanie nieotwartego FCB pliku2

   MOV    BYTE PTR FCB2[CURRENT_RECORD], 0

                                ; inicjacja numeru rekordu przy
                                ; odczycie sekwencyjnym
   MOV    AX, ES
   MOV    DS, AX                ; inicjacja segmentu danych
   MOV    AL, BYTE PTR FCB2[1]  ; pobranie pierwszego znaku z nazwy pliku2
   CMP    AL, ' '
   JZ     CONT1                 ; skok gdy nie podano nazwy pliku2
                                ; podano nazwę pliku2
   MOV    DX, OFFSET FCB2
   MOV    AH, CREATE
   INT    DOS                   ; utworzenie i otwarcie pliku2

 CONT1:
   MOV    DX, OFFSET BUFFER     ; adres bufora dyskowego
   MOV    AH, SET_DTA
   INT    DOS                   ; wybranie nowego bufora dyskowego
   MOV    DX, OFFSET FCB1
   MOV    AH, OPEN
   INT    DOS                   ; otwarcie pliku

 LOOP:
   MOV    DX, OFFSET FCB1
   MOV    AH, READ
   INT    DOS                   ; sekwencyjny odczyt rekordu z pliku1
   PUSH   AX                    ; chroń na stosie wynik operacji w AL
   MOV    DI, OFFSET BUFFER
   MOV    CX, LENGTH BUFFER

 MAKEASC:
   MOV    AL, [DI]
   AND    AL, 7Fh
   MOV    [DI], AL              ; zamień znaki w buforze na znaki ASCII,
   INC    DI                    ; zerując w każdym znaku
   LOOP   MAKEASC               ; najstarszy bit
   MOV    AL, BYTE PTR FCB2[1]  ; pobranie pierwszego znaku z nazwy pliku
   CMP    AL, ' '
   JZ     CONT2                 ; skok gdy nie podano nazwy pliku2
                                ; podano nazwę pliku 2
   MOV    DX, OFFSET FCB2
   MOV    AH, WRITE
   INT    DOS                   ; zapisanie rekordu ASCII do pliku2

 CONT2:
   MOV    DX, OFFSET BUFFER
   MOV    CX, LENGHT BUFFER
   POP    AX                    ; AL <- wynik operacji odczytu
   CMP    AL, EOF               ; czy koniec pliku ?
   JZ     FINISHED
   CMP    AL, DATA_SMALL        ; czy za mały bufor dyskowy ?
   JZ     FINISHED              ; 
   CMP    AL, PARTIAL_REC       ; czy odczyt pełnego rekordu ?
   JNZ    OUTPUT
                                ; odczyt niepełnego rekordu
   MOV    DI, DX
   MOV    AL, CONTROL_Z
   REPNE  SCASB                 ; poszukiwanie znaku końca pliku
   MOV    AX, LENGHT BUFFER-1
   SUB    AX, CX                ; obliczenie długości do znaku końca pliku
   MOV    CX, AX
   CALL   OUT_STRING            ; wyświetlenie niepełnego rekordu
   JMP    FINISHED

 OUTPUT:
   CALL   OUT_STRING            ; wyświetlenie rekordu
   JMP    LOOP                  ; obsługa następnego rekordu

 FINISHED:
   MOV    DX, OFFSET FCB1
   MOV    AH, CLOSE
   INT    DOS                   ; zamknięcie pliku1
   MOV    AL,BYTE PTR FCB2[1]   ; pobranie pierwszego znaku z nazwy pliku2
   CMP    AL, ' '
   JZ     CONT3                 ; skok gdy nie podano nazwy pliku2
                                ; podano nazwę pliku 2
   MOV    DX, OFFSET FCB2
   MOV    AH, CLOSE
   INT    DOS

 CONT3:
   MOV    TERMINATE
   INT    DOS                   ; zakończenie programu

 OUT_STRING PROC
   CMP    CX, 0                 ; czy długość ciągu znaków = 0?
   JZ     RETURN
   MOV    DI, DX

   CONT4:
     MOV    AL, '$'
     REPNE  SCASB               ; poszukiwanie znaku '$'
     CMP    CX, 0               ; czy nie znaleziono ?
     JNZ    OUT
     MOV    BL, [DI-1]          ; zastąpienie ost. znaku w buf, przez '$'
     MOV    BYTE PTR [DI-1], '$'

   OUT:
     MOV    AH, STRING_OUT
     INT    DOS                 ; wyświetlenie zawartości bufora
     CMP    CX, 0               ; czy wyświetlono cały bufor
     JZ     LAST
     MOV    DL, '$'
     MOV    AH, CHAR_OUT
     INT    DOS                 ; wyświetlenie znaku '$'
     MOV    DX, DI              ; ustawienie adresu pocz. nast. ciągu znaków
     JMP    CONT4

   LAST:
     MOV    DL, AL
     MOV    AH, CHAR_OUT
     INT    DOS                 ; wyświetlenie ostatniego znaku

   RETURN:
     RET
 OUT_STRING ENDP

 CODE ENDS
 END START

Zbigniew Pojmański, Mikroklan, październik 1987r.