Estoy tratando de mantener un shell ( bash
/ zsh
/ etc
) en ejecución mediante el uso de Process de la biblioteca Foundation.
Sé cómo usar Pipe para entrada/salida/error estándar para otros ejecutables de línea de comando, pero parece que los programas de shell requieren que el archivo de entrada/salida/error estándar sea un archivo de dispositivo terminal.
Parece que estos son los archivos llamados /dev/ttys<3 digit number>
que se crean para cada nueva instancia de shell. ¿Cómo puedo crear estos archivos yo mismo y usarlos rápidamente?
SOLUCIÓN:
Así que esto resultó ser mucho más fácil de lo que pensé que sería. Estos son los pasos necesarios para crear un par de objetos FileHandle
de pseudo-terminal maestro y esclavo:
Darwin
.posix_openpt(int oflag)
para obtener un descriptor de archivo para un dispositivo pseudo-terminal maestro disponible.grantpt(int filedes)
para establecer la propiedad del dispositivo pseudo-terminal esclavo correspondiente.unlockpt(int filedes)
para desbloquear el dispositivo pseudo-terminal esclavo.ptsname(int filedes)
para obtener la ruta al dispositivo pseudo-terminal esclavo.FileHandle
utilizando el descriptor de archivo del paso 2 y la ruta del paso 5. En los pasos 3 a 5, filedes
es el descriptor de archivo devuelto por la llamada a posix_openpt(int oflag)
del paso 1. Use O_RDRW
para el parámetro oflag
en el paso 1 para obtener permisos de lectura y escritura en el archivo del dispositivo pseudo-terminal maestro.
Código de ejemplo en el que mantenemos activa una sesión de bash
y hacemos que ejecute el comando tty
:
import Foundation import Darwin class A: NSObject { var task: Process? var slaveFile: FileHandle? var masterFile: FileHandle? override init() { self.task = Process() var masterFD: Int32 = 0 masterFD = posix_openpt(O_RDWR) grantpt(masterFD) unlockpt(masterFD) self.masterFile = FileHandle.init(fileDescriptor: masterFD) let slavePath = String.init(cString: ptsname(masterFD)) self.slaveFile = FileHandle.init(forUpdatingAtPath: slavePath) self.task!.executableURL = URL(fileURLWithPath: "/bin/bash") self.task!.arguments = ["-i"] self.task!.standardOutput = slaveFile self.task!.standardInput = slaveFile self.task!.standardError = slaveFile } func run() { DispatchQueue.global(qos: .userInitiated).async { [weak self] in guard let self = self else { return } do { try self.task!.run() } catch { print("Something went wrong.\n") } } let data = self.masterFile!.availableData let strData = String(data: data, encoding: String.Encoding.utf8)! print("Output: "+strData) self.masterFile!.write("tty\u{0D}".data(using: String.Encoding.utf8)!) sleep(1) let data1 = self.masterFile!.availableData let strData1 = String(data: data1, encoding: String.Encoding.utf8)! print("Output: "+strData1) } } let a = A() a.run()
Una respuesta aún más simple que es compatible con Playground.
import PlaygroundSupport import Foundation import Darwin // use socat to create psudo echo device // socat -d -d -x pty,raw,nonblock,ispeed=115200,echo=0,link=/dev/ttys000 EXEC:/bin/cat print("Started:\n") var masterFile: FileHandle? var masterFD: Int32 = 0 masterFD = open("/dev/ttys000",O_RDWR|O_NONBLOCK) grantpt(masterFD) unlockpt(masterFD) masterFile = FileHandle.init(fileDescriptor: masterFD) masterFile!.write("tty\u{0D}".data(using: String.Encoding.utf8)!) sleep(1) let data1 = masterFile!.availableData let strData1 = String(data: data1, encoding: String.Encoding.utf8)! print("Output: "+strData1)