bashparameter-passingrefactoring

Is there a way to make the output from one bash command go to a user selected location?


I'm writing a bash script that amongst other things, starts a user specified command, sending the output to either file, file and stdout, or just stdout as required.

I don't like the duplication of the line that runs the user command which has different endings depending on the output type wanted.

#!/bin/bash

# messy but works
function my_function() {

    local pid_file=$1; shift
    local logfile=$1; shift
    local tee_output=$1; shift
    local cmd=$*; shift

    # do some stuff

    { # codeblock to allow redirection of all content by default to somewhere different
    
        # do some stuff
    
        # This IF Block runs the user specified cmd. Output will go to different locations as specified
        if [ "$logfile" = "NO_LOG" ] ; then
            # ALL Output -> default stdout and default stderr
            sh -c "echo \$\$ > '$pid_file'; $cmd" 1>&3 2>&4 &
        else
            if [ "$tee_output" -eq 1 ] ; then
                    # ALL Output -> logfile AND default stdout and default stderr
                    sh -c "echo \$\$ > '$pid_file'; $cmd" 1>&3 2>&4 | tee "$logfile" &
                else
                    # ALL Output -> logfile
                    sh -c "echo \$\$ > '$pid_file'; $cmd" >> "$logfile" &
                fi
        fi
        
        # do some stuff
        
    } >> "script_output.log" 2>&1 ; # This will redirect stderr and stdout for this codeblock to file
    
    # do some stuff
    
}

exec 3>&1 4>&2 ; # make a copy of the default stdout and stderr destinations
my_function cmd.pid output.log echo hi

Is there a way of rewriting this horrible IF block so that the various options are appended to the end of the sh -c "echo \$\$ > '$pid_file'; $cmd" part that remains constant?

I think something like the following statement might be the answer, but I can't quite get it working. Can you show me a neat way of doing this please? Thanks.

# possible idea for a neater solution (not working)
sh -c "echo \$\$ > '$pid_file'; $cmd" $(if [ "$logfile" = "NO_LOG" ] ; then
            echo '1>&3 2>&4 &' ;# send output to console
          else
            if [ "$tee_output" -eq 1 ] ; then
                echo '1>&3 2>&4 | tee $logfile &' ; # send output to console AND logfile
            else
                echo '>> "$logfile" &' ; # send output to logfile
            fi
          fi)

Solution

  • Overall, it's exec this>there everywhere.

    #!/bin/bash
    my_function() {
        local pid_file=$1; logfile=$2
        local tee_output=0
        if [ "$3" = "tee" ] ; then
            tee_output=1
            shift
        fi
        cmd=("${@:3}")
        {
            echo "executing ${cmd[@]}"
            if [[ "$logfile" = "NO_LOG" ]]; then
                exec 1>&3 2>&4
            else
                if [ "$tee_output" = "1" ] ; then
                        exec 1>&3 2>&4
                        exec 1> >(tee "$logfile")
                    else
                        exec >>"$logfile"
                    fi
            fi
            
            echo "$BASHPID" > "$pid_file"
            exec "${cmd[@]}"
        } >>"script_output.log" 2>&1    
    }
    exec 3>&1 4>&2
    my_function cmd.pid output.log echo hi
    

    Also, service managers is an old topic. Consider just using systemd or systemd-run nowadays.