Adam Ryczkowski
Adam Ryczkowski

Reputation: 8064

How to perform a variable expansion of the string variable in bash?

I am writing a simple bash debugging tool, log_next_line. The intended behavior is when the log variable is defined it reads next line of the script, expand variables on it, writes it to the file specified by $log, executes the command, and capture its output to the same log.

I have the problem with the variable expansion. Suppose I have a line of bash code in variable $line. How to expand the strings in it without executing any external programs, and without executing pipes (which is dangerous)?

The simple line=$("echo $line") doesn't work at all. When I try line=$(bash -c "echo $line") I have better luck, but then I need to export ALL bash variables to the spawned bash (which I have no idea how to do). And calling the external program for it seems like a overkill; besides the bash will execute the pipes and external programs, if e.g. line="echo $(rm -r /)".

Is there any way to do the variable expansion that will not involve writing a Bash parser from scratch ;-) ? I only need it to work under Linux (Ubuntu >= 14.04 to be precise).

For a full picture I include the prototype of the function:

function log_next_line()
{
    if [ -n "$log" ]; then
        file=${BASH_SOURCE[1]##*/}  #Takes path to the file, from where the function is called
        linenr=$((${BASH_LINENO[0]} + 1 )) #Line number
        line=`sed "${linenr}q;d" $file` #Reads this line number from the source file
        line=$("echo $line") #DOESN'T WORK. I want it to do a variable expansion
        echo "\$ $line" >>$log #Writes the variable-expanded line to the log
        $line >>$log 2>>$log #Executes the line and 
        exitstatus=$?
        if [ "$exitstatus" -ne "0" ]; then
            echo "## Exit status: $exitstatus" >>$log
        fi
        echo >>$log
        BASH_LINENO[0]=$((BASH_LINENO[0] + 1)) #Skips executing of the next line, since we have already executed it (and captured its output)
        if [ "$exitstatus" -ne "0" ]; then
            exit $exitstatus
        fi
    fi
}

And the simple script that can be used as a test case

#!/bin/bash

log=mylog.log

. ./log_next_line.sh

rm mylog.log

a=4
b=example
x=a

log_next_line
echo $x
log_next_line
echo ${!b}
log_next_line
touch ${!b}.txt > /dev/null
log_next_line
touch ${!x}.txt
log_next_line
if [ (( ${#a} - 6  )) -gt 10 ]; then echo "Too long string";fi
log_next_line
echo "\$a and \$x"|tee file.txt

unit tests:

if x=a, a="example" then I want the following expansions:

  1. echo $x should be echo a.
  2. echo ${!b} should be echo example
  3. touch ${!b}.txt>/dev/null should be touch example.txt>/dev/null
  4. if [ (( ${#a} - 6 )) -gt 10 ]; then echo "Too long string";fi should be if [ 1 -gt 10 ]; then echo "Too long string";fi
  5. echo "\$a and \$x"|tee file.txt should be echo "\$a and \$x"|tee file.txt"

This question is a generalization of the https://unix.stackexchange.com/questions/131150/bash-is-it-safe-to-eval-bash-command . The answers given there don't pass all my test cases.

Upvotes: 3

Views: 349

Answers (1)

user3159253
user3159253

Reputation: 17455

the function could be implemented as a simple set -x to turn on tracing at a given point of a script and set +x to turn it off.

If you definitely wish to implement it as a one line flag, I would take a look at this question and configure -x flag through DEBUG trap.

Upvotes: 1

Related Questions