2

Information

I have an issue where I want to return a value (array or string) from a bash script function.

I already know the following :

  • A bash script called outputtext=$(functionName) runs in a sub-shell
  • A bash function does not act the same as in 'normal' programming languages, it only kind of returns the text output
  • A bash function can only return an error code as status and can be access via $?

Test Script

A test script I created now to demonstrate what I want to do, and why I can't use output directly

   #!/bin/bash
   declare GLOBAL_VAR
   function callThis(){
      local output="==========================================\n"
      output="$output [START callThis()]\n"
      # For demonstration only - error 5 might be a built-in bash error code, but if caller
      # expects 0/5 they can be handled
      local statusCode=5
      # This variable is empty as soon as it exits the function
      GLOBAL_VAR="Some array value OR text output into GLOBAL_VAR"
      # Because the script has alot of text (echo'd/printf) output, and the function
      # calculates a result (array), I would like to have it as a RESULT
      # for another function.
      output="$output This is some output that will be sent to caller.\n"
      output="$output Test to see if GLOBAL_VAR is assigned:\n"
      output="$output '$GLOBAL_VAR'\n"
      output="$output [END callThis()]\n"
      output="$output ==========================================\n"
      echo -e $output
      return $((statusCode))
   }

   function startHere(){
      OUTPUT=$(callThis)
      STATUS_RESULT=$?
      echo -e "[Text output : $OUTPUT\n]"
      echo -e "[Status Code : $STATUS_RESULT\n]"
      echo -e "[Global VAR in startHere():\n'$GLOBAL_VAR']"
      if [ "$STATUS_RESULT" -eq 5 ]; then
         echo "Success! Status code $STATUS_RESULT found!"
      fi
   }

   echo -e $(startHere)
   echo "Global VAR is now (outside functions): '$GLOBAL_VAR'"

Result above

I am aware echo -e does not give exactly this exact output for this function, besides this topic

   [Text output :
   ========================================== 
   [START callThis()]
    This is some output that will be sent to caller. 
   Test to see if GLOBAL_VAR is assigned: 
   'Some array value OR text output into GLOBAL_VAR' 
   [END callThis()]
   ========================================== ] 
   [Status Code : 5 ] 
   [Global VAR in startHere(): '']
   Success! Status code 5 found!
   Global VAR is now (outside functions): ''

The script above shows most of the things I want to do: I have functions that are quite long and have calculated results array, and handling a exit code.

QUESTION

Since it is a sub-shell created when calling $(startHere), or even within startHere(), the GLOBAL_VAR is empty as soon as callThis() exits.

I do not know how to proceed because I need the value for another function. How can I solve this?

SOLUTION

See @mjrezaee's solution for the example I provided.

It turns out that I do not always need to use sub-shell $() for everything, most of the time call the function directly, and the output, global variables, and even the error code (return code) are available. (I do not work with bash scripts alot). Based on the advise I adapted my 400+ line script and were able to use global variables easily.

