2

zsh module zsh/zpty seems to work perfectly well on my Linux virtual machine running on Apple M1 macOS machine. But the same does not work on macOS (even on the same host machine). I have tried this with both the build of zsh that comes preinstalled on macOS /bin/zsh and one that is installed from homebrew.

In my observation, a process is indeed created when zpty command is executed to create pseudo terminal and run a command in it. But after that, writing to process does not work. Here's some example, I entered the following commands one by one in an interactive zsh session:

zmodload zsh/zpty
zpty -b hello 'vim --clean'
zpty -w hello ':e foo'
zpty -w hello $'aHello there from foo file\e:w'
cat foo
zpty -d hello

On macOS cat would fail with cat: foo: No such file or directory. On Linux you will see Hello there from foo file.

What am I trying to achieve with zpty? I would like to start a process in a pseudo terminal as promised by the zsh manual, and send it input asynchronously. I don't care about the stdout of process. An ideal solution would discard all stdout without being CPU intensive and would not have delay loops. Also, I am not looking for an alternative solution using expect or any other external program.

Is this a zsh or macOS bug? Can it be bypassed like dtrace restriction can be bypassed like this? Though I would be surprised if it is SIP (System Integrity Protection) related.

codepoet
  • 566
  • 3
  • 14

1 Answers1

0

I'd say it's a bug in your code that means it might or might not work. The fact that it's working for you on Linux doesn't even guarantee that it'll work for others, and it doesn't make Linux better than macOS.

  1. You're sending commands to vim and never waiting for it to react. So it's quite possible that foo will appear eventually, but zsh is quicker and checks before the file is ready. This depends on the relative execution speed of vim vs zsh, which can depend on the exact version of each, the system libraries, the operating system kernel, the CPU, the phase of the moon, etc.
  2. You never read from the terminal, so vim might get blocked when writing to it. Whether it gets stuck depends on how much vim wants to write (which can depend on the system libraries (specifically Curses) and on the terminfo entry), and how much the terminal interface allows it to write before the terminal consumes the data (which depends on the kernel and maybe on system libraries used by zsh).

The proper solution is to read from the terminal until vim acknowledges that it has finished doing what you wanted to do, i.e. wait for the "B written" line. This takes care both of ensuring vim won't get blocked, and that you wait long enough.

I've done a little experimenting on macOS, but not looked in a debugger. Try this code, which waits a little then reads whatever vim has written and checks for the file, without waiting for any particular acknowledgement:

rm -f foo
zpty -b hello 'vim --clean'
zpty -w hello ':e foo'
zpty -w hello $'iHello there from foo file\e:w'
sleep 0.02
zpty -r hello > >(xxd)
ls foo
zpty -d hello

For me, foo might or might not exist when I run this. You may need to tweak the sleep value for your system to be around the threshold. On my machine, sleep 0.2 gets foo to be always present, and omitting sleep gets foo to be always absent, but as I wrote above this can depend on all kinds of parameters.

The following code will reliably wait until foo has been saved (assuming the message from Vim contains B written; if I run vim with my configuration rather than vim --clean, the message contains bytes written instead).

rm -f foo
zpty -b hello 'vim --clean'
zpty -w hello ':e foo'
zpty -w hello $'iHello there from foo file\e:w'
while zpty -r hello data; [[ $data != *"B written"* ]]; do sleep 0.1; done
ls foo
zpty -d hello
Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
  • Neither of those snippet work on munched Apple, works on happy Penguin. Even if I sleep the shell for full 1 second, or wait for "bytes written" instead of "B written". Also, I had originally run the commands one by one in an interactive zsh shell, have updated the question to reflect that. So it was not about any race condition, at least not in my original case. Though yes, it would be good to have a solution that works both at human speeds and speed of computer. – codepoet Dec 25 '22 at 22:30
  • @reportaman I tested on macOS Big Sur, and I found that I needed both the delay and reading to make it work. It might take a delay both before and after reading, or perhaps multiple reading steps, depending on buffer sizes involved (and I don't know what the sizes are). I can't think of what third thing might be needed. – Gilles 'SO- stop being evil' Dec 26 '22 at 09:31
  • Update: Vim by default created .foo.swp file. In its presence it would not continue unless user sends some precise key. Option -n bypasses swap file, and set sequence doesn't create new ones. With this change your both your snippet work on macOS. Though yesterday I had added clarification for my question, I'll accept this answer after my command edits are accepted, and ask another question as a followup. – codepoet Dec 27 '22 at 02:25
  • @reportaman Ah I see. That's not a difference between macOS and Linux or between versions of Vim. It's because you happened to have the swap file on one machine and not on the other. Which you can tell by inspecting the output from vim. – Gilles 'SO- stop being evil' Dec 27 '22 at 10:21