4

I have a script that can be sourced in bash or ksh or it runs in bash if executed. The script stores a desired return code in ${_rc}. I can return $_rc but that leaves _rc defined in the tty. I can unset _rc afterward but that overrides the return code. If the script is not sourced I need to use exit instead of return or i get an error message

I have the following code at the end of my script

cleanup_script # unsets all variables except _rc and _was_sourced
if [ "${_was_sourced}" == 1 ];
    return ${_rc}
else
    exit ${_rc}
fi

If I run this command

typeset > before.txt
.  ./myscript
echo $?
typeset > after.txt
diff before.txt after.txt

I get (in addition to variables and functions defined by the script that I want available to the terminal)

226a236
> _rc=0
232a243
> _was_sourced=1

Which I desire to not have if possible

5
  • 1
    You can unset _was_sourced before return, but I don't think there's any way to get rid of _rc. Commented Jun 11 at 15:41
  • 1
    Eh, it's doable. Move the unset into a function that returns the original $? value. Problem, though, is that then you have the function escaping into the outer shell... but maybe that's considered less messy? Commented Jun 11 at 15:46
  • If there weren't a cross-shell compatibility requirement I'd think about using traps for the purpose. Commented Jun 11 at 15:47
  • 1
    What I would consider here, if it's really just TTY use at hand, is changing the variable name from _rc to _ -- that way it's a variable that'll be overwritten by the interactive shell's regular operation. Commented Jun 11 at 16:01
  • @CharlesDuffy That a good idea that works for bash but I tested and _ doesnt seem to be used in ksh93. no idea about ksh88 Commented Jun 11 at 18:34

3 Answers 3

7

Just:

eval "unset _rc; return $_rc"
Sign up to request clarification or add additional context in comments.

9 Comments

as an aside: If we didn't need ksh compatibility and could restrict this to bash 5.0 and later, I'd be suggesting ${_rc@Q} just to foreclose any possibility that _rc could get a non-integer / attacker-controlled value. Shouldn't ever be relevant if the flow control doesn't allow this to be run other than behind _rc=$?, but extra robustness is extra robustness.
@CharlesDuffy oops, yeah. I recently came from stackoverflow.com/questions/79656811/… and forgot this isn't escaped
this is very clever. why does the variable stick around untill after eval'd though? Also It still complains about returning when the script is not sourced. I will try to use this as the base for the logic I used before
@guest you could try: eval "unset _rc; (exit $_rc)". I think the answer is meant to be used in the if/return leg of the code in your question
@guest the variable doesn't stick around. Its value is interpolated into the string. Then eval just sees the constant.
|
4

You could use this, to inject the _rc value before unsetting it

_rc=42
{
    unset _rc
    return "$(cat)"
} <<< "${_rc}"

Or as oneliner

{ unset _rc; return "$(cat)"; } <<< "${_rc}"

5 Comments

Better to use echo "$value" -- don't want IFS=1 to change 10 to 0.
@CharlesDuffy: Or maybe return $(cat)?
Reasonable -- { unset _rc; return "$(cat)"; } <<<"$_rc" should work fine -- though the quotes are still called for around the substitution to prevent word-splitting. Main argument that comes to mind against that is that cat is an external executable to run, so read+echo is going to be faster.
@CharlesDuffy Yes, you are right about IFS. IMHO your $(cat) variant looks best
This was a good answer and I +1'd. The eval method worked better for me when _rc was entirely controlled by me and I marked it as a solution but this answer has an advantage over the eval one if you need information form a variable that a user has input to
2

I'm adding this as an extension of @KamilCuk's answer for people like me who needed a little extra info to fill in the gaps. This is the complete solution that works when sourced in bash/ksh and when executed:

#!/bin/bash
# inputs for testing
_rc=2 # for testing

(
  [[ -n $KSH_VERSION && "$(cd -- "$(dirname -- "$0")" && pwd -P)/$(basename -- "$0")" != "$(cd -- "$(dirname -- "${.sh.file}")" && pwd -P)/$(basename -- "${.sh.file}")" ]] || 
  [[ -n $BASH_VERSION ]] && (return 0 2>/dev/null)
) && _was_sourced=1 || _was_sourced=0

#solution
if [ "${_was_sourced}" == 1 ]; then
    unset _was_sourced
    eval "unset _rc; (return $_rc)"
else 
    unset _was_sourced
    eval "unset _rc; (exit $_rc)"
fi

4 Comments

Aside: When aiming for portability, better to use = than == in [. See pubs.opengroup.org/onlinepubs/9799919799: behavior of [ s1 = s2 ] is standardized; [ s1 == s2 ] is not.
BTW, you can use (exit $_rc) even when was_sourced is true, because the exit only applies to the subshell that the parens create.
Why do you need to unset anything in the non-sourced case?
@Barmar Its a good point, I dont. I could have just exited with the desired run code in the else statement there

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.