Assign variable in the background shell - bash

I want to assign a variable in the background while I'm showing a progress indicator like this
sleep_echo(){
sleep $1
echo $2
}
show_ellapsed_time () {
#...
}
(mivar=$(sleep_echo 3 hello)) &
show_ellapsed_time $!
echo $mivar
echo "after asignment"
When I run this in terminal it outputs the progress indicator and afterwards echoes myvar
processing 00:00:03 [\]
hello
after asignment
However the assignation doesn't happen when I run it within a script
processing 00:00:03 [\]
after asignment
Why is this failing? Isn't te function invoked?

A child process cannot communicate with its parent process through variable assignment.
In Bash, a child process can be created in several ways :
Launching an external command
Launching a subshell by enclosing statements with ()
Launching a subshell when using redirection (such as pipes)
Launching anything in the background with &
External commands receive a copy of all shell variables that are marked for export (e.g. export VAR, local -x VAR)
Subshells receive a copy of all variables. This is useful, but makes it easy to not realize you are in a new context. Any assignment made in the child process will never be seen by the parent.
What you can do:
Use inter-process communication to capture the output
Put the information you need in a file, as files are accessible by all processes (assuming adequate permissions)
Use a named FIFO, which is a special kind of file allowing separate processes to communicate (see mkfifo).
Structure your script in a way that you keep the code blocks that most need it in the main context by using the right kind of construct to build pipes.
For that last one, using process substitution like <(COMMAND) and >(COMMAND) is often much better than the typical pipelines built with |, for instance allowing the body of a while loop (or, really, any other statement the substitution is fed to/from) to remain in the context of the script. This allows variable assignments inside loops to have the intended effect.
In your case, you have these lines:
(mivar=$(sleep_echo 3 hello)) &
show_ellapsed_time $!
Maybe you could replace them with something like :
show_ellapsed_time &
BG_PID=$!
mivar=$(sleep_echo 3 hello)
kill $BG_PID
I do not know enough about your show_ellapsed_time to know if that could work without the parameter you were passing to that function, but the assignment would work. You could also pass the PID of the main shell to the function, like this :
show_ellapsed_time $$ &

your variable is inside another scope. If you enclose some commands with round brackets ( and ), the variables you use within that scope are no longer accessible once outside that scope. An example:
(myvar=test)
echo "myvar=$myvar"
will give:
myvar=
since myvar is not known anymore. The only reason your command seems to work in the terminal; is that probably you have set your variable mivar during your tests; so the variable is still set. If you open a new terminal your script won't work anymore.
Sending the process to the background (using &); will also cause the scope to change; so even removing the brackets won't help. I think you should redesign your script (or consider using a higher level language), since it seems like you want to introduce multithreading into bash :)

Related

How to read environment variable inside shell file

i have a tcl file and defining my environment variable inside that file. something like below
set env(ET_PINASSIGN_SCRIPT) $ET_PINASSIGN_SCRIPT
where $ET_PINASSIGN_SCRIPT variable will have a user incoming input value. Now I need to read this env variable in a shell file (#!/bin/ksh
). This is what i am trying and not working
$env ET_PINASSIGN_SCRIPT .
Any suggestions?
Thanks
I understand this is not possible. A program which is running, eg. your script, receives a duplicate of the environment, and can modify it. But when the program stops running, it disappears, together with its environment and changes.
There are few direct methods to communicate 'from the dead' process. You could create a temporary file, or return simple integers from the exit code.
Another way, would be to run the other program concurrently in a way that they share the same environment,
More info on the environment:
http://en.wikipedia.org/wiki/Environment_variable
Edit: I feel I wasn't clear enough: I just wanted to point out that programming that modifies the environment for other programs is 'extreme' (i.e. dangerous) programming. All kind of tricks can be done which can have lasting influences on the use of the computer.
The simplest method would be to make the Tcl code write a shell script to set the variable to standard out (with puts) and to then do:
eval `tclsh yourscript.tcl`
in your shell.
The Tcl code would probably be something like:
puts "ET_PINASSIGN_SCRIPT='$ET_PINASSIGN_SCRIPT'"
except you'll have to worry about any embedded ' characters, so might do…
puts "ET_PINASSIGN_SCRIPT='[string map {' '\\''} $ET_PINASSIGN_SCRIPT]'"

global variable in bash function not global if called with & to background the command

Here is an example of what I'm trying to achieve.
#!/bin/bash
func2() {
myarray=("e" "f")
}
func1() {
myarray=("c" "d")
for i in [1..10]
do
func2 &
done
}
myarray=("a" "b")
func1
echo "${myarray[#]}"
The echo at the end should be
e f
but it ends up being
c d
because of the & when I call func2. If I take out the & it runs as expected. This is a proof of concept for a more complicated script I am using to make ssh calls to several servers at once. I need the multi-threading capability of &, but since it seems to run in a subshell, all of its variables are local?!?!?
I assume your questions is how to affect parent shell variables from a subshell, although you don't quite ask it.
There is no way to do that directly, but you can manage it by communicating with the background processes. The easiest way is to arrange them to write data to files after completion and then interpreting that. If you trust your subprocesses, simply make them write bash code and eval that in your parent process.
You might be able to detect when they finish using a trap on SIGCHLD.
Another way might be using GNU "parallel" tool and reaping the output files when it completes.
Yet another way could be using tools like "parallel-ssh".
A more specific answer could be made if you provide more details.

Disown, nohup or & on Mac OS zsh… not working as hoped

