0

I'm working with a set of scripts with functions treated as readonly. The functions are more than just a list of commands, for example, there can be loops and change directories and even calls to other functions:

func() {
    cd folder/
    run command1
    mkdir folder2/ ; cd folder2/
    run command2
}

For a moment, pretending that I could change the scripts, to show you what I want one might look like this:

func() {
    cd folder/
    string[0]="command1" ; run command1 |& tee out0.log ; result[0]="$?" ; finished_command_number 0
    mkdir folder2/ ; cd folder2/
    string[1]="command2" ; run command2 |& tee out1.log ; result[1]="$?" ; finished_command_number 1
}

So, for commands which can be piped, but not for cd or loops, I want to store a string, store the stdout (stderr), store the exit code, and run another command afterwards. However, I cannot add this in, it must be done in my script which is invoking the one with func().

To just get the post command call functionality alone, I've tried copying the function and running it from my script with a trap foo debug, but that doesn't seem to propagate into functions. I don't think literally copying line by line will work because the cd and loops can't really be isolated since they are control statements rather than subshell commands.

If the strings can just be a function of the commands themselves, that's still useful.

  • 1
    From the bash manual: "the DEBUG and RETURN traps are not inherited unless [...] the -o functrace option has been enabled with the set builtin, (in which case all functions inherit the DEBUG and RETURN traps)" – muru Apr 24 '18 at 12:23
  • @muru I used functrace to come up with an answer. – Timothy Swan May 1 '18 at 22:16
1

If I'm reading your question correctly, you're looking for a way to:

  • Capture stdout and stderr independently from the outputs of a function call
  • Preserve the current working directory against any changes to it in the course of a function's execution

This can be done rather simply:

#!/bin/bash
stdout=/path/to/output.std
stderr=/path/to/output.err

somefunc() {
    : Do things
}

( somefunc arg1 arg2 ) >> $stdout 2>> $stderr

Running the function in a subshell will allow it to do whatever it needs to with respect to changing the working directory, and outputting to both stdout and stderr as normal. When the subshell exits, the working directory of the script is unchanged, and all of the stdout and stderr outputs are caught and redirected into your log files.

Adding a (global or otherwise) array in which to capture exit codes of commands is trivial:

declare -a exitcodes
( exit 0 )
exitcodes+=( $? )
( exit 2 )
exitcodes+=( $? )
( exit 0 )
exitcodes+=( $? )

echo ${exitcodes[@]}
  • No, I'm not looking to preserve the working directory. That can be reset to whatever I want when I call the function after each command. I want to 1. store a string for each command. 2. store the output 3. store the exit code and 4. run a function afterwards. Because of number 4. I can reset the directory but obviously I prefer not to because otherwise the script can't be ran standalone. It seems like you didn't understand my question. If you truly did, then show an example of a script which can be ran both standalone and with the 4 attributes listed. – Timothy Swan Apr 23 '18 at 13:12
  • 1
    Perhaps, then, you should outline this more clearly in your question rather than in a comment on an answer. – DopeGhoti Apr 23 '18 at 15:33
  • No. In the question, it is clear, even with an example that 4 things need to occur: string[1]="command2" ; run command1 |& tee out1.log ; result[1]="$?" ; finished_command_number 1 Obviously running cd folder/ inside a pipe will not even syntactically work out well and won't do any good, so it's fundamentally different than running a command which does syntactically work out in that situation and does perform its function inside of a pipe. – Timothy Swan Apr 23 '18 at 17:04
1
#!/bin/bash
# test.sh
post() {
        echo "post [$BASH_COMMAND] [$?]"
        echo "== $RANDOM =="
}
set -o functrace
trap post debug
func() {
        . check.sh
        tryme |& tee out.txt
}
func

The output can be filtered by the lines marked with random. I should test this further to see how well it works with multiple processes, but it seems to work just fine with short lived commands. Notice the exit code lags by one command since debug is apparently called first.

#!/bin/bash
# check.sh
tryme() {
        echo "one"
        echo "two"
        mkdir -p hello
        cd hello/
        echo "three"
        false
        echo "four"
}

===

$ bash test.sh 
post [func] [0]
== 22542 ==
post [func] [0]
== 10758 ==
post [. check.sh] [0]
== 9115 ==
post [tryme 2>&1] [0]
== 11979 ==
post [tee out.txt] [0]
== 17814 ==
post [tryme 2>&1] [0]
== 22838 ==
post [echo "one"] [0]
== 5251 ==
one
post [echo "two"] [0]
== 18036 ==
two
post [mkdir -p hello] [0]
== 4247 ==
post [cd hello/] [0]
== 21611 ==
post [echo "three"] [0]
== 24685 ==
three
post [false] [0]
== 8557 ==
post [echo "four"] [1]
== 7565 ==
four

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.