2

I want to execute a trap command in "global" scope, but the signal will come from within function. Of course it is possible to declare the variable globally beforehand or use the -g declare option. But in cases where I want to source on trap that's not very practicable as shown below:

#!/bin/bash
# ./variables
declare say=hello
declare -ri times 3

and the actual script:

#!/bin/bash
# ./trapsource

setTraps () {
  trap 'echo trap called in scope ${FUNCNAME[@]}; source ./variables' SIGUSR1
}

sourceVariables () {
  kill -SIGUSR1 $$
}

setTraps
sourceVariables
echo I want you to $say \"hello\" $times times.
printf "$say\n%.0s" $(seq 1 $times)

But without declare -g NAME, thank you.

EDIT: Is it possible to detach the kill -s USR1 $$ part completely from the current process, so that the signal comes from 'outside'?

I tried nohup and disown without success so far.

EDIT 2: Has anyone enough experience with bash custom builtins to point into a direction where a true solution can be achieved?

2
trap 'echo trap called in scope ${FUNCNAME[@]}; declare say=hello; declare -ri times=3' SIGUSR1

Instead of using declare -i within the trap, do that beforehand, and just assign the new value in the trap:

declare -i times=999
trap 'times=3' USR1

I think you could even use readonly times within the trap, since readonly in itself doesn't make the variable local.

E.g. this prints 1, 3, 3 and then an error modifying a readonly variable.

#!/bin/bash
trap 'readonly num=3' USR1
sub() {
        kill -USR1 $$
        echo "$num"
}

declare -i num=999
num=1
echo "$num"
sub
echo "$num"
num=1234

Then again, if it's the global variable you want to modify, why not use declare -g?

Is it possible to detach the kill -s USR1 $$ part completely from the current process, so that the signal comes from 'outside'?

I don't think there's a difference between a signal sent by (the builtin) kill from the script itself, and one sent by another process. As you saw, Bash seems to run the trap code in the context of the running function.

  • you have a good point with declaring the variable beforehand. but there are cases when thats not practicable. For example sourcing a new file on trap. I'll clarify my question of course. – Dominik Kummer Feb 17 at 17:42
  • @DominikKummer, I'm not really sure I see the utility of that, and I can't really see why you wouldn't use declare -g, since that appears to do what you want. But then I suppose one possibility would be to just set a flag in the trap, and then do the actual processing in the main level of the script. – ilkkachu Feb 17 at 17:59
  • @ikkachu I wrote a custom Source function which does some additional lookups, caches and finally sources the files. But within the sourced files I have to declare all variables global with -g option. which works of course, you are right. It is actually a syntactical detail to be forced to declare a variable as global which is meant to be global in the first place. I hoped that I can work around with trapping the source mechanism to make the sourced variables global without -g option. – Dominik Kummer Feb 17 at 18:17
1
#!/bin/bash

setTraps () {
    trap 'echo trap called in scope ${FUNCNAME[@]}; say=hello' USR1
}

sendSignal () {
    kill -s USR1 "$$"
}

setTraps
sendSignal
printf 'I want you to %s "hello"\n' "$say"

If you don't use declare at all, the trap will set the say variable to the string in global scope. The trap is still called within the scope of sendSignal scope though, but I don't think there's a way of changing that.

  • even if the best answer is the simplest, I have to admit that I also would like to make use of $ say=hello; declare -i times=3; within the trap. – Dominik Kummer Feb 16 at 12:59
  • regarding impossibility, that unfortunately forces me into bash source code, because it would be pointless. Is it possible to detach the kill -s USR1 "$$" completely from the process, so that the signal would come from "outside"? – Dominik Kummer Feb 16 at 13:23
0

Is it possible to detach the kill -s USR1 $$ part completely from the current process, so that the signal comes from 'outside'?

The problem is not that your signal comes from "inside", it is rather that your main script receives that signal while it is still running within the function scope. It is not enough to send signal from "outside", otherwise a simple subshell like (kill -SIGUSR1 $$) would suffice. You also need to send it in a way to give the main script a chance to return from the sourceVariables function and enter whatever other scope you want your trap to be run within. Supposedly the main scope, if you want to make your variables "global" without marking them explicitly so.

For your sample code, I'd say: just run the sourceVariables function in background. That way the trap will certainly run in main scope.

For instance, the following code does as (I think) you intend:

#!/bin/bash

set -x  # <-- set debugging to see what happens

setTraps () {
  trap 'echo trap called in scope ${FUNCNAME[@]}; source ./variables' SIGUSR1
}

sourceVariables () {
  kill -SIGUSR1 $$
}

setTraps
sourceVariables &
while ! [ $say ] ; do :; done  # <-- careful: cpu-intensive loop, only for demonstration
echo I want you to $say \"hello\" $times times.
printf "$say\n%.0s" $(seq 1 $times)

But I guess your next piece of requirement will be that your actual sourceVariables function (not the one you showed to us so far) must not be run all in background.

If that's the case, you also need some synchronization mechanism between the kill and the script to make sure everything happens at the right moment. There can be various ways and the best one may depend on your actual application.

A simple one using the coproc builtin, which requires Bash v4+:

#!/bin/bash

set -x  # <-- set debugging to see what happens

setTraps () {
  trap 'echo trap called in scope ${FUNCNAME[@]}; source ./variables' USR1
}

sourceVariables () {
  # Do interesting stuff

  coproc { read ; kill -USR1 $$ ; }  # run a coprocess in background
  # the coprocess starts by waiting on `read`, which serves as a "go-ahead notification"
  # from the script when this latter is ready to receive the signal

  # Do yet more interesting stuff
}

setTraps
sourceVariables

# do even more stuff not yet ready for USR1

echo >&${COPROC[1]}  # notify the coprocess that we're now ready to receive the signal
while ! [ $say ] ; do :; done  # <-- careful: cpu-intensive loop, only for demonstration
echo I want you to $say \"hello\" $times times.
printf "$say\n%.0s" $(seq 1 $times)

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.