zsh-users
 help / color / mirror / code / Atom feed
From: Joey Pabalinas <joeypabalinas@gmail.com>
To: Ben Klein <robobenklein@gmail.com>
Cc: zsh-users@zsh.org, Joey Pabalinas <joeypabalinas@gmail.com>
Subject: Re: Capturing STDOUT without subshells or file I/O
Date: Sun, 2 Sep 2018 14:40:47 -1000	[thread overview]
Message-ID: <20180903004047.ew2kvmzhnecqgki2@gmail.com> (raw)
In-Reply-To: <04a12c6a-c926-b088-f386-8a2bdb81dad2@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 3680 bytes --]

On Sun, Sep 02, 2018 at 04:57:49PM -0400, Ben Klein wrote:
> Currently, I have this:
> 
>   p10k_render_prompt_from_spec p10k_left p10k_opts > $tmpd/prompt
>   read -d $'\0' _P10K_RENDERED_OUTPUT_PROMPT < $tmpd/prompt
>   p10k_render_prompt_from_spec p10k_right p10k_opts right > $tmpd/prompt
>   read -d $'\0' _P10K_RENDERED_OUTPUT_RPROMPT < $tmpd/prompt
> 
> And that involves creating a temporary file (/tmp) which might be
> mounted on a spinning drive, so performance might take a hit.
> 
> The other route using a pipe:
> 
> p10k_render_prompt_from_spec p10k_left p10k_opts | read -d $'\0'
> _P10K_RENDERED_OUTPUT_PROMPT
> 
> Causes environment variables and changes made during the `render_prompt`
> call to be ignored. (Ends up in a subshell...?)
> 
> Is there some way I can use a virtual FD, or perhaps Zsh provides some
> kind of buffer I could use instead?
> 
> In the end, I'm looking for a way to connect STDOUT to STDIN between ZSH
> functions/builtins without any subshells or file I/O. (It's fine if it
> gets buffered until close-of-stream.)

You can sort of use the fact that if you delete the filesystem
name of a temporary file (which would make the hard link count 0),
as long as there still exists open file descriptors referring to
it, you are still able to read/write to it.

Let's make an example function that changes $FOO from 0 to 42, then
prints some output, sort of like your function:

> % FOO=0
> % fun() { FOO=42; print -r - "output of $funcstack[1]()"; }
> % print -r - "FOO=$FOO"
> FOO=0

Now we will use mktemp(1) to create a file in a tmpfs (usually /tmp)
directory, which a swap-backed ramdisk, then redirects an arbitrary
read/write fd to it and deletes the file:

> % tmp=$(mktemp)
> % exec {FD}<>$tmp
> % rm -fv -- $tmp
> removed '/tmp/tmp.sS1G9F6OjR'

The redirected file descriptor is still open however (if you have
colors in your ls you will see it in red because the file no longer
exists), and you can write to it by redirecting stdout:

> % ls -li /proc/$$/fd
> 29987340 lrwx------ 1 jp jp 64 Sep  2 14:36 0 -> /dev/pts/2
> 29986634 lrwx------ 1 jp jp 64 Sep  2 14:36 1 -> /dev/pts/2
> 29986638 lrwx------ 1 jp jp 64 Sep  2 14:36 12 -> /tmp/tmp.dneCPiOqS5
> 29986635 lrwx------ 1 jp jp 64 Sep  2 14:36 2 -> /dev/pts/2
> % fun >&$FD

Reading back your output, however, is a little weirder. The r/w
file descriptor file position is set past your output; reading
from it normally would give us back our output. You also can't
reopen it (file is gone), nor is there a mechanism in zsh to
call something like lseek(2) to rewind file descriptors.

However, what we *can* do it (ab)use the fact that subshells
will rewind the file descriptors since they are duplicated
during fork(). So we can make a subshell, redirect our fd to
stdin and read back what we have written! Like so:

> hobbes% output=$((</dev/stdin)<&$FD)
> hobbes% print -lr - "FOO=$FOO" "output=\"$output\""
> FOO=42
> output="output of fun()"

And of course, it's always good practice to close your
file descriptors in the main shell instance (subshell
file descriptors are implicitly closed when the subshell
exits) when you are finished with them:

> hobbes% exec {FD}>&-

Of course, realize this is kind of a hacky solution, and the better
way is to just have your function either not play with the current
environment (does something like a prompt generation script really
need to change the environment?) or have it set `PROMPT` and `RPROMPT`
itself and avoid worrying stdout at all.

But hey, we all need to have a little fun sometimes :)

-- 
Cheers,
Joey Pabalinas

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

  reply	other threads:[~2018-09-03  0:40 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-09-02 20:57 Ben Klein
2018-09-03  0:40 ` Joey Pabalinas [this message]
2018-09-03 14:02 ` Daniel Shahaf
2018-09-03 18:43   ` Joey Pabalinas
2018-09-17 20:13     ` Ben Klein
2018-09-17 20:46       ` Daniel Shahaf
2018-09-17 21:01         ` Mikael Magnusson
2018-09-17 21:20           ` Bart Schaefer
2018-09-17 21:19       ` dana
2018-09-17 21:33         ` Bart Schaefer
2018-09-18  5:12 ` Sebastian Gniazdowski
2018-09-21  6:58   ` Oliver Kiddle

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20180903004047.ew2kvmzhnecqgki2@gmail.com \
    --to=joeypabalinas@gmail.com \
    --cc=robobenklein@gmail.com \
    --cc=zsh-users@zsh.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/zsh/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).