What is '$$' in the bash shell? - linux

I'm beginner at bash shell programming. Can you tell me about '$$' symbols in the bash shell?
If I try the following
#> echo $$
it prints
#>18756
Can you tell me what this symbol is used for and when?

It's the process id of the bash process itself.
You might use it to track your process over its life - use ps -p to see if it's still running, send it a signal using kill (to pause the process for example), change its priority with renice, and so on.
Process ids are often written to log files, especially when multiple instances of a script run at once, to help track performance or diagnose problems.
Here's the bash documentation outlining special parameters.
BASHPID, mentioned by ghostdog74, was added at version 4.0. Here's an example from Mendel Cooper's Advanced Bash-Scripting Guide that shows the difference between $$ and $BASHPID:
#!/bin/bash4
echo "\$\$ outside of subshell = $$" # 9602
echo "\$BASH_SUBSHELL outside of subshell = $BASH_SUBSHELL" # 0
echo "\$BASHPID outside of subshell = $BASHPID" # 9602
echo
( echo "\$\$ inside of subshell = $$" # 9602
echo "\$BASH_SUBSHELL inside of subshell = $BASH_SUBSHELL" # 1
echo "\$BASHPID inside of subshell = $BASHPID" ) # 9603
# Note that $$ returns PID of parent process.

if you have bash, a relatively close equivalent is the BASHPID variable. See man bash
BASHPID
Expands to the process id of the current bash process. This differs from $$ under certain circumstances, such as subshells
that do not require bash to be re-initialized.

Related

Capturing output from a background subshell in bash?

I'm trying to run multiple subshells in a bash script and capture the stdout result to a variable. When I run the subshell in the background I would expect I can use wait to let the subshell complete and then use the variable the result is assigned to later in the program.... but it doesn't seem to work.
Simple example script:
l=$(ls) &
wait $!
echo "L=$l"
Then when I run it:
$ bash -x test2.sh
+ wait 16821
++ ls
+ l='test1.sh test2.sh'
+ echo L=
L=
The output from my test program would suggest the variable l should be assigned the result of the subshell, but when I use echo it is empty...
If I don't background the subshell (or use wait) then it works as expected...
l=$(ls)
echo "L=$l"
Results in:
$ bash -x test1.sh
++ ls
+ l='test1.sh test2.sh'
+ echo 'L=test1.sh test2.sh'
L=test1.sh test2.sh
Am I missing something obvious or ... ?
From bash manpage (emphasis mine):
Command substitution, commands grouped with parentheses, and
asynchronous commands are invoked in a subshell environment that is
a duplicate of the shell environment, except that traps caught by the
shell are re‐set to the values that the shell inherited from its
parent at invocation. Builtin commands that are invoked as part of a
pipeline are also executed in a subshell environment. Changes made to the
subshell environment cannot affect the shell's execution environment.
So, l=$(ls) & would be like (l=$(ls)) if not backgrounded.

Csh script wait for multiple pid

