$> bash script.sh ... does the forked bash process in turn create a sub-shell? - bash

If I run:
$> bash script.sh
a fork-and-exec happens to run the bash binary. Does that process execute script.sh or does it create a sub-shell in turn in the same way that
$> ./script.sh
first creates a sub-shell to execute the script?

The bash process that runs bash script.sh executes the script directly, not as a second layer of fork and exec. Obviously, individual commands within the script are forked and executed separately, but not the script itself.
You could use ps to show that. For example, script.sh might contain:
tty
echo $$
sleep 20
You could run that and in another terminal window run ps -ft tty0 (if the tty command indicated tty0), and you'd see the shell in which you ran the bash script.sh command, the shell which is running script.sh and the sleep command.
Example
In ttys000:
$ bash script.sh
/dev/ttys000
65090
$
In ttys001:
$ ps -ft ttys000
UID PID PPID C STIME TTY TIME CMD
0 2422 2407 0 9Jul14 ttys000 0:00.13 login -pfl jleffler /bin/bash -c exec -la bash /bin/bash
199484 2428 2422 0 9Jul14 ttys000 0:00.56 -bash
199484 65090 2428 0 3:58PM ttys000 0:00.01 bash script.sh
199484 65092 65090 0 3:58PM ttys000 0:00.00 sleep 20
$

You can use pstree or ps -fax to look at the process tree. In your case when specifying bash as a (forked) command with a script parameter it will not (need) to fork a subshell as running with "command file" is one mode of operation (if not used -c).
BTW: you can also use exec sh script.sh to replace your current shell process with the new sub shell.
When you call a shell script without the source (or .) command, it will run in a subshell. This is the case for your second line. If you want to run the script in the current one, you would need to use . ./script.sh.

Related

Executing a shell with a command and returning

man bash seems to suggest that if I want to execute a command in a separate bash shell all I have to do is bash -c command:
-c string If the -c option is present, then commands are read from string.
I want to do that because I need to run a few things in different environments:
bash --rcfile ~/.bashrc.a -c mytest.a
bash --rcfile ~/.bashrc.b -c mytest.b
However, that didn't work as expected; one can see that by the number of bash shells running, for example:
$ bash
$ ps
PID TTY TIME CMD
7554 pts/0 00:00:00 bash
7573 pts/0 00:00:00 ps
28616 pts/0 00:00:00 bash
$ exit
exit
$ ps
PID TTY TIME CMD
7582 pts/0 00:00:00 ps
28616 pts/0 00:00:00 bash
$ bash -c ps
PID TTY TIME CMD
7583 pts/0 00:00:00 ps
28616 pts/0 00:00:00 bash
How should the invocation of bash should be modified so that it would start a new shell with the specified rc, execute the given command in that shell (with the env modified according to the rc), and exit back?
It's already working exactly the way you want it to. The lack of an extra process is simply due to bash's tail-call optimization.
Bash recognizes that there's no point in having a shell instance whose only job is to wait for a process and exit. It will instead skip the fork and exec the process directly. This is a huge win for e.g. var=$(ps), where it cuts the number of expensive forks from 2 to 1.
If you give it additional commands to run afterwards, this optimization is no longer valid, and then you'll see the additional process:
$ bash -c 'ps'
PID TTY TIME CMD
4531 pts/10 00:00:00 bash
4540 pts/10 00:00:00 ps
$ bash -c 'ps; exit $?'
PID TTY TIME CMD
4531 pts/10 00:00:00 bash
4549 pts/10 00:00:00 bash
4550 pts/10 00:00:00 ps
bash --rcfile ~/.bashrc.a mytest.a will already run mytest.a in a separate process. -c is for specifying a shell command directly, rather than running a script.
# NO!
bash for x in 1 2 3; do echo "$x"; done
# Yes.
bash -c 'for x in 1 2 3; do echo "$x"; done'

Why no ouput for the code in non login-shell status?

