Two exec commands in one shell script?

Hi Folks -

Is there a way to add two execs to one script? For instance, I need to redirect the stdout and stderr to two separate directories. I want to do this:

#::-- Direct STDOUT and STDERROR to repositories --::# 
exec 2>"${_ERRORFILE}" > "${_LOGFILE}"
exec 2>"/new/path/file.err" > "/new/path/file.log"

But obviously only the last exec is being used. Any tricks?

Thanks!

What's your OS and shell versions?

HI RudiC-

  GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
  

Thanks!

With that bash version, providing "process substitution", this might work for you:

exec > >(tee ZZ > XX) 2> >(tee ZZZ > XXX)
1 Like

Actually there are - but not using exec . Let me first explain the use of exec , then what you can do to fulfill your requirement.

The keyword exec is used for two very different purposes: the first is to replace (instead of fork) the current process. Consider this script part:

command1
command2

What happens when the shell executes this is:

1) create a subprocess and start command1 in this
2) wait until this subprocess has finished
3) create another subprocess and start command2 in it
etc..

As long as it takes i.e. command1 to run you would see the process of your script (say, yourscript.sh ) in the process list as parent process and command1 as child process. exec instead will replace the running script (effectively stopping its execution) with the command in question, which will take over the process number and everything else of the - up to now - running script. That means:

exec command1
command2

would be pointless because after the first line being executed the script is not running any more and hence command2 will never be executed. So, in this sense, two exec in one script would not make sense - except, of course, if there is some condition attached to it:

if <condition> ; then
     exec command1
else
     exec command2
fi

The second purpose - without any command but a redirection instead - of exec is to redirect I/O as you have done. The problem regarding your goal is not so much exec itself but a property of UNIX processes: processes have (per default) one input - <stdin> - and two outputs: <stdout> and <stderr>. Any of these "I/O descriptors", as the correct wording is, can point to any source providing a data stream: a file, a device, a pipeline, ... exec > /some/where is just a fancy way of directing the <stdout> I/O descriptor to that certain file.

You can use several such exec s to re-redirect these streams to various sources/targets, but each such redirection will overwrite the previous one. Not because of the way exec works, but because each I/O-descriptor can either point this way or that way, but not both. Picture a UNIX process as a sort-of garden hose: you fill something in at one end and something comes out on the other. You can use what comes out to fill this bucket or that bucket, but not both at the same time.

For such a thing - to fill both buckets at the same time - you would need a "T-piece", as plumbers call it: something to divide the water stream into two water streams. Exactly this analogy inspired the makers of UNIX to create the tee utility. You can use it to duplicate an output stream so that it will go to two different targets at the same time. You cannot use that inside the script but you can use it outside with a redirection:

yourscript.sh | tee /some/file >/other/file

will write <stdout> of yourscript.sh to both /some/file and /other/file . I suggest you read the man page of tee to get acquainted. If you still need help just ask.

I hope this helps.

bakunin

Hi Rudi -

That worked great!!! I do have one additional follow up. I also use a trap command to remove the error file and error directory (if empty) at the end as with exec, they need to be created first.

My current trap is only working for what I have speicfic. How would I add in the a second trap statement? Do I need to do this in a function?

#!/bin/bash
#::--:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#::-- Script Name: CFPBCSDDI.sh                                               --::
#::                                                                           --::
#::-- Description: Executes Cloud Artifact Snapshot Backup                    --::
#::                                                                                                                                                                                                                                                                                     --::
#::                                                                                                                                                                                                                                                                                     --::
#::                                                                                                                                                                                                                                                                                                 --::
#::--  Calls:      _env.sh                                                    --::
#::--  Called By:  N/A                                                        --::
#::                                                                                                                                                                                                                                                           --::
#::-- Parameters:  Not Applicable                                                                                                                                                                                 --::
#::                                                                                                                                                                                                                                                           --::
#::-- Author:      XXX                                                                                                             --::
#::-- Date:                   12/31/17                                                                       --::
#::                                                                                                                                                                                                                                                                                                 --::
#::--:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

#::-- Set Environment File --::#
source "$PWD/_env_DDI.sh"

#::-- Prepare File Name Format --::#
#::   _SN          = Script Name with extension
#::   ${_SN%%.sh*} = Script name without extension
#::   _FN          = File Name

_SN="${0##*/}"
_FN="${_DATESTAMP}_${_TIME}_${_SN%%.sh*}"

#::-- Set Log & Error subdirectories pertaining to the specific process --::#
_PLOGPATH="${_SN%%.sh*}_Logs/"
_PERRORPATH="${_SN%%.sh*}_Errors/"

#::-- Establish STDOUT and STDERROR repositories --::
_INTRAPATH="${_LOGPATH}${_PLOGPATH}${_YEAR}_${_MONTH}${_DAY}"
_ERRORINTRAPATH="${_ERRORPATH}${_PERRORPATH}${_YEAR}_${_MONTH}${_DAY}"

for _DIR in "${_INTRAPATH}" "${_ERRORINTRAPATH}"; do mkdir -m777 -p "${_DIR}"; done

#::-- Establish STDOUT and STDERROR files --::#
_LOGFILE="${_INTRAPATH}/${_FN}.log"
_ERRORFILE="${_ERRORINTRAPATH}/${_FN}.err"

#::-- Direct STDOUT and STDERROR to repositories --::# 
#exec 2>"${_ERRORFILE}" > "${_LOGFILE}"

_CTRLM_OUTPUT="/cfit/ctrlm/dev/links/logs"
exec > >(tee "${_CTRLM_OUTPUT}/${_FN}.log" > "${_LOGFILE}") 2> >(tee "${_CTRLM_OUTPUT}/${_FN}.err" > "${_ERRORFILE}")

#::-- Delete YYYY_MMDD error file subdirectory if empty --::
trap "[ -s ${_ERRORFILE} ] || rm -f ${_ERRORFILE} && rmdir ${_ERRORINTRAPATH}" EXIT

#::-- Additional Variable Assignment --::#  

_SNAPSHOT="Artifact Snapshot"
_TARGETPATH="${_BACKUPPATH}${_SNAPSHOT}"; mkdir -m777 -p "${_TARGETPATH}"
_CNT="0"

#::--------------------Initialize Functions--------------------------::#
 

A trap is code which is executed when a script receives a certain signal (like, via the kill command or via the keyboard, i.e. pressing <CTRL>-<C>). Since each signal can only have one reaction attached to it you cannot specify two trap s for the same signal although you can specify traps for different signals.

Notice that - depending on your shell - there might also be a "pseudo-signal" raised when leaving a script via the normal exit. This is no real signal at all but it can be trapped the same way any other signal can. In bash this is done like this:

#! /bin/bash

function myexit
{
<put all that needs to be executed upon exit here>
}

# main code starts here:
trap myexit EXIT
<rest of your code>

The function myexit() will be executed when the script is left through the exit command.

I hope this helps.

bakunin

What keeps you from extending the trap to deal with any number of files you created and want deleted when exit ing yopur script? If that is getting lengthy, why not collect all of it in a function and calling that in the trap ?
Having said that, the reason why someone would need two files per output channel that, on top, will be deleted on exit escapes me.