Bash: expand variables, but not special characters

1.2k views Asked by At

I have a bash script that creates and executes an expect script by stitching together dozens of different files containing pieces of expect code. Those files contain environment variables that need to be expanded. Example:

expect.piece:

send "command\r"
sleep $timeout
send "command argument\r"

script.sh:

#let's try it like this
eval echo $(cat expect.piece)
#or maybe like this
eval "echo \"$(cat expect.piece)\""

output:

send command\r sleep 1 send command argument\r
send commandr
sleep 1
send command argumentr

Desired otput:

send "command\r"
sleep 1
send "command argument\r"

I need a solution without sed string substitution (there is a lot of environment variables) and without modifying original expect script files. I guess it could be done line by line, but is there a more elegant solution?

4

There are 4 answers

0
Ulrik On BEST ANSWER

I invented the solution for this problem, it is a kludge, but it works.

expect.piece:

sleep $timeout
send "foo bar\r"
send "$(date)\r"

script.sh:

timeout=1
eval echo $(sed " \
s/\\\/\\\\\\\/g; \
s/\"/\\\\\"/g; \
s/\"/\\\\\`/\"/\\\\\`/g; \
" "expect.piece" | tr '\n' '+') | tr '+' '\n'

output:

sleep 1
send "foo bar\r"
send "Wed Feb 18 03:19:24  2015\r"

First, we need to escape all the backslashes, backticks and quotes in the file, because they will be removed during the evaluation. Then, we need to replace all the newline characters with pluses, in order to make it in a single line. After that, we evaluate that line (the evaluation will "expand" all the environment variables and command substitutions) and replace pluses back to newlines.

2
RBH On

Default field separator in bash is a space so set input file separator as a new line like IFS=$(echo -e '\n') before executing eval echo $(cat expect.piece) .

Final script would be :

#Storing original field separator in variable OFS
OFS=$IFS
#Setting IFS as new line using echo -e. Unfortunately IFS="\n" does not work in bash
IFS=$(echo -e '\n')
eval echo $(cat expect.piece)
#Resetting the field separator as space
IFS=$OFS
0
Dinesh On

There is one another way which you can put your expect code with -c flag in the shell script as shown below.

script.sh

#Calling the expect.piece file code here
expect -c expect.piece

You can make use of the optional command line values as ,

expect -c "set count 1" myscript.exp

where the variable count will be used in the expect script file myscript.exp.

You can directly give the whole code as

expect -c "
             send \"command\r\"
             sleep $timeout
             send \"command argument\r\"
"

Notice the use of backslash to escape the double quotes wherever needed. Single quotes can also be used. But, if you use double quotes, then only shell substitution can happen.

Please refer here to know more about the -c flag in expect.

0
tripleee On

It's not clear from your question in what context you want this output. If it's okay to embed the Expect script as a here document, what you want is trivial.

#!/bin/sh

timeout=1

cat <<____HERE
send "command\r"
sleep $timeout
send "command argument\r"
____HERE

(Maybe you can even replace the cat with expect but I'm not familiar enough with Expect to make any recommendations.)

If you need to take the input from a file, and only have a limited set of variables you want expanded, you could do that with sed.

sed "s/\$timeout/$timeout/g" file

If you need a more general solution, you might want to swich to Perl:

perl -pe 's/\$(\w+)/$ENV{$1} || "\$$1" /ge' file

but this requires you to export (or otherwise expose to Perl) the shell environment variables you want exported. (This will just not substitute any undefined variables; change the value after || if you want to change that aspect of the behavior.)