0

I have a script like this:

#!/usr/bin/bash
COMMAND='some_command >> some_log_file 2>&1'
until $COMMAND; do
    echo "some_command crashed with exit code $?.  Respawning.." >&2
    sleep 1
done

(I got the until ... done bit from https://stackoverflow.com/a/697064/88821 , FWIW, but changed it a bit.)

While some_command is being run, the problem is that the output is not going to some_log_file. Instead, it goes to the stdout of the shell from which I ran this wrapper script. How can I get the output to go to some_log_file while keeping the entire command (including redirects) in the variable COMMAND? The idea is to use this script as a more general wrapper script that can be used with other programs. (COMMAND is actually being passed as an argument into the script).

Eddified
  • 3,085
  • 8
  • 36
  • 47

2 Answers2

1

You're passing >> some_log_file 2>&1 as arguments to some_command, rather than honoring them as redirections. This happens because parsing shell syntax (such as redirections) happens before parameter expansions are performed (the point in processing where $foo is replaced with the contents of the relevant variable). That's actually desirable behavior -- it would be impossible to write code in shell handling untrusted data otherwise.

Don't store code in strings. You can include it literally:

until some_command >> some_log_file 2>&1; do
    echo "some_command crashed with exit code $?.  Respawning.." >&2
    sleep 1
done

...or you can store it in a function:

mycode() { some_command >> some_log_file 2>&1; }

until mycode; do
    echo "some_command crashed with exit code $?.  Respawning.." >&2
    sleep 1
done
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
0

One way is

#!/usr/bin/bash
COMMAND='some_command >> some_log_file 2>&1'
until bash -c "$COMMAND"; do
    echo "some_command crashed with exit code $?.  Respawning.." >&2
    sleep 1
done

The problem with your approach is that it does not evaluate the variable as bash code, but just as a series of literal strings. It's like how putting "1+1" in a Java string will never cause 2 to be printed without invoking a Java interpreter:

String cmd="1+1";
System.out.println(cmd);  // Prints 1+1
System.out.println(1+1);  // Prints 2
that other guy
  • 116,971
  • 11
  • 170
  • 194
Wilfredo Pomier
  • 1,091
  • 9
  • 12
  • Nice try, but now the `until` loop doesn't work. I.e. when I kill the process, it is not automatically restarted again. I guess the `bash` is just returning 0 every time. – Eddified Sep 14 '17 at 23:01
  • 1
    @Eddified, bash returns the exit status of the last command it runs. In this case, that *is* the exit status of `some_command`. I don't like this answer one bit (the practice it describes don't work in more interesting cases -- ie. with arguments taken from variables -- unless you modify it in ways that introduce security holes, or pass those variables through the environment), but it certainly doesn't break the functionality of the `until` loop (which is to say that I suspect the way you're killing the process isn't actually resulting in a nonzero exit status, or is killing the shell). – Charles Duffy Sep 14 '17 at 23:02
  • @CharlesDuffy, be that as it may, it still doesn't work. It *does* break the functionality in my case, I do not know why. If I go back to my original code (as seen in the question), then the auto-restarting works again. Here is how I'm killing it: `$ kill `. I've tried it several times, I know I'm not killing the shell. – Eddified Sep 14 '17 at 23:06
  • @Eddified, which pid, *specifically*? There are at least three of them -- the parent shell, the child shell started with `bash -c`, and then the command. And the exit status of a command that receives a SIGTERM is under that command's control (if it traps the signal, it can implement any kind of handler it wants) -- do you *know* that exit status is nonzero for your `some_command` implementation? – Charles Duffy Sep 14 '17 at 23:12
  • @Eddified, ...incidentally, if you made it `bash -c 'exec some_command >>some_log_file 2>&1'`, that `exec` would mean that your `some_command` would inherit the PID of the `bash` interpreter that starts it, so you'd avoid extending your call chain unnecessarily. Which still wouldn't make starting an extra interpreter a *good idea*, but would at least reduce the amount of damage being done by pursuing it. – Charles Duffy Sep 14 '17 at 23:16
  • the child shell started with `bash -c` is not appearing in `ps` output. I see the same number of total processes (2) whether with `until $COMMAND; do` or `until bash -c "$COMMAND"; do` .. and I don't understand why. – Eddified Sep 14 '17 at 23:16
  • @Eddified, some versions of bash `exec` the last command implicitly when there are no traps set or other places where the shell can determine that it needs to stay open. It's possible that you may have one of those. – Charles Duffy Sep 14 '17 at 23:17
  • Thank you. So, why then the different behavior of the loop? Any other ideas? – Eddified Sep 14 '17 at 23:17
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/154491/discussion-between-charles-duffy-and-eddified). – Charles Duffy Sep 14 '17 at 23:18