0

Can somebody help on this:

I'm want to execute some commands in shell script by reading them from another text file something like this:

command_file.txt contains :

EXECUTED_OK_$(date)
EXECUTED_OK_$(hostname)

shell script.sh

#!/bin/sh

while read command
do
echo $command 
done < command_file.txt

My problem is, echo prints as it is text as in the command file. How can I make it expand and print actual date and hostname.

For example Expected output should be:

EXECUTED_OK_22-02-2010 11:10:10
EXECUTED_OK_localhost

But I'm getting:

EXECUTED_OK_$(date)
EXECUTED_OK_$(hostname)
Dark Matter
  • 300
  • 2
  • 15
  • Consider `envsubst` if it's available. – bishop May 21 '19 at 19:23
  • The behavior you're seeing is desired and to-spec. If data were expanded as code "behind your back", it would be basically impossible to write code handling untrusted data in bash. – Charles Duffy May 21 '19 at 19:40
  • 1
    (then again, you're basically *requesting that* your data be executed as code, which is exactly what `eval` is for. It's also a general bad idea and best avoided, but... well, it *is* what you're asking for). – Charles Duffy May 21 '19 at 19:43
  • @Charles Duffy If we consider a situation in which we have to create dynamic commands at runtime, it can be easier way rather then running individual commands and concatenating the final output. – Dark Matter May 21 '19 at 19:50
  • ...but if you want the effect of `eval`, why aren't you actually *using* `eval`? It's bad practice, but that's because *the thing it accomplishes* (breaching the boundary between data and code) is bad practice. You're actively asking for that thing (to evaluate your data as code, trusting its contents to be safe to execute), so, well, might as well use it if you're really, *really* sure that's what you want. – Charles Duffy May 21 '19 at 19:50
  • @Charles Duffy that's why I'm now going for function approach, I thought there may be some workaround for this situation except eval – Dark Matter May 21 '19 at 20:27

3 Answers3

1

This whitelisting approach will not allow arbitrary code to be executed like eval does. If security is not an issue, you should stick with eval, as externals used in this example have many corner cases for which the substitution will not work as expected.

The program substitutes certain externals referenced in each command of your command file with their output before printing the command:

#!/bin/bash

while read command
do
    while read -a external ; do
        output=$( "${external[@]}" )
        command=$( sed "s/\$(${external})/${output}/g" <<< "$command" )
    done <<END
    date -R
    hostname
END
    echo "$command"
done < command_file.txt

To use more externals, you need to add them to the heredoc between <<END and END. I have added -R to date to showcase an external with arguments.

As it has been pointed out, no processing (e.g. quotes, substitutions) except word splitting will be done on the external commands (blanks can be escaped (one\ two) instead of quoting).

Michael Jaros
  • 4,586
  • 1
  • 22
  • 39
  • `$($anything)` is not fully equivalent to `$(eval "$anything")`; see [BashFAQ #50](http://mywiki.wooledge.org/BashFAQ/050). – Charles Duffy May 21 '19 at 19:49
  • ...similarly, `read -a external` doesn't process quotes &c. the same way shell parsing does, so just cutting over to `"${external[@]}"` doesn't really fix the cases unquoted `$anything` gets wrong. (Try `touch 'hello world'` as an example case; quote-unaware string-splitting will have `touch` create two separate files, `'hello` and `world'`). – Charles Duffy May 21 '19 at 20:01
  • @CharlesDuffy Thanks, that would not have worked with arguments. I have modified my example to use an array. – Michael Jaros May 21 '19 at 20:01
1

use eval to expand the variables and re-execute it.

$ foo='EXECUTED_OK_$(date)'
$ echo $foo
EXECUTED_OK_$(date)
$ eval "echo $foo"
EXECUTED_OK_Tue May 21 12:39:45 PDT 2019

EDIT: eval is powerful but can be dangerous; it's basically code injection. Quoting the arguments is a good idea; see the comments below for more explanation.

evil otto
  • 10,348
  • 25
  • 38
  • 2
    `eval "echo $foo"` is a bit less buggy (or at least, a bit less misleading about how it works). If you run `eval echo $foo` without the quotes when `foo='*'`, for example, you get the `*` replaced with a list of filenames **before** `eval` is run, making its behavior almost impossible to predict without knowledge of those names. – Charles Duffy May 21 '19 at 19:41
  • (If you have `foo='*'; eval echo $foo`, you'd better hope that nobody created a file with `touch '$(rm -rf ~)'`; whereas with `foo='*'; eval "echo $foo"`, the `*` is expanded *after* the `eval`, so filenames it expands to can't be executed as code). – Charles Duffy May 21 '19 at 19:57
  • 1
    BTW, see [BashPitfalls #14](http://mywiki.wooledge.org/BashPitfalls#echo_.24foo) for a discussion of why it's a good idea to quote even in `echo "$foo"`. – Charles Duffy May 21 '19 at 20:05
0

One option is to use eval, assuming you trust the input file

#!/bin/sh

while read command; do 
  eval "echo $command" 
done < command_file.txt
zwbetz
  • 1,070
  • 7
  • 10