如何在 Linux 汇编语言中从 STDIN 正确读取文件名?

How to properly read a filename from STDIN in Linux Assembly Language?

我正在阅读 Jonathan Bratlett 的一本名为《编程》的书。这本书使用 GCC 工具集教授 x86 处理器和 linux 操作系统的汇编语言。

在讲授错误处理的第 7 单元中,要求您修改现有程序以添加一个 程序恢复机制,允许它在无法打开标准文件的情况下从 STDIN 读取在程序中硬编码。

这里的问题是用户从STDIN输入的文件名默认情况下在末尾附加\n。所以程序找不到要读取的文件。

我必须在程序中手动将 \n 替换为 0 才能正常工作。而且感觉这不是处理这种情况的正确方法。我该如何正确解决这个问题?

PS: 这是我第一次在这里提问。如果我能以任何方式改进问题,请告诉我。谢谢。

代码如下:

.include "consts/linux.s"
.include "consts/record-def.s"

.section .data
input_filename:
    .ascii "wrongtest.dat[=11=]"
output_filename:
    .ascii "testout.dat[=11=]"

.section .bss
.lcomm record_buffer, RECORD_SIZE

.section .text
.equ ST_INPUT_DESCRIPTOR, -4
.equ ST_OUTPUT_DESCRIPTOR, -8

.globl _start
_start:
    movl %esp, %ebp
    subl , %esp

    movl $SYS_OPEN, %eax
    movl $input_filename, %ebx
    movl [=11=], %ecx
    movl 66, %edx
    int $LINUX_SYSCALL
    movl %eax, ST_INPUT_DESCRIPTOR(%ebp)    #Input descriptor.

    cmpl [=11=], %eax
    jg continue_processing
    .section .data
    no_open_error_code:
        .ascii "0001[=11=]"
    no_open_error_message:
        .ascii "Could not open the file[=11=]"

    .section .text
    pushl $no_open_error_message
    pushl $no_open_error_code
    call error_exit
    addl , %esp

    movl $SYS_READ, %eax
    movl $STDIN, %ebx
    movl $record_buffer, %ecx
    movl $RECORD_SIZE, %edx
    int $LINUX_SYSCALL

    ###Manually replace last byte of the filename with 0###
    #decl %eax
    #movb [=11=], record_buffer(,%eax,1)

    movl $SYS_OPEN, %eax
    movl $record_buffer, %ebx
    movl [=11=], %ecx
    movl 66, %edx
    int $LINUX_SYSCALL
    movl %eax, ST_INPUT_DESCRIPTOR(%ebp)    #Recovery input descriptor.

    continue_processing:
    movl $SYS_OPEN, %eax
    movl $output_filename, %ebx
    movl 01, %ecx
    movl 66, %edx
    int $LINUX_SYSCALL
    movl %eax, ST_OUTPUT_DESCRIPTOR(%ebp)   #Output descriptor.

    start_inc_age_loop:
        pushl ST_INPUT_DESCRIPTOR(%ebp)
        pushl $record_buffer
        call read_record
        addl , %esp   

        cmpl $RECORD_SIZE, %eax
        jne end_inc_age_loop
        incl record_buffer + RECORD_AGE

        pushl ST_OUTPUT_DESCRIPTOR(%ebp)
        pushl $record_buffer
        call write_record
        addl , %esp
        jmp start_inc_age_loop
    end_inc_age_loop:
        movl %ebp, %esp
        movl $SYS_EXIT, %eax
        movl [=11=], %ebx
        int $LINUX_SYSCALL

终端/文件输入需要手动处理换行是正常的。 read 系统调用只允许您访问原始字节流,无需解析。

这就是为什么通常从命令行参数中获取文件名(如 cat foo.txt),而不是从标准输入中获取文件列表的原因。

以及为什么在 Unix shell 编程中,您一定要避免从文本流中解析文件名:https://unix.stackexchange.com/questions/128985/why-not-parse-ls-and-what-to-do-instead - 为什么像 find -print0xargs -0使用 [=14=] 作为分隔符存在。

\n 是可以出现在文件名中的合法字符,因此 safely/unambiguously 从 stdin 解析文件名的唯一方法是将它们与不能出现在文件名中的一个字节分开, 0,C 字符串终止符。 (或者使用某种具有明确长度的格式,这样你就会知道接下来的 123 个字符都是文件名,不管它们是什么。)


对于单个文件名,您还可以期望用户在文件名末尾结束输入,例如通过按 EOF 字符提交 TTY 输入(默认为 control-D,运行 stty 以显示终端模式)。

然后您可以直接将 read() 的结果作为 C 字符串使用到已经清零的缓冲区中。 (除非仍然不能完全让您在终端不处于原始模式的情况下处理文件名中的换行符;用户键入换行符会导致提交终端输入,即 read() 会 return。但是,用户可以通过使用 control-V 使下一个字符成为“文字”来解决这个问题,让他们点击 ^V enter 来键入文字换行符,无需在“熟化”规范模式下使用 TTY 提交输入。尝试自己在终端上输入 catstrace cat,以及当您在非空模式下输入 control-D 时会发生什么行(读取 returns 非零),对比空行(或在前一个 control-D 之后)使读取 return 为零,即 EOF)