CvRChameleon
  • 367
  • 1
  • 5
  • 29
  • 2
    this will be helpful i guess https://stackoverflow.com/questions/23564995/how-to-modify-a-global-variable-within-a-function-in-bash – mjrezaee Jul 24 '20 at 12:22
  • Coincidentally I have just been looking at the same topic about two minutes ago. The first answer is very long, I am now going to look at `capture` that he is talking about. Seems more complicated to update a value in a sub-shell than I anticipated. – CvRChameleon Jul 24 '20 at 12:27
  • calling the function directly (`startHere` and `callThis`) will update your global variable easily, but if you want to use sub-shell, that will create some complications – mjrezaee Jul 24 '20 at 12:35
  • How do I get the output and exit code as well then? Aka the `echo's` and result code? (Let me test this) – CvRChameleon Jul 24 '20 at 12:39
  • 1
    `echo` will work in function instead of writing it to another variable by sub-shell. result code can be captured by `$?` as you used in your code – mjrezaee Jul 24 '20 at 12:59
  • Thank you @mjrezaee, this indeed seems to be possible (I created a simple test script with two functions). I now need to figure out if it will be possible to update my long script to not use sub-shells for some results. – CvRChameleon Jul 24 '20 at 13:11
  • What Bash version are you using? – Benjamin W. Jul 24 '20 at 13:13
  • `GNU bash, version 5.0.16(1)-release (x86_64-pc-linux-gnu)` – CvRChameleon Jul 24 '20 at 13:14
  • you could print different vars into different redirections (side note: return 0; at end of function and return 1; or [other](https://unix.stackexchange.com/questions/110348/how-do-i-get-the-list-of-exit-codes-and-or-return-codes-and-meaning-for-a-comm) on error) – alecxs Jul 24 '20 at 13:19
  • @alecxs, Not sure what you mean, care to give an example please? As you see from my example I am handling a `error 5 (return 5)` in `startHere()`. – CvRChameleon Jul 24 '20 at 13:22
  • You pretty much have to either use a global variable and no command substitution, or command substitution with passing the global variable around directly. It's also an important distinction if `GLOBAL_VAR` is an array or not; in your example, it's a string, but you mention it might be an array? – Benjamin W. Jul 24 '20 at 13:24
  • I have tested both, at first I assumed that my issue is because I am using a global variable array, but later on on changing the calling function to concatenate array into a string, I discovered that both string and array global variables are empty... which means the issue isn't if it is array/string, but the global variable usage itself. – CvRChameleon Jul 24 '20 at 13:27
  • You mention "command substitution with passing the global variable around directly", not sure how that is meant to be used differently. At the moment, I have been using @mjrezaee's advice which indicated I do not need to use $() command substitution; so I am now trying to adapt a 400 line script. I almost kind of always used $(), and never really needed to rely on global variables, I would text-manipulate short `echo` returns with exit codes. – CvRChameleon Jul 24 '20 at 13:30
  • Very good early question, but please read (and absorb) about [mcve]. Good luck! – shellter Jul 24 '20 at 13:33
  • Thanks @shelter, I will keep in mind to try to make my examples shorter in the future! – CvRChameleon Jul 24 '20 at 13:36
  • 1
    you have stuff to read for 1 hour already, but anyway https://stackoverflow.com/questions/7082001/how-do-file-descriptors-work – alecxs Jul 24 '20 at 13:48
  • I assume this method relates to writing values to a temporary file, I will consider this in the future as well. I read alot today yes! – CvRChameleon Jul 24 '20 at 13:52
  • 1
    You don't have to (or actually should not) update the question when it's been answered – the fact that there's an accepted answer already covers that. – Benjamin W. Jul 24 '20 at 14:19
  • nope, i meant you could write different fd's into different var's (but technically that **are** files). you could even `read -u3` string from fd3 split into array – alecxs Jul 25 '20 at 12:35
  • Oh I see, I will keep that topic in mind for reference as well! – CvRChameleon Jul 26 '20 at 09:20
  • 1
    if i would know how exactly to do it i would have posted that as answer, but i am still learning too ;) i wished there would be something like here-strings `>>>` for writing fd into variable (haven't found that anywhere) – alecxs Jul 26 '20 at 13:54

1 Answers1

2

As i said in comments, you can use simple function calling, simple changes to your test code will be as follow:

   #!/bin/bash
   declare GLOBAL_VAR
   function callThis(){
      local output="==========================================\n"
      output="$output [START callThis()]\n"
      # For demonstration only - error 5 might be a built-in bash error code, but if caller
      # expects 0/5 they can be handled
      local statusCode=5
      # This variable is empty as soon as it exits the function
      GLOBAL_VAR="Some array value OR text output into GLOBAL_VAR"
      # Because the script has alot of text (echo'd/printf) output, and the function
      # calculates a result (array), I would like to have it as a RESULT
      # for another function.
      output="$output This is some output that will be sent to caller.\n"
      output="$output Test to see if GLOBAL_VAR is assigned:\n"
      output="$output '$GLOBAL_VAR'\n"
      output="$output [END callThis()]\n"
      output="$output ==========================================\n"
      echo -e $output
      return $((statusCode))
   }

   function startHere(){
      # OUTPUT=$(callThis)
      callThis
      STATUS_RESULT=$?
      # echo -e "[Text output : $OUTPUT\n]"
      echo -e "[Status Code : $STATUS_RESULT\n]"
      echo -e "[Global VAR in startHere(): '$GLOBAL_VAR']"
      if [ "$STATUS_RESULT" -eq 5 ]; then
         echo "Success! Status code $STATUS_RESULT found!"
      fi
   }
   # echo -e $(startHere)
   startHere
   echo "Global VAR is now (outside functions): '$GLOBAL_VAR'"

output:

==========================================
 [START callThis()]
 This is some output that will be sent to caller.
 Test to see if GLOBAL_VAR is assigned:
 'Some array value OR text output into GLOBAL_VAR'
 [END callThis()]
 ==========================================

[Status Code : 5
]
[Global VAR in startHere(): 'Some array value OR text output into GLOBAL_VAR']
Success! Status code 5 found!
Global VAR is now (outside functions): 'Some array value OR text output into GLOBAL_VAR'

also this can be helpful as an explanation for other methods for using sub-shell

mjrezaee
  • 1,100
  • 5
  • 9
  • Yep thanks, I have figured this out from your original comment. I am now trying to update my long script to use this method of calling functions instead of `$() subshells`. – CvRChameleon Jul 24 '20 at 13:33