Does the wait command work in a csh script to wait for more than 1 PID to finish?
Where the wait command waits for all the PID listed to complete before moving on to the next line
e.g.
wait $job1_pid $job2_pid $job3_pid
nextline
as the documentation online that I usually see only shows the wait command with only 1 PID, although I have read of using wait for multiple PID, like here :
http://www2.phys.canterbury.ac.nz/dept/docs/manuals/unix/DEC_4.0e_Docs/HTML/MAN/MAN1/0522____.HTM
which says quote "If one or more pid operands are specified that represent known process IDs,the wait utility waits until all of them have terminated"
No, the builtin wait command in csh can only wait for all jobs to finish. The command in the documentation that you're referencing is a separate executable that is probably located at /usr/bin/wait or similar. This executable cannot be used for what you want to use it for.
I recommend using bash and its more powerful wait builtin, which does allow you to wait for specific jobs or process ids.
From the tcsh man page, wait waits for all background jobs. tcsh is compatible with csh, which is what the university's documentation you linked is referring to.
wait The shell waits for all background jobs. If the shell is interactive, an interrupt will disrupt the wait and cause the shell
to print the names and job numbers of all outstanding jobs.
You can find this exact text on the csh documentation here.
The wait executable described in the documentation is actually a separate command that waits for a list of process ids.
However, the wait executable is not actually capable of waiting for the child processes of the running shell script and has no chance of doing the right thing in a shell script.
For instance, on OS X, /usr/bin/wait is this shell script.
#!/bin/sh
# $FreeBSD: src/usr.bin/alias/generic.sh,v 1.2 2005/10/24 22:32:19 cperciva Exp $
# This file is in the public domain.
builtin `echo ${0##*/} | tr \[:upper:] \[:lower:]` ${1+"$#"}
Anyway, I can't get the /usr/bin/wait executable to work reliably in a Csh script ... because the the background jobs are not child processes of the /usr/bin/wait process itself.
#!/bin/csh -f
setenv PIDDIR "`mktemp -d`"
sleep 4 &
ps ax | grep 'slee[p]' | awk '{ print $1 }' > $PIDDIR/job
/usr/bin/wait `cat $PIDDIR/job`
I would highly recommend writing this script in bash or similar where the builtin wait does allow you to wait for pids and capturing pids from background jobs is easier.
#!/bin/bash
sleep 4 &
pid_sleep_4="$!"
sleep 7 &
pid_sleep_7="$!"
wait "$pid_sleep_4"
echo "waited for sleep 4"
wait "$pid_sleep_7"
echo "waited for sleep 7"
If you don't want to rewrite the entire csh script you're working on, you can call out to bash from inside a csh script like so.
#!/bin/csh -f
bash <<'EOF'
sleep 4 &
pid_sleep_4="$!"
sleep 7 &
pid_sleep_7="$!"
wait "$pid_sleep_4"
echo "waited for sleep 4"
wait "$pid_sleep_7"
echo "waited for sleep 7"
'EOF'
Note that you must end that heredoc with 'EOF' including the single quotes.

bash: daemonizing by forking process as a new child

I have a bash script which should daemonize itself after being run. My solution looks as follows:
#!/bin/sh -xe
child() {
echo child
}
child & # fork child
echo parent
kill $$ # kill parent
However, putting the whole script itself inside the function child does not seem the correct thing to do. Unfortunately exec & won't fork-off the whole process into a backgrounded child.
How can a achieve the desired effect?
I usually do something like this:
#!/bin/bash
if [ -z "$_IS_DAEMON" ]; then
_IS_DAEMON=1 /bin/bash $0 "$#" &
exit
fi
echo "I'm a deamon!"
The script effectively restarts itself in the background, while exiting the script started by user.
To recognize the daemonization status, it uses an environment variable (the $_IS_DAEMON in the example above): if not set, assume started by user; if set, assume started as part of daemonization.
To restart itself, the script simply invokes $0 "$#": the $0 is the name of the script as was started by the user, and the "$#" is the arguments passed to the script, preserved with white-spaces and all (unlike the $*). I also typically call needed shell explicitly, as to avoid confusion between /bin/bash and /bin/sh which are on most *nix systems are not the same.

How to kill all background processes in zsh?

