0

When foo is run in the background, the BASHPID of foo (bashpid_of_foo) is not available inside the bodies bar_1 to bar_n via $BASHPID, since they get invoked via the Command Substitution feature of bash:

function foo() {
    local bashpid_of_foo=$BASHPID
    local output

    # desired to be shared by all Command Substitutions
    # in the body of this function.
    local log=/path/to/log.$BASHPID

    ... >> $log

    output=$(bar_1 ...)
      ...
    output=$(bar_n ...)
}

function bar_1() {
   # log only specific (and NOT all) messages
   # to the shared log file of the invoking thread.
    ... >> /path/to/log.$BASHPID
}

foo &
foo &

Question: Is there an elegant way around the above limitation, without having to pass bashpid_of_foo via adhoc environment variables or external disk files?

By elegant, I mean, being able to keep the interfaces and bodies of bar_* functions clean by relying only on bash-provided features. (Eg BASHPID is a bash feature.)

If I try to override the value of BASHPID, like this,

out_1=$(BASHPID=$BASHPID bar_1 ...)

... it (rightly) complains about BASHPID being a readonly variable.

EDIT: (1) Added the definition of bar_1 above. (2) Added the 2nd call to foo in the background. Each foo invocation needs to maintain its own log file, since writing to a common file could result in garbled contents.

NOTE: Whatever logging happens in the runtime context of foo, I want it to go into the foo-specific log file, /path/to/log.$BASHPID WITHOUT passing around the name of this log file or even the PID of foo. foo can have multiple instances running in the background.

6
  • 3
    BASHPID may be special, but bashpid_of_foo is not. You should be able to use that in the command substitutions.
    – Kusalananda
    Jul 27, 2018 at 7:28
  • 2
    @Harry If you save $BASHPID in bashpid, then the only thing you have to do differently in your command substitutions is to not press shift when typing bashpid.
    – Kusalananda
    Jul 27, 2018 at 8:02
  • 2
    These are the types of best practices or tricks you have to resort to @Harry as Kusalananda is saying by keeping complex software in Bash.
    – slm
    Jul 27, 2018 at 8:04
  • @Kusalananda "then the only thing you have to do differently in your command substitutions is to not press shift when typing bashpid" Lol that was astute! Ok, bro.
    – Harry
    Jul 27, 2018 at 8:34
  • My boss was pressuring me to write my script in Java (yuck!). This is a customer's server - it has Java and bash, but no Perl, Python, Ruby, etc. I fought with him to let me do it in bash since I'm mostly using /bin and /usr/bin commands - coding which in Perl, Python, and Ruby can be relatively more verbose than bash. And now I run into this dead-end - which I can surely workaround (like you suggested) but would've preferred not to (by relying some existing facility. Btw, I feel, this is a feature-flaw in bash: A bash function (and, not an external program) should be allowed +
    – Harry
    Jul 27, 2018 at 8:39

2 Answers 2

1
#!/bin/bash

bar () {
    # bashpid is set in our environment from the calling function
    printf 'bar BASHPID = %d, bar bashpid = %d\n' "$BASHPID" "$bashpid"

    # in your case, you would have...

    local logfile="/some/path/to/log.$bashpid"

    # etc.
}

foo () {
    local bashpid="$BASHPID"
    local message

    local logfile="/some/path/to/log.$BASHPID"

    message=$( bashpid="$bashpid" bar ); printf 'Message from bar: %s\n' "$message"
    message=$( bashpid="$bashpid" bar ); printf 'Message from bar: %s\n' "$message"
    message=$( bashpid="$bashpid" bar ); printf 'Message from bar: %s\n' "$message"
    message=$( bashpid="$bashpid" bar ); printf 'Message from bar: %s\n' "$message"
}

foo &
foo &
foo &

wait

Example run:

$ bash script.sh
Message from bar: bar BASHPID = 71979, bar bashpid = 18461
Message from bar: bar BASHPID = 7420, bar bashpid = 71036
Message from bar: bar BASHPID = 6109, bar bashpid = 18461
Message from bar: bar BASHPID = 27868, bar bashpid = 71036
Message from bar: bar BASHPID = 44547, bar bashpid = 60086
Message from bar: bar BASHPID = 69310, bar bashpid = 71036
Message from bar: bar BASHPID = 37649, bar bashpid = 60086
Message from bar: bar BASHPID = 15999, bar bashpid = 71036
Message from bar: bar BASHPID = 81520, bar bashpid = 18461
Message from bar: bar BASHPID = 92568, bar bashpid = 60086
Message from bar: bar BASHPID = 72438, bar bashpid = 18461
Message from bar: bar BASHPID = 15094, bar bashpid = 60086

For each invocation of foo in the main part of the script, four calls to bar will be made and four lines of output will be produced. As you see, there is only three unique bashpid numbers, each from one of the foo invocations.

Another way of passing $bashpid from foo to bar is obviously to pass it as a command line argument and to receive it with local bashpid="$1" in bar or something similar, but you say you don't want to do this.

4
  • The bashpid approach should work for me. But if bashpid is the local/dynamic scope of foo, why are you passing it as an env var when calling bar?
    – Harry
    Jul 29, 2018 at 4:46
  • 1
    @Harry It allows for the case where bar is a script or other application (not a function).
    – Kusalananda
    Jul 29, 2018 at 9:13
  • Then, can't we just export it from within foo so that ALL calls to functions or programs/scripts from within foo automatically get it, instead of I passing it explicitly every time?
    – Harry
    Jul 29, 2018 at 9:26
  • 1
    @Harry Yes, of course, if you wish to do it that way. However, that would also set it in foo and in whatever environment foo is executing in. In this case, you are running foo as a background job, so it doesn't affect the other foos or the main script.
    – Kusalananda
    Jul 29, 2018 at 9:42
1

NOTE: In regards to your general questions around $BASHPID, this is readonly and you cannot manipulate it. This is by design.

It's typically my general advice that when you get to the level of sophistication with Bash scripts like this, it's time to move it to Python, Ruby, whatever.


Your example

I guess I don't see what your issue is, this works for me:

$ cat subby.bash
#!/bin/bash

function foo() {
    local bashpid_of_foo=$BASHPID
    local output=blipblop

    echo "foo: $bashpid_of_foo"
    echo "foo: $output"
    out_1=$(echo $bashpid_of_foo)
    out_n=$(echo $output)
    echo "out_1: $out_1"
    echo "out_n: $out_n"
}

foo &


$ ./subby.bash
foo: 4900
foo: blipblop
out_1: 4900
out_n: blipblop

Addition of export

If we change this around by making bar_1 into a shell script:

$ cat bar_1
#!/bin/bash

echo "from bar_1: $bashpid_of_foo"

And change your original script like this:

#!/bin/bash

function foo() {
    export bashpid_of_foo=$BASHPID
    local output=blipblop

    echo "foo: $bashpid_of_foo"
    echo "foo: $output"
    out_1=$(echo $bashpid_of_foo)
    out_2=$(./bar_1)
    out_n=$(echo $output)
    echo "out_1: $out_1"
    echo "out_2: $out_2"
    echo "out_n: $out_n"
}

foo &

We can see that the $bashpid_of_foo is getting exported properly to subshells:

$ ./subby.bash
foo: 5014
foo: blipblop
out_1: 5014
out_2: from bar_1: 5014
out_n: blipblop

We need to use an export here and not just a local because otherwise the environment variables will not get exported to any children. The subshells are child shells here.

$ help export
...
   Marks each NAME for automatic export to the environment of subsequently
    executed commands.  If VALUE is supplied, assign VALUE before exporting.
0

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.