How can I populate a variable passed to bash script, which is called from another bash script? I want to know about options and best practices.
Let me elaborate why the many questions answered by either setting a exit status or echo
ing are not sufficient:
exit n
: n is restricted to 0 <= n <= 255echo foo
doesn't allow me to echo any relevant information in that script, alternate screen buffer also won't help with this.
I have figured out one possible solution:
#outer.sh
source inner.sh
populate result
echo "Evaluated: $result"
#inner.sh
function populate {
local __popvar=$1
eval $__popvar="'RETURN VALUE'"
}
I dislike this solution for three reasons:
- The need to
source
the inner script, polluting global scope with helper functions. - The need to
eval
, especially because of the confusing multi-quotes. - Verbosity. I first need to source, and then call one function, whereas I'd much rather like to
bash inner.sh result
.
Further information on the inner script
The inner script is supposed to write to the alternate screen buffer. On this buffer, the user is able to select an option from an array (selection via arrow keys or ijkl style, confirmation with space or enter). This option should be returned from the script somehow. Returning the index is not an option, as the number of elements in the array can exceed 256. Code:
#!/usr/bin/bash
prompt=$1; shift
options=( "$@" )
c_opts=${#options[@]}
selected=0
# switch to alternate screen and trap the kill signal to switch back
tput smcup
trap ctrl_c INT
function ctrl_c {
tput rmcup
exit 1
}
function print_opts {
for (( i = 0; i < $c_opts; i++ )); do
if [[ i -eq $selected ]]; then
echo -e "\t\e[7m ${options[i]} \e[0m"
else
echo -e "\t ${options[i]} "
fi
done
}
function reset_term {
for (( i = 0; i < $c_opts; i++ )); do
tput cuu1 # move cursor up 1 line
tput el # delete current line
done
}
function reprint_opts {
reset_term
print_opts
}
echo $prompt
print_opts
while read -sN1 key; do
read -sN1 -t 0.0001 k1
read -sN1 -t 0.0001 k2
read -sN1 -t 0.0001 k3
key+="${k1}${k2}${k3}"
# colemak layout
case "$key" in
n|u|$'\e[A'|$'\e0A'|$'\e[D'|$'\e0D') # up or left
((selected > 0)) && ((selected--));;
e|i|$'\e[B'|$'\e0B'|$'\e[C'|$'\e0C') # down or right
((selected < $c_opts-1)) && ((selected++));;
$'\e[1~'|$'\e0H'|$'\e[H') # home key
selected=0;;
$'\e[4~'|$'\e0F'|$'\e[F') # end key
((selected = $c_opts-1));;
' '|$'\x0a') # enter or space
tput rmcup && echo ${options[$selected]} && exit 0;;
q|$'\e') # q or escape
tput rmcup && exit 0;;
esac
reprint_opts
done
Based on @JohnKugelman's comment, the script should be called as follows:
prompt="Your options are:"
options=(
"Option A"
"Option B"
"Option C"
"Option D"
)
result=$( exec 3>&1; bash select-menu.sh "$prompt" "${options[@]}" 2>&1 1>&3; exec 3>&- )
echo $result
This does seem an appealing solution, but it does not fix the problem. The Selection menu that is to be printed on the alternate screen buffer is not printed. Input however works correctly and the selection is stored in result
.
To get a sense of the desired behavior you can replace the last two lines in the calling script like so:
bash select-menu.sh "$prompt" "${options[@]}"