As in the title - how to kill all background processes in zsh?
alias killbg='kill ${${(v)jobstates##*:*:}%=*}'
. It is zsh, no need in external tools.
If you want to kill job number N:
function killjob()
{
emulate -L zsh
for jobnum in $# ; do
kill ${${jobstates[$jobnum]##*:*:}%=*}
done
}
killjob N
one should use the builtin zsh built-in command alongside with the other kill zsh built-in command as:
builtin kill %1
as kill is also a separate binary file from util-linuxpackage (upstream, mirror) located in /usr/bin/kill which does not support jobs (kill: cannot find process "%1").
use keyword builtin to avoid name conflict or enable the kill built-in if it is disabled.
there is a concept of disabling and enabling built-in commands (ie. shell's own commands such as cd and kill ) in shells, and in zsh you can enable (a disabled) kill builtin as:
enable kill
issue disable to check if the builtin is disabled (and enable to see the enabled ones).
Minor adjustment to #Zxy's response...
On my system, I found that suspended jobs weren't killed properly with the default kill signal. I had to actually change it to kill -KILL to get suspended background jobs to die properly.
alias killbg='kill -KILL ${${(v)jobstates##*:*:}%=*}'
Pay special attention to the SINGLE QUOTES around this. If you switched to double quotes, you would need to escape the each "$". Note that you can NOT use a function to wrap this command since the function will increment the $jobstates array causing the function to try killing itself... Must use an alias.
The killjob script above is a bit redundant since you can just do:
kill %1
Less keystrokes and it's already build into zsh.
This works for both ZSH and Bash:
: '
killjobs - Run kill on all jobs in a Bash or ZSH shell, allowing one to optionally pass in kill parameters
Usage: killjobs [zsh-kill-options | bash-kill-options]
With no options, it sends `SIGTERM` to all jobs.
'
killjobs () {
local kill_list="$(jobs)"
if [ -n "$kill_list" ]; then
# this runs the shell builtin kill, not unix kill, otherwise jobspecs cannot be killed
# the `$#` list must not be quoted to allow one to pass any number parameters into the kill
# the kill list must not be quoted to allow the shell builtin kill to recognise them as jobspec parameters
kill $# $(sed --regexp-extended --quiet 's/\[([[:digit:]]+)\].*/%\1/gp' <<< "$kill_list" | tr '\n' ' ')
else
return 0
fi
}
#zyx answer didn't work for me.
More on it here: https://gist.github.com/CMCDragonkai/6084a504b6a7fee270670fc8f5887eb4
alias killbg='for job in \`jobs -l | egrep -o "([0-9][0-9]+)"`;

Difference between bash pid and $$

I'm a bash scripting beginner, and I have a "homework" to do. I figured most of the stuff out but there is a part which says that I have to echo the pid of the parent bash and the pid of the two subshells that I will be running. So I looked online and found this (The Linux documentation project):
#!/bin/bash4
echo "\$\$ outside of subshell = $$" # 9602
echo "\$BASH_SUBSHELL outside of subshell = $BASH_SUBSHELL" # 0
echo "\$BASHPID outside of subshell = $BASHPID" # 9602
echo
( echo "\$\$ inside of subshell = $$" # 9602
echo "\$BASH_SUBSHELL inside of subshell = $BASH_SUBSHELL" # 1
echo "\$BASHPID inside of subshell = $BASHPID" ) # 9603
# Note that $$ returns PID of parent process.
So here are my questions:
1) What does the first echo print? Is this the pid of the parent bash?
2) Why does the 2nd echo print out 0?
3) Is $BASH_SUBSHELL a command or a variable?
4) I'm doing everything on a mac, I will try all of this on a Linux machine in some days but
whenever I run this script $BASHPID doesn't return anything, I just get a new line. Is this because I'm running this on a mac and $BASHPID doesn't work on a mac?
Looking at documentation on this, it looks like:
$$ means the process ID that the script file is running under. For any given script, when it is run, it will have only one "main" process ID. Regardless of how many subshells you invoke, $$ will always return the first process ID associated with the script. BASHPID will show you the process ID of the current instance of bash, so in a subshell it will be different than the "top level" bash which may have invoked it.
BASH_SUBSHELL indicates the "subshell level" you're in. If you're not in any subshell level, your level is zero. If you start a subshell within your main program, that subshell level is 1. If you start a subshell within that subshell, the level would be 2, and so on.
BASH_SUBSHELL is a variable.
Maybe BASHPID isn't supported by the version of bash you have? I doubt it's a "Mac" problem.
It'd be best to get well-acquainted with bash(1):
BASHPID
Expands to the process ID of the current bash process.
This differs from $$ under certain circumstances, such
as subshells that do not require bash to be re-
initialized.
[...]
BASH_SUBSHELL
Incremented by one each time a subshell or subshell
environment is spawned. The initial value is 0.
$BASHPID was introduced with bash-4.0-alpha. If you run bash --version you can find out what version of bash(1) you're using.
If you're going to be doing much bash(1) work, you'll also need the following:
Greg's bash FAQ
TLDP bash reference card

Resources