How do I indirectly assign a variable in bash to t

2019-06-13 14:22发布

问题:

I have found many snippets here and in other places that answer parts of this question. I have even managed to do this in many steps in an inefficient manner. If it is possible, I would really like to find single lines of execution that will perform this task, rather than having to assign to a variable and copy it a few times to perform the task.

e.g.

executeToVar ()
{
    # Takes Arg1: NAME OF VARIABLE TO STORE IN
    # All Remaining Arguments Are Executed
    local STORE_INvar="${1}" ; shift
    eval ${STORE_INvar}=\""$( "$@" 2>&1 )"\"
}

Overall does work, i.e. $ executeToVar SOME_VAR ls -l * # will actually fill SOME_VAR with the output of the execution of the ls -l * command that is taken from the rest of the arguments. However, if the command was to output empty lines at the end, (for e.g. - echo -e -n '\n\n123\n456\n789\n\n' which should have 2 x new lines at the start and the end ) these are stripped by bash's sub-execution process. I have seen in other posts similar to this that this has been solved by adding a token 'x' to the end of the stream, e.g. turning the sub-execution into something like:

eval ${STORE_INvar}=\""$( "$@" 2>&1 ; echo -n x )"\" # <-- ( Add echo -n x )
# and then if it wasn't an indirect reference to a var:
STORE_INvar=${STORE_INvar%x} 
# However no matter how much I play with:
eval "${STORE_INvar}"=\""${STORE_INvar%x}"\"  
# I am unable to indirectly remove the x from the end.

Anyway, I also need 2 x other variants on this, one that assigns the STDIN stream to the var and one that assigns the contents of a file to the var which I assume will be variations of this involving $( cat ${1} ), or maybe $( cat ${1:--} ) to give me a '-' if no filename. But, none of that will work until I can sort out the removal of the x that is needed to ensure accurate assignment of multi line variables.

I have also tried (but to no avail):

IFS='' read -d '' "${STORE_INvar}" <<<"$( $@ ; echo -n x )"
eval \"'${STORE_INvar}=${!STORE_INvar%x}'\"

回答1:

This is close to optimal -- but drop the eval.

executeToVar() { local varName=$1; shift; printf -v "$1" %s "$("$@")"; }

The one problem this formulation still has is that $() strips trailing newlines. If you want to prevent that, you need to add your own trailing character inside the subshell, and strip it off yourself.

executeToVar() {
  local varName=$1; shift;
  local val="$(printf %s x; "$@"; printf %s x)"; val=${val#x}
  printf -v "$varName" %s "${val%x}"
}

If you want to read all content from stdin into a variable, this is particularly easy:

# This requires bash 4.1 for automatic fd allocation
readToVar() {
  if [[ $2 && $2 != "-" ]]; then
    exec {read_in_fd}<"$2"              # copy from named file
  else
    exec {read_in_fd}<&0                # copy from stdin
  fi
  IFS= read -r -d '' "$1" <&$read_in_fd # read from the FD
  exec {read_in_fd}<&-                  # close that FD
}

...used as:

readToVar var < <( : "run something here to read its output byte-for-byte" )

...or...

readToVar var filename

Testing these:

bash3-3.2$ executeToVar var printf '\n\n123\n456\n789\n\n'
bash3-3.2$ declare -p var
declare -- var="

123
456
789

"

...and...

bash4-4.3$ readToVar var2 < <(printf '\n\n123\n456\n789\n\n')
bash4-4.3$ declare -p var2
declare -- var2="

123
456
789

"


回答2:

what'w wrong with storing in a file:

   $ stuffToFile filename $(stuff)

where "stuffToFile" tests for a. > 1 argument, b. input on a pipe

   $ ... commands ... | stuffToFile filename   

and

   $ stuffToFile filename < another_file

where "stoffToFile" is a function:

function stuffToFile
{
    [[ -f $1 ]] || { echo $1 is not a file; return 1; }

    [[ $# -lt 2 ]] && { cat - > $1; return; }

    echo "$*" > $1
}

so, if "stuff" has leading and trailing blank lines, then you must:

   $ stuff | stuffToFile filename