Platform:debian8 + bash.
Running the following command in my terminal gives me the following result:
prompt> echo $0
/bin/bash
I want to get what shell login status ,echo give more info than echo $SHELL.
1.the shell type is bash
2.it is a non-login shell
prompt> echo $0
-/bin/bash
It means not only bash shell was used but only a login-shell.
When I create a file test.sh, containing only the line:
echo $0
running it produces:
prompt> /bin/bash test.sh
test.sh
In other words, I get the script name rather than the shell name. Is there a way to get the shell name /bin/bash instead?
echo $SHELL is not i want to get,echo $SHELL only contain shell type ,not telling me it is login shell or non-login shell.
To make my intent clarity,let's edite two files: ~/.profile and ~/.bashrc
vim .profile
varLog="i am login shell"
vim .bashrc
varLog="i am not login shell"
~$ /bin/bash
~$ echo $varLog
i am non-login shell
~$ /bin/bash --login
~$ echo $varLog
i am login shell
Now to edit a bash script.
vim /tmp/decideShell.sh
echo $varLog
Logout and login again.
debian8#hwy:~$ ls -al /tmp/decideShell.sh
-rw-r--r-- 1 debian8 debian8 13 Mar 8 09:40 /tmp/decideShell.sh
debian8#hwy:~$ /bin/bash /tmp/decideShell.sh
debian8#hwy:~$ /bin/bash --login /tmp/decideShell.sh
i am login shell
Why nothing output for /bin/bash /tmp/decideShell.sh?
Try the following:
myname=$(id -u -n)
myloginshell=$(grep "${myname}:" /etc/passwd | cut -d ':' -f 7)
printf "my login shell: %s\n" ${myloginshell}
Depends on your login privileges and security rules you will find your username in /etc/passwd or not (i.e. ldap authority or other). If your username is stored in this file, you can find your default login shell as the 7th column. The first column is your username.
If you are authorized by other way (ldap or special other) and didn't find your username in the passwd-file, you can change your profile (depends on your unix-version in .profile or .bashrc or whatelse). Set your own variable (i.e. myloginshell) with
myloginshell=$(echo $0 | sed -e 's/\-//')
and don't forget to export this variable. The complete path to your shell can everytime examinated with
type -p ${myloginshell}
Last but not least you can find some special informations with the shopt command (login_shell, restricted_shell, ...).
$0 has a special meaning and it's value depends on how the shell or the shell is interpreting the script is called. There are special cases that are explained in detail in the bash man page but in most cases it's the name of the shell or the shell script being interpreted ... so it's perfectly normal that your script returns it's name not the shell that is being used to interpret the script (and in your case the interpreter is implicit in the command used to execute it).
I don't really understand why you want to know some things that would be implicit of the way you executed the script or depend on the way the interpreter is specified in the script itself ... but you might have your reasons for doing that ... in which case you might want to look at this answer on stackexchange: https://unix.stackexchange.com/a/26782
As to which shell is interpreting $SHELL is the right place to look.
If you just want the shell without the path do a longest prefix removal on $SHELL like this:
echo ${SHELL##*/}
This script can give you some idea:
echo ppid pid command
me=$$
while [ $me != 1 ]; do
ps=$(ps h -o ppid,pid,args -p $me)
echo $ps
me=$(echo $ps |cut -d" " -f1)
done
Output in my case (Debian stable linux, launched from an mrxvt launched from putty) is:
ppid pid command
4921 4922 bash
3938 4921 bash
3937 3938 mrxvt -fn --fixed-medium-r---12------iso8859-15 -vb -sl 300
1 3937 -bash
This script prints out the tree of the process, starting from the current up to init (which is not printed because we know everything about it). For every line there is the parent pid, used to go up, the pid, and the full command line. You can vary the options to ps(1), and check its output. May be you are interested only in the first line printed (the current process), or in its parent ($PPID).
I hope it helps to start.
UPDATE after comment. Running this script via this command inside putty:
./sc.sh
the output is:
ppid pid command
3938 4004 -bash
3936 3938 -bash
2306 3936 sshd: root#pts/1
1 2306 /usr/sbin/sshd
and I can see that the shell I am in is a login shell (because of "-bash").
Instead, calling the script in the following way:
bash --login script.sh
the output turns to:
ppid pid command
3938 3945 bash --login sc.sh
3936 3938 -bash
2306 3936 sshd: auser#pts/1
1 2306 /usr/sbin/sshd
shows that the process per se is not in a login shell, but it has been launched from a login shell. I think that the methods used here can be used to detect any situation.

Multiple exec in a shell script

What happens if you have multiple exec commands in a shell script, for example:
#!/bin/sh
exec yes > /dev/null &
exec yes alex > /dev/null
I assume that a fork is still needed in order to execute the first command since the shell needs to continue executing?
Or does the & specify to create a sub process in which the exec is actually then run?
The use of & implie a sub-process.
So exec have no effect.
Demo:
export LANG=C
echo $$
17259
exec sh -c 'echo $$;read foo' &
[1] 17538
17538
[1]+ Stopped exec sh -c 'echo $$;read foo'
fg
exec sh -c 'echo $$;read foo'
17259
I run the script: echo $$;read foo in order to prevent exit before having quietly read previous output.
In this sample, the current process ID is 17259.
When run with ampersand (&), the output is another pid (bigger). when run without ampersand, the new shell replace the command and is not forked.
Replacing the command by:
sh -c 'echo $$;set >/tmp/fork_test-$$.env;read'
re-running the whole test will generate two files in /tmp.
On my desk, I could read:
19772
19994
19772
So I found two files in /tmp:
-rw-r--r-- 1 user0 user0 2677 jan 22 00:26 /tmp/fork_test-19772.env
-rw-r--r-- 1 user0 user0 2689 jan 22 00:27 /tmp/fork_test-19994.env
If I run: diff /tmp/fork_test-19*env, I read:
29c29
< SHLVL='0'
---
> SHLVL='1'
46a47
> _='/bin/sh'
So the first run, with ampersand is in a sublevel.
Nota: This was tested under many different shell.
The shell forks to run the background process, but that means the new shell still needs to fork to run yes. Using exec eliminates the fork in the subshell.

Blank first line of shell script: explain behavior of UID variable

I have two very simple scripts, differing only by the presence of a blank first line:
$ cat test.bash
#!/bin/bash
echo ${UID}
$ cat test_blank.bash
#!/bin/bash
echo ${UID}
Now I run then, with and without nice:
$ ./test.bash
1060
$ ./test_blank.bash
1060
$ nice ./test.bash
1060
$ nice ./test_blank.bash
Please explain why, in the final case, the UID variable is unset. The behavior is the same when replacing nice with sudo or nohup.
Observe:
$ bash test_blank.bash
1060
$ dash test_blank.bash
bash produces output but dash, which is the default sh on debian-like systems, does not. This is because bash sets UID but dash does not. (POSIX does not require a shell to set UID.) So, the question becomes which shell executes the script.
When bash sees ./test.sh, it (bash) runs the script. When another command, such as nice, receives the script as an argument and the script does not have a valid shebang as the first line, then the default shell, likely dash, is run.
If you want UID in dash, or any other shell that does not provide it, use the id command:
UID=$(id -u)
Finding out which shell is running a script
To see which shell is running a script, use:
$ cat test2.sh
#!/bin/bash
ps $$
echo UID=${UID}
Under bash:
$ ./test2.sh
PID TTY STAT TIME COMMAND
1652 pts/12 S+ 0:00 bash -rcfile .bashrc
UID=1060
If we invoke it using nice, by contrast, we can see that it is running under /bin/sh and the UID variable is not assigned:
$ nice test2.sh
PID TTY STAT TIME COMMAND
1659 pts/12 SN+ 0:00 /bin/sh test2.sh
UID=

How to access an autoloaded zsh function from a ruby script?

I have a function that I declare as autoloaded in my zshenv. It works fine when I call it from the zsh prompt, or from a zsh script. But when I try to call it from a Ruby script (using `my_zsh_autoloaded_function`), it fails. I know Ruby is using zsh, and I know the zshenv is being sourced. One workaround is to do `zsh -c "my_zsh_autoloaded_function"`. Is there a way to give Ruby direct access?
You can't. That's not how Unix command execution works.
Zsh is essentially a REPL just like irb.
In order to for Zsh to execute internal commands it has to do one of the following:
Read the command from the REPL loop (STDIN)
Execute the command using the -c switch, or..
Run the command from a Zsh shell script file
None of those prerequisites are fulfilled when you just do `zsh_function`.
But you actually found (one of) the correct solutions already by using zsh -c.
But you were confused because you are looking at $SHELL and thinking you are running under zsh. $SHELL is not a reliable indicator of which shell is running:
> zsh
> echo $SHELL
/usr/bin/zsh
> sh
$ echo $SHELL
/usr/bin/zsh
$ bash
$ echo $SHELL
/usr/bin/zsh
$ SHELL=foo
$ bash
$ echo $SHELL
foo
So SHELL is simply copied from the originating environment into whatever new environment or shell you are spawning.
Now the final piece of the puzzle is hidden inside the Ruby source code, and also in the help file for Kernel.exec:
The standard shell always means "/bin/sh" on Unix-like systems, same as ENV["RUBYSHELL"] (or ENV["COMSPEC"] on Windows NT series), and similar.
So no, Kernel.system or backticks on Unix will not run zsh. It will always run either the program directly (for example /bin/ls) or run sh -c in case of a bash command or a command which requires any other shell processing (like ls *).
It's easy to check too:
> zsh
> irb
> puts `ps xf; echo hello`
PID TTY STAT TIME COMMAND
24122 ? S 0:00 sshd: casper#pts/7
24127 pts/7 Ss 0:00 \_ -zsh
24686 pts/7 S 0:00 \_ zsh
24706 pts/7 Sl+ 0:00 \_ irb
24710 pts/7 S+ 0:00 \_ sh -c ps xf; echo hello
24712 pts/7 R+ 0:00 \_ ps xf
Solution
Either run using zsh -c, like you did, or create a separate Zsh shell script with the command and run that.

Resources