1

I have a bash script listening on a pipe for commands. When it sees a command it runs it. However, I would like it to support the "sideloading" of bash functions that can be called at a later time.

Here are some input/output examples to give you an idea of what the script should be doing:

# left of the arrow is input, right of arrow is output
"echo 'test'" -> "test"
"_a() { echo 'test' }" -> ""
"_a" -> "test"

I have it working fine for normal commands, however am running into issues with trying to load functions. I suspect this is because I am executing user input in a subshell, so any function defined won't be available to the parent for future commands. I have tried using a file descriptor to store the data in (stdout and stderr of the eval), but could never get it working. I currently have something like this:

exec 3>&1
eval 'echo "Hello World"' >&3
read -u 3 var
exec 3>&- 
echo "$var"

Is this possible?

muru
  • 72,889
nopcorn
  • 9,559
  • I don't understand what you want.    Please give a simple, straightforward example of what you want to accomplish. – G-Man Says 'Reinstate Monica' Mar 24 '24 at 01:15
  • 1
    And, please, explain the big picture behind, what are you trying to achieve? – Gilles Quénot Mar 24 '24 at 01:22
  • I've added a bit more context – nopcorn Mar 24 '24 at 01:27
  • 1
  • Though I still don't understand the context. Are you getting literal code as input from a user and evaling it, and want to keep the evaled state after getting the output? – muru Mar 24 '24 at 01:33
  • Sorry, but I still don’t get it. Are you trying to have a script define functions that will subsequently be available in the interactive shell session in which the script is running? That’s going to be impossible. Are you trying to have a script define functions that will subsequently be available *in the script?* That should be possible — but the code that you show doesn’t particularly look like an attempt to do that. – G-Man Says 'Reinstate Monica' Mar 24 '24 at 01:36
  • @muru yes that's correct – nopcorn Mar 24 '24 at 01:47
  • @G-ManSays'ReinstateMonica' does muru's explanation make more sense? – nopcorn Mar 24 '24 at 01:48
  • muru’s comment sounds like the second option I offered in my comment.  Do you see a difference?  If that’s what you want, as I said, it should be possible. What have you tried?  As I said, I don’t see anything in your question that looks like an attempt to solve your problem. – G-Man Says 'Reinstate Monica' Mar 24 '24 at 01:56
  • Then the coproc method here: https://unix.stackexchange.com/a/354604/70524 should work – muru Mar 24 '24 at 02:00
  • @muru doesn't coproc spawn a new process? How would the child process be able to set new functions that are available to the parent? – nopcorn Mar 24 '24 at 11:05
  • Yes, but you're using the new process to capture the output from your current process, in which you will run the input code. The new process isn't used to run the input code. Check the example in the linked answer. – muru Mar 24 '24 at 12:24
  • 2
    Please [edit] your question to show a minimal, complete script that is attempting to do what you want to do (and nothing else!) and show the input and output (both desired and actual) with which it is failing so we can get a better understanding of your needs and have something to copy/paste to test with. – Ed Morton Mar 24 '24 at 14:23

1 Answers1

0

This might work for you:

$ cat runcmd
#!/usr/bin/env bash

tmpout=$(mktemp) || exit tmperr=$(mktemp) || exit trap 'rm -f -- "$tmpout" "$tmperr"; exit' EXIT

while IFS= read -r -a cmd; do printf '==========\n' declare -p cmd

eval "${cmd[@]}" > "$tmpout" 2>"$tmperr"

readarray -t out < "$tmpout"
readarray -t err < "$tmperr"

declare -p out
declare -p err

done

$ cat <<'EOF' | ./runcmd
echo 'test of echo with globbing * and variables $RANDOM and    spaces'
_a() { echo 'test of a()'; }
_a
awk 'BEGIN{print "multi line\noutput string"; print "and multi\nline error\nmessage" | "cat>&2"}'
EOF
==========
declare -a cmd=([0]="echo 'test of echo with globbing * and variables \$RANDOM and    spaces'")
declare -a out=([0]="test of echo with globbing * and variables \$RANDOM and    spaces")
declare -a err=()
==========
declare -a cmd=([0]="_a() { echo 'test of a()'; }")
declare -a out=()
declare -a err=()
==========
declare -a cmd=([0]="_a")
declare -a out=([0]="test of a()")
declare -a err=()
==========
declare -a cmd=([0]="awk 'BEGIN{print \"multi line\\noutput string\"; print \"and multi\\nline error\\nmessage\" | \"cat>&2\"}'")
declare -a out=([0]="multi line" [1]="output string")
declare -a err=([0]="and multi" [1]="line error" [2]="message")

but obviously writing a program to execute code provided as input is dangerous so please read Bash FAQ: I'm trying to put a command in a variable, but the complex cases always fail! and why-should-eval-be-avoided-in-bash-and-what-should-i-use-instead before implementing anything that does so.

Ed Morton
  • 31,617