Search code examples
assemblystdindosx86-16io-redirection

How to read a character from standard input in DOS such that redirection works?


I am currently writing a DOS program. In this program, I use service 21/AH=01 to read a character from standard input. However, it appears that when I redirect standard input from a file, EOF detection does not work as expected. I wrote this sample program in nasm syntax to illustrate the issue:

    org 0x100

    ; read characters and sum them up
main:   xor bx,bx

.1: mov ah,1
    int 0x21
    xor ah,ah
    add bx,ax
    cmp al,0x1a       ; eof reached?
    jnz .1

    mov cx,4
.2: mov si,bx
    shr si,12
    mov dl,[hextab+si] ; convert to hex
    mov ah,2
    int 0x21
    shl bx,4
    loop .2

    ret

hextab: db '0123456789ABCDEF'

When I redirect standard input from a file, the program hangs unless that file contains a ^Z somewhere. I was under the impression that EOF is marked with service 21/AH=01 returning ^Z in DOS, however, that does not seem to be the case.

How do I read a character from standard input in DOS in a way that works with redirected stdin such that the character is echoed to the screen in case input is not redirected and such that I can detect an EOF condition? Ideally, I would like to have something that behaves like the getchar() function.


Solution

  • You should use the MS-DOS "read" function instead. In general you want to avoid the old obsolete MS-DOS 1.x API functions. The output functions (AH=1, AH=9) are usually OK, but most of the rest shouldn't be used unless you need your program to run under MS-DOS 1. MS-DOS 2.0 introduced a whole new set of Unix-like file functions that for the most part work just like the equivalent Unix function. So in this case MS-DOS read function, just like the Unix read system call, takes three parameters: a file handle, the address of a buffer and the number of characters to read. Just like with Unix, file handle 0 is for standard input, 1 is for standard output and 2 is for standard error.

    So you could rewrite your example to use the MS-DOS read (AH=3Fh) and write (AH=40h) functions like this:

        ORG 0x100
    
    main:
        xor di, di
        mov bp, buf
        mov dx, bp
        mov cx, 1
    
    loopchar:
        mov ah, 0x3f
        int 21h
        jc  error
        dec ax
        js  eof           ; EOF reached?
        cmp BYTE [bp], 0x1a
        je  eof
        add di, [bp]
        jmp loopchar
    
    eof:
        inc cx
        mov ax, di
        mov al, ah
        call printhex1
        jc  error
        xchg ax, di
    
    printhex1:
        mov bx, hextab
        aam 16
        xlat
        mov [bp + 1], al
        mov al, ah
        xlat    
        mov [bp], al
        mov bx, 1
        mov ah, 0x40
        int 21h
    
    error:
        ret
    
    buf db  0, 0
    
    hextab: db '0123456789ABCDEF'