13

I have the following functions.

hello () {
        echo "Hello"
}
func () {
        hello
        echo "world"
}

If I don't want the output of the hello function to be printed but want to do something with it, I want to capture the output in some variable, Is the only possible way is to fork a subshell like below? Is it not an unnecessary creation of a new child process? Can this be optimized?

func () {
        local Var=$(hello)
        echo "${Var/e/E} world"
}
codeforester
  • 34,080
  • 14
  • 96
  • 122
balki
  • 24,438
  • 28
  • 97
  • 142
  • It may be a creation of a subshell, but why is it a problem? Are you sure you're not optimizing prematurely? – evil otto Sep 22 '11 at 04:26
  • 3
    @evilotto It may be a problem if function has side effects besides outputting to stdout: for example, changing variables. – ZyX Sep 22 '11 at 16:01

6 Answers6

4

An ugly solution is to temporarily replace echo so that it sets a global variable, which you can access from your function:

func () {
  echo () {
    result="$@"
  }
  result=
  hello
  unset -f echo
  echo "Result is $result"
}

I agree it's nasty, but avoids the subshell.

Idelic
  • 14,060
  • 4
  • 34
  • 40
  • I was also thinking on similar lines. I can replace all echo with Echo which does something like this. Will this make the shell functions faster? – balki Sep 21 '11 at 16:46
3

You can make the caller pass in a variable name to hold the output value and then create a global variable with that name inside the function, like this:

myfunc() { declare -g $1="hello"; }

Then call it as:

myfunc mystring
echo "$mystring world" # gives "hello world"

So, your functions can be re-written as:

hello() {
    declare -g $1="Hello"
}

func() {
    hello Var
    echo "${Var/e/E} world"
}

The only limitation is that variables used for holding the output values can't be local.


Related post which talks about using namerefs:

codeforester
  • 34,080
  • 14
  • 96
  • 122
2

Not a bash answer: At least one shell, ksh optimises command substitution $( ... ) to not spawn a subshell for builtin commands. This can be useful when your script tends to perform a lot of these.

Henk Langeveld
  • 7,670
  • 42
  • 56
2

How about using a file descriptor and a Bash here string?

hello () {
    exec 3<<<"Hello"
}

func () {
    local Var
    exec 3>&-
    hello && read Var <&3
    echo "${Var/e/E} world"
    exec 3>&-
}

func
tim
  • 21
  • 1
  • Bash cryptics are practically endless,so4the~99% of us who even as long-time Bash coders NOT know-else-memomorize 100% of Bash docs, IN ENGLISH,"recode called fn so all its output is to a file-descriptor OTHER than stdout then manipulate that"(advanced!),per relevant OFFICIAL URLs FOR *ADVANCED* Bash ops http://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html#index-exec & http://www.gnu.org/software/bash/manual/html_node/Redirections.html#Duplicating-File-Descriptors tho no find defined there quote(>&-) tho I do at http://www.tldp.org/LDP/abs/html/io-redirection.html#CFD – Destiny Architect Jun 11 '14 at 06:05
  • --Advanced! But is there a way (put in another answer here) to do this as the asker apparently asked(I really want,too): WITHOUT having to recode the function being called(so say in this case function remains hello(){ echo "Hello" })? -but still NOT creating a sub-shell(so then allowing the fn to do visible environment side-effects&more benefits),as called fn's stdout still redirected to this other file-descriptor. I made attempts such as my (internal)ID N6ZPBM where quote(exec hello 1>&3) (see my last cmt for official defs of these ops),but sadly these just give quote(3: Bad file descriptor). – Destiny Architect Jun 11 '14 at 06:24
1

Do you have the option of modifying the hello() function? If so, then give it an option to store the result in a variable:

#!/bin/bash

hello() {
  local text="hello"

  if [ ${#1} -ne 0 ]; then
    eval "${1}='${text}'"
  else
    echo "${text}"
  fi
}

func () {
  local var     # Scope extends to called functions.
  hello var
  echo "${var} world"
}

And a more compact version of hello():

hello() {
  local text="hello"
  [ ${#1} -ne 0 ]  && eval "${1}='${text}'" || echo "${text}"
}
Andrew Vickers
  • 2,254
  • 2
  • 9
  • 12
0

This doesn't literally answer the question, but it is a viable alternate approach for some use cases...

This is sort of a spin off from @Andrew Vickers, in that you can lean on eval.

Rather than define a function, define what I'll call a "macro" (the C equivalent):

MACRO="local \$var=\"\$val world\""

func()
{ 
    local var="result"; local val="hello"; eval $MACRO; 
    echo $result; 
}
BuvinJ
  • 9,113
  • 5
  • 74
  • 84