Cách (tạm thời) chụp stdout

Sep 23 2020

Làm cách nào để chụp tạm thời stdout trong Nim?

Tôi muốn có một mẫu với chữ ký sau:

template captureStdout(ident: untyped, body: untyped) = discard

Sao cho mã này ( main.nim) chạy mà không có lỗi:

var msg = "hello"
echo msg & "1"
var s: string
captureStdout(s):
  echo msg & "2"
  msg = "ciao"
echo msg & "3"
assert s == "hello2\n"

và đầu ra phải là:

hello1
ciao3

nỗ lực hiện tại

hiện tại, tôi có thể chụp stdout bằng một tệp tạm thời, nhưng tôi không thể phát hành trở lại stdout. Tôi làm điều này với những điều sau:

template captureStdout*(ident: untyped, body: untyped) =
  discard reopen(stdout, tmpFile, fmWrite)
  body
  ident = readFile(tmpFile)

với điều này main.nimchạy mà không có lỗi xác nhận nhưng đầu ra chỉ

hello1

và trong tmpFile tôi thấy:

hello2
ciao3

Trả lời

3 Clonk Sep 23 2020 at 16:16

Khi bạn gọi, reopenbạn gán lại biến stdoutcho một biến được Fileghi vào tmpFile.

Để in kết quả đầu ra cho STDOUT hệ thống, bạn cần gán lại biến stdoutthành một biến Fileghi vào STDOUT systel của bạn.

Do đó, câu trả lời là khác nhau giữa Linux và Windows.

Đối với Linux, cách thực hiện điều này là sử dụng hàm C trùng lặp và lặp lại 2 để sao chép bộ stdoutmô tả tệp và sử dụng một tệp khác (để bạn có thể khôi phục stdout). Vì sao chép và trùng lặp2 không có system/iotrong Nim nên chúng tôi cần có các ràng buộc unistd.h.

Đây là một ví dụ:

#Create dup handles
proc dup(oldfd: FileHandle): FileHandle {.importc, header: "unistd.h".}
proc dup2(oldfd: FileHandle, newfd: FileHandle): cint {.importc,
    header: "unistd.h".}

# Dummy filename
let tmpFileName = "tmpFile.txt"

template captureStdout*(ident: untyped, body: untyped) =
  var stdout_fileno = stdout.getFileHandle()
  # Duplicate stoud_fileno
  var stdout_dupfd = dup(stdout_fileno)
  echo stdout_dupfd
  # Create a new file
  # You can use append strategy if you'd like
  var tmp_file: File = open(tmpFileName, fmWrite)
  # Get the FileHandle (the file descriptor) of your file
  var tmp_file_fd: FileHandle = tmp_file.getFileHandle()
  # dup2 tmp_file_fd to stdout_fileno -> writing to stdout_fileno now writes to tmp_file
  discard dup2(tmp_file_fd, stdout_fileno)
  #
  body
  # Force flush
  tmp_file.flushFile()
  # Close tmp
  tmp_file.close()
  # Read tmp
  ident = readFile(tmpFileName)
  # Restore stdout
  discard dup2(stdout_dupfd, stdout_fileno)

proc main() =
  var msg = "hello"
  echo msg & "1"
  var s: string

  captureStdout(s):
    echo msg & "2"
    msg = "ciao"

  echo msg & "3"
  echo ">> ", s
  assert s == "hello2\n"

when isMainModule:
  main()
  # Check it works twice
  main()