The last line in this script won't work as I expect:
myfile="afile.txt"
mycmd='cat $myfile'
eval $mycmd
echo eval $mycmd
Here echo eval $mycmd prints eval cat $myfile.
How can I print eval cat afile.txt?
Let's take things step by step:
When you do this:
mycmd='cat $myfile'
You prevent the shell from interpolating $myfile. Thus:
$ echo $mycmd
cat $myfile
If you want to allow the interpolation, you can use double quotes:
$ mycmd="echo $myfile" #Double quotes!
$ echo "$mycmd"
cat afile.txt
This, of course, freezes the interpretation of $mycmd when you do an eval.
$ myfile="afile.txt"
$ mycmd="echo $myfile"
$ echo $mycmd
cat afile.txt
$ eval $mycmd #Prints out afile.txt
$ myfile=bfile.txt
$ eval $mycmd #Still prints out afile.txt and not bfile.txt
Compare this to:
$ myfile="afile.txt"
$ mycmd='cat $myfile' #Single quotes hide $myfile from the shell
echo $mycmd
cat $myfile #Shell didn't change "$myfile", so it prints as a literal
$ eval $mycmd #Prints out afile.txt
$ myfile=bfile.txt
$ eval $mycmd #Now prints out bfile.txt
What you probably want to do is to evaluate the $mycmd in an echo statement when you echo it:
$ echo $(eval "echo $mycmd")
$ cat afile.txt
$ myfile=bfile.txt
$ echo $(eval "echo $mycmd")
cat bfile.txt
You can write:
eval echo eval $mycmd
or a bit more robust:
eval echo eval "$mycmd"
That said, I'd recommend avoiding eval whenever possible; it tends to be very fragile, because there are many complicated steps in Bash command-line processing, and use of eval typically means you will perform those steps more than once. It's hard to keep track of what's really going on.