Have a look at the code:
#!/bin/bash
read -p "Eneter 1 for UID and 2 for LOGNAME" choice
if [ $choice -eq 1 ]
then
read -p "Enter UID: " uid
logname=`cat /etc/passwd | grep $uid | cut -f1 -d:`
else
read -p "Enter Logname: " logname
fi
not=`ps -au$logname | grep -c bash`
echo "The number of terminals opened by $logname are $not"
This code is used to find out the number of terminals opened by a user on the same PC.
Now there are two users logged on, say x and y. I am currently logged in as y and there are 3 terminals open in user x. If I execute this code in y using different ways as mentioned above the results are :
$ ./file.sh
The number of terminals opened by x are 3
$ bash file.sh
The number of terminals opened by x are 5
$ sh file.sh
The number of terminals opened by x are 3
$ source file.sh
The number of terminals opened by x are 4
$ . ./file.sh
The number of terminals opened by x are 4
Note: I passed 1 and uid 1000 to all these executables.
Now can you please explain the differences among all these?
Best Answer
The only major difference is between sourcing and executing a script.
source foo.sh
will source it and all the other examples you show are executing. In more detail:./file.sh
This will execute a script called
file.sh
that is in the current directory (./
). Normally, when you runcommand
, the shell will look through the directories in your$PATH
for an executable file calledcommand
. If you give a full path, such as/usr/bin/command
or./command
, then the$PATH
is ignored and that specific file is executed.../file.sh
This is basically the same as
./file.sh
except that instead of looking in the current directory forfile.sh
, it is looking in the parent directory (../
).sh file.sh
This equivalent to
sh ./file.sh
, as above it will run the script calledfile.sh
in the current directory. The difference is that you are explicitly running it with thesh
shell. On Ubuntu systems, that isdash
and notbash
. Usually, scripts have a shebang line that gives the program they should be run as. Calling them with a different one overrides that. For example:That script will simply print the name of the shell used to run it. Let's see what it returns when called in different ways:
So, calling calling a script with
shell script
will override the shebang line (if present) and run the script with whatever shell you tell it.source file.sh
or. file.sh
This is called, surprisingly enough, sourcing the script. The keyword
source
is an alias to the shell builtin.
command. This is a way of executing the script within the current shell. Normally, when a script is executed, it is run in its own shell which is different than the current one. To illustrate:Now, if I set the variable
foo
to something else in the parent shell and then run the script, the script will print a different value offoo
(because it is also set within the script) but the value offoo
in the parent shell will be unchanged:However, if I source the script instead of executing it, it will be run in the same shell so the value of
foo
in the parent will be changed:So, sourcing is used in the few cases where you want a script to affect the shell you are running it from. It is typically used to define shell variables and have them available after the script finishes.
With all that in mind, the reason you get different answers is, first of all, that your script does not do what you think it does. It counts the number of times that
bash
appears in the output ofps
. This is not the number of open terminals, it is the number of running shells (in fact, it isn't even that, but that's another discussion). To clarify, I simplified your script a little to this:And run it in the various ways with only a single terminal open:
Direct launching,
./foo.sh
.Here, you are using the shebang line. This means that the script is executed directly by whatever is set there. This affects the way that the script is shown in the output of
ps
. Instead of being listed asbash foo.sh
, it will only be shown asfoo.sh
which means that yourgrep
will miss it. There are actually 3 bash instances running: the parent process, the bash running the script and another one which runs theps
command. This last is important, launching a command with command substitution (`command`
or$(command)
) results in a copy of the parent shell being launched and that runs the command. Here, however, none of these are shown because of the way thatps
shows its output.Direct launching with explicit (bash) shell
Here, because you're running with
bash foo.sh
, the output ofps
will showbash foo.sh
and be counted. So, here we have the parent process, thebash
running the script and the cloned shell (running theps
) all shown because nowps
will show each of them because your command will include the wordbash
.Direct launching with a different shell (
sh
)This is different because you are running the script with
sh
and notbash
. Therefore, the onlybash
instance is the parent shell where you launched your script. All the other shells mentioned above are being run bysh
instead.Sourcing (either by
.
orsource
, same thing)As I explained above, sourcing a script causes it to run in the same shell as the parent process. However, a separate subshell is started to launch the
ps
command and that brings the total to two.As a final note, the correct way to count running processes is not to parse
ps
but to usepgrep
. All of these problems would have been avoided had you just runSo, a working version of your script that always prints the right number is (note the absence of command substitution):
That will return 1 when sourced and 2 (because a new bash will be launched to run the script) for all other ways of launching. It will still return 1 when launched with
sh
since the child process is notbash
.