4

I'm trying to write a bash script where every command is passed through a function that evaluates the command using this line:

eval $1 2>&1 >>~/max.log | tee --append ~/max.log

An example of a case where it does not work is when trying to evaluate a cd command:

eval cd /usr/local/src 2>&1 >>~/max.log | tee --append ~/max.log

The part the causes the issue is the | tee --append ~/max.log part. Any idea why I'm experiencing issues?

  • What doesn't work ? It doesn't cd into the directory ? – edi9999 Sep 19 '15 at 18:51
  • 1
    cd doesn't work well with pipes, `cd folder | ls` for example won't put you in "folder" either – edi9999 Sep 19 '15 at 18:52
  • Can you move the mainfunction to a function and use `tee` for the function output? Something like `function mymain { ... }` and `mymain 2>&1 | tee -append ~/max.log` ? Or use something like `script` ? – Walter A Sep 19 '15 at 19:07
  • @edi9999 It doesn't change directory when the | tee --append ~/max.log is appended – ProfessorManhattan Sep 19 '15 at 19:13
  • To redirect and append both stdout and stderr to a file, see http://stackoverflow.com/questions/876239/how-can-i-redirect-and-append-both-stdout-and-stderr-to-a-file-with-bash . – dave_thompson_085 Sep 20 '15 at 08:10

1 Answers1

6

From the bash(1) man page:

Each command in a pipeline is executed as a separate process (i.e., in a subshell).

Therefore, cd can not change the working directory of the current shell when used in a pipeline. To work around this restriction, the usual approach would be to group cd with other commands and redirect the output of the group command:

{
    cd /usr/local/src
    command1
    command2
} | tee --append ~/max.log

Without breaking your existing design, you could instead handle cd specially in your filter function:

# eval all commands (will catch any cd output, but will not change directory):
eval $1 2>&1 >>~/max.log | tee --append ~/max.log
# if command starts with "cd ", execute it once more, but in the current shell:
[[ "$1" == cd\ * ]] && $1

Depending on your situation, this may not be enough: You may have to handle other commands that modify the environment or shell variables like set, history, ulimit, read, pushd, and popd as well. In that case it would probably be a good idea to re-think the program's design.

Michael Jaros
  • 4,586
  • 1
  • 22
  • 39
  • +1 for the cause. Also need to special-case anything that sets or declares an envvar or shell variable, if those settings are used. Including `read` etc. And `set` of `$*` and/or shell options. Or alters `ulimit` settings. Or `history`. And maybe more I've missed. Easier to redirect correctly. – dave_thompson_085 Sep 20 '15 at 08:13
  • @dave_thompson_085: Thanks, I've made the respective statement more general to include everything that modifies environment or shell variables. – Michael Jaros Sep 20 '15 at 10:42