12

I have file.txt with command stored in one line (this command is valid when running in console) and I want to execute it in one line with sh like

cat file.txt | eval

what is missing? any protips?

and what if I have file with many commands (one for each line) and I want to execute only one command (one whole line)? my first idea is:

head -n5 | tail -n1 | eval
Mikel
  • 57,299
  • 15
  • 134
  • 153
quester
  • 273

4 Answers4

19

eval does not read its command string from stdin.

eval "$(cat file.txt)"
# or easier, in ksh/bash/zsh
eval "$(<file.txt)"
# usually you cannot be sure that a command ends at the end of line 5
eval "$(head -n 5 file.txt)"

Instead of eval you can use standard . or bash/zsh/ksh source if the commands are in a file anyway:

source ./file

(note that it's important to add that ./. Otherwise source looks for file in $PATH before considering the file in the current directory. If in POSIX mode, bash would not even consider the file in the current directory, even if not found in $PATH).

That does not work with choosing a part of the file, of course. That could be done by:

head -n 5 file.txt >commands.tmp
source ./commands.tmp

Or (with ksh93, zsh, bash):

source <(head -n 5 file.txt)
Hauke Laging
  • 90,279
4

so... a solution to your question is 100% possible, and (in the context of make) important.

I ran into this with a makefile, and given the difficulty of nesting bash commands in a makefile which already uses $(...) to call variables, it is nice to be able to do exactly what you are asking about.

Rather than using eval, just use awk or perl's system command:

// command_list.sh:
echo "1"
echo "2"
echo "3"

// command line prompt:
$: cat command_list.sh | awk '{system($0)}'
1
2 
3

And, command building:

// a readable version--rather than building up an awk miniprogram, 
// split into logical blocks:

$: cat inputs.txt | awk '{print "building "$1" command "$2" here "}' | awk '{system($0)}' 
Chris
  • 961
  • 7
  • 20
  • Beware that this runs the commands in a separate bash instance, so if you are trying to do something like set variable values, this will fail in such a case. – b_laoshi Mar 01 '19 at 00:36
  • @b_laoshi well it won’t set the variables globally, if that is what you mean by “fail”. You would be better off “source”ing a config.sh file than nesting variable declarations in a nested shell. – Chris Mar 01 '19 at 07:02
  • @b_laoshi anyhow, this problem is the same problem as running with a bash file—see this answer: https://stackoverflow.com/questions/1464253/global-environment-variables-in-a-shell-script – Chris Mar 01 '19 at 07:07
  • 1
    Yes, I see that, but that's not the point of my comment. The point is that this is solution as is will fail when simply trying to set variables. It's far simpler to take Hauke Laging's approach when setting variables or running any command that needs to run in the same shell instance. – b_laoshi Mar 04 '19 at 00:26
  • @b_laoshi ah, so you are marketing a different question? Or is your point just a non sequitor? – Chris Mar 05 '19 at 11:29
  • 1
    Neither, I had a file containing variable assignments. I copy and pasted your command, and it did not have the intended effect of evaluating the text in my file to assign variables in the working scope. My comment serves to simply warn anyone who might try something similar that your answer, as presented, creates a separate subshell in which the commands will indeed run. However, if the essence of those commands is to effect change in the current shell, this answer will not. – b_laoshi Mar 06 '19 at 02:46
3

Technically it's impossible to evaluate something within your current environment if you want to read it from a pipe, since each command in the pipeline executes as a separate process.

However, depending on what you're trying to accomplish, that might not matter. In any case, here's running eval against the contents of a pipe:

❯ echo echo hi | eval "$(cat -)"
hi

Here's an example of the catch. Something like the following won't print 1:

❯ echo a=1 | eval "$(cat -)"; echo $a

We'd have to do the following instead:

❯ echo a=1 | { eval "$(cat -)"; echo $a; }
1
3

If you aren't determined to use eval, use sh:

cat file.txt | sh

Yes, it will run in a separate sh instance, but that fact is self-documented.

And there's also while read:

cat file.txt | while read cmd; do eval $cmd; done

To execute the command on the 5th line of file.txt:

sed -n 5p file.txt | sh

(I realize this is an old question.)

kendall
  • 31