Hi. I'm new to the shell and am working on my first kludged together script. I've read all over the intertube and SO and there are many, MANY places where disown, nohup, & and return are explained but something isn't working for me.
I want a simpler timer. The script asks for user input for the hours, mins., etc., then:
echo "No problem, see you then…"
sleep $[a*3600+b*60+c]
At this point (either on the first or second lines, not sure) I want the script OR the specific command in the script to become a background process. Maybe a daemon? So that the timer will still go off on schedule even if
that terminal window is shut
the terminal app is quit completely
the computer is put to sleep (I realize I probably need some different code still to wake the mac itself)
Also after the "No problem" line I want a return command so that the existing shell window is still useful in the meantime.
The terminal-notifier command (the timer wakeup) is getting called immediately under certain usage of the above (I can't remember which right now), then a second notification at the right time. Using the return command anywhere basically seems to quit the script.
One thing I'm not clear on is whether/how disown, nohup, etc. are applicable to a command process vs. a script process, i.e., will any of them work properly on only a command inside a script (and if not, how to initialize a script as a background process that still asks for input).
Maybe I should use some alternative to sleep?
It isn't necessary to use a separate script or have the script run itself in order to get part of it to run in the background.
A much simpler way is to place the portions that you want to be backgrounded (the sleep and following command) inside of parentheses, and put an ampersand after them.
So the end of the script would look like:
(
sleep $time
# Do whatever
)&
This will cause that portion of the code to be run inside a subshell which is placed into the background, since there's no code after that the first shell will immediately exit returning control to your interactive shell.
When your script is run, it is actually run by starting a new shell to execute it. In order for you to get your script into the background, you would need to send that shell into the background, which you can't do because you would need to communicate with its parent shell.
What you can do is have your script call itself with a special argument to indicate that it should do the work:
#! /bin/zsh
if [ "$1" != '--run' ] ; then
echo sending to background
$0 --run $# &
exit
fi
sleep 1
echo backgrounded $#
This script first checks to see if its first argument is --run. If it is not, then it calls itself ($0) with that argument and all other arguments it received ($#) in the background, and exits. You can use a similar method, performing the test when you want to enter the background, and possibly sending the data you will need instead of every argument. For example, to send just the number of seconds:
$0 --run $[a*3600+b*60+c] &

Getting last process' PID in Makefile

My Makefile look something like this:
setsid ./CppServer>daemon.log 2>&1 &
echo $!>daemon.pid
What I expect it to do is to store the PID of my_awesome_script in corresponding file. However there's nothing there. So where's the problem?
If your makefile really looks like this you will get an error, because I don't see any actual make syntax, just shell syntax. However, my crystal ball tells me that these two lines might be part of the recipe for a rule. If they are, you should realise how make executes recipes; for each line a separate subshell is created, in which that line's command is executed independently: your two commands don't know anything about each other's environment. If you want two commands to be executed in the same subshell, you should issue them as one line (using line continuation characters if necessary), or use make's ONESHELL directive.
The variable you're trying to use prints the pid of the last program run in the background. It is correctly written as echo $! > filename.extension. But since you are running it in the foregorund you have two choices. Run it in the background by appending an & to the end of the line ./script_to_run &, or you can have the script itself print to file the pid of the currently running process by using echo $$ > filename.extension (inside the script). Here is a link that might help you http://tldp.org/LDP/abs/html/internalvariables.html

Shell script that can check if it was backgrounded at invocation

I have written a script that relies on other server responses (uses wget to pull data), and I want it to always be run in the background unquestionably. I know one solution is to just write a wrapper script that will call my script with an & appended, but I want to avoid that clutter.
Is there a way for a bash (or zsh) script to determine if it was called with say ./foo.sh &, and if not, exit and re-launch itself as such?
The definition of a background process (I think) is that it has a controlling terminal but it is not part of that terminal's foreground process group. I don't think any shell, even zsh, gives you any access to that information through a builtin.
On Linux (and perhaps other unices), the STAT column of ps includes a + when the process is part of its terminal's foreground process group. So a literal answer to your question is that you could put your script's content in a main function and invoke it with:
case $(ps -o stat= -p $$) in
*+*) main "$#" &;;
*) main "$#";;
esac
But you might as well run main "$#" & anyway. On Unix, fork is cheap.
However, I strongly advise against doing what you propose. This makes it impossible for someone to run your script and do something else afterwards — one would expect to be able to write your_script; my_postprocessing or your_script && my_postprocessing, but forking the script's main task makes this impossible. Considering that the gain is occasionally saving one character when the script is invoked, it's not worth making your script markedly less useful in this way.
If you really mean for the script to run in the background so that the user can close his terminal, you'll need to do more work — you'll need to daemonize the script, which includes not just backgrounding but also closing all file descriptors that have the terminal open, making the process a session leader and more. I think that will require splitting your script into a daemonizing wrapper script and a main script. But daemonizing is normally done for programs that never terminate unless explicitly stopped, which is not the behavior you describe.
I do not know, how to do this, but you may set variable in parent script and check for it in child:
if [[ -z "$_BACKGROUNDED" ]] ; then
_BACKGROUNDED=1 exec "$0" "$#" & exit
fi
# Put code here
Works both in bash and zsh.
the "tty" command says "not a tty" if you're in the background, or gives the controlling terminal name (/dev/pts/1 for example) if you're in the foreground. A simple way to tell.
Remember that you can't (or, not recommended to) edit the running script. This question and the answers give workarounds.
I don't write shell scripts a long time ago, but I can give you a very good idea (I hope). You can check the value of $$ (this is the PID of the process) and compare with the output of the command "jobs -l". This last command will return the PID of all the backgrounded processes (jobs) and if the value of $$ is contained in the result of the "jobs -l", this means that the current script is running on background.

Resources