12

I have rbenv (ruby version manager) installed on machine and it works like that:

$ rbenv local
2.3.1

Writing to stdout the local version of my ruby. I want to rescue this version and declare it in a variable to reuse in another occasion.

$ declare -r RUBY_DEFINED_VERSION=$(rbenv local)
$ echo Using ruby version $RUBY_DEFINED_VERSION
Using ruby version 2.3.1

It works!

But I don't want to use a subshell to do the work (using $() or ``). I want to use the same shell and I don't want to create a tmp file to do the work.

Is there a way to do this?

Note: declare -r is not mandatory, it can be a simple var=FOOBAR.

10
  • if you wanna call rbenv in current shell ; then you need at laest a named pip ; cmd > fifo; var="<fifo" !
    – Yunus
    Jan 3, 2017 at 14:17
  • Any reason for not wanting to use $(...) or temporary file?
    – Kusalananda
    Jan 3, 2017 at 14:24
  • 1
    @Kusalananda The rbenv local changes some variables and I want to use these variables. The shell script will run on various projects and I can't trust in /tmp or permissions. Some machines I just can write on /var/tmp.
    – dgmike
    Jan 3, 2017 at 14:28
  • Can't you just parse the .ruby-version file in the current directory? BTW, I can not find anything that says rbenv local changes anything. It's supposed to only report the local version according to github.com/rbenv/rbenv#rbenv-local
    – Kusalananda
    Jan 3, 2017 at 14:36
  • @Kusalananda in case of rvm (the shell script will prevent the rbenv and rvm) some variables are set, like RUBY_VERSION. But I think I can use cat .ruby-version. :-)
    – dgmike
    Jan 3, 2017 at 14:57

3 Answers 3

12

There is a hack, but I think it just make sense if you need it in a loop.

you can open a cat coproc like this: coproc CAT { cat; }

This will start a cat command in background, and set two environment variables: CAT_PID and CAT. The CAT variable is an array with STDOUT and STDIN (in this order) file descriptor (pipes) used by cat.

So, you can execute anything writing the output to &${CAT[1]} that represents the STDIN, and use the builtin command read to set your variable reading from ${CAT[0]} that is the STDOUT of cat.

Here a sample:

coproc CAT { cat; }
echo 123 >&${CAT[1]}
read myvar <&${CAT[0]}

To test:

echo $myvar
123

Don't forget to stop the cat after use it. You can do it by by killing the process.

kill $CAT_PID

This makes a great difference in performance tuning.

Update: bash implements strings null delimited. So when dealing with binary data, read is really tricky. You can read with LC_ALL=C read -r -n1 -d $'\0' one byte at time, then the null will be empty strings on ${REPLY} variable.

3
  • 2
    IFS= read -r -d $'\0' myvar to read null-delimited data Aug 29, 2018 at 15:47
  • anyone have an idea on how to get this or something similar working in zsh?
    – Chris
    Aug 9, 2022 at 3:42
  • try something like this: coproc cat; echo test >&p ; read -p var; echo $var ... and look at this
    – ton
    Aug 9, 2022 at 18:15
5

With bash you can also do it like this :

read a < <(echo hello)
echo "$a"

Or like this :

shopt -s lastpipe
echo hello | read a
shopt -u lastpipe
echo "$a"

But you still have to launch a sub-process which will run ruby, so I don't really understand what you are trying to avoid...

6
  • I really don't understand why this answer was downvoted.
    – ton
    Mar 15, 2018 at 14:44
  • what is a? is that a variable? Dec 22, 2018 at 4:55
  • 1
    read a, where a in this case is a variable where the read command you put the content readed. The command read will create or replace this variable and you can put any non-reserved word here, If you do not want to choose a name, you can omit it and the default variable name is REPLY, so you can read the content from $REPLY, use help read for more details.
    – ton
    Jan 18, 2019 at 16:45
  • 2
    Your first method has a subshell, breaking variable assignment. So read a < <(foo=bar; echo baz); echo "$a:$foo" outputs baz: instead of baz:bar. Your second method doesn't change $a's value. Maybe shopt -u lastpipe is broken for me? Aug 7, 2019 at 23:57
  • 1
    Running bash -x yourscript.sh with your first code example makes very clear the problem. You are doing command substitution hence the command runs in a subshell. While second examples runs in the current shell so even running changing environment commands will work well. Thank you.
    – GiovaLomba
    Mar 25, 2020 at 11:44
5

If on Linux, with bash versions prior to 5.1, you could do:

{
  chmod u+w /dev/fd/3 # only needed in bash 5.0
  rbenv local > /dev/fd/3
  IFS= read -rd '' -u 3 variable
} 3<<< ''

That does use a temp file like every here-document or here-string, though that's hidden to you. bash 5.1 switched to using pipes instead of regular temp files.

If rbenv outputs less data than can fit in a pipe without blocking (typically 64KiB), still on Linux and Linux only, you can use a pipe instead of the temp file with:

{
  rbenv local > /dev/fd/3
  IFS= read -rd '' -u 3 variable
} 3< <(:)

With ksh93 or recent versions of mksh, use the form of command substitution that doesn't start a subshell:

variable=${
  rbenv local
}

Beware that contrary to the IFS= read -rd '', that removes the trailing newline characters in the output (like all command substitutions).

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .