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

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.

Related

What is the difference between "bash -i myscript.sh" vs "bash myscript.sh"?

According to bash man page, it says -i is for interactive mode of shell.
I tried example code to find out what -i option does.
interactive.sh is script that needs user input, which means interactive script.
The default bash option is non-interactive mode.
But the interactive.sh runs without any problem with non-interactive mode.
It also runs well with interactive mode. It confuses me.
What is the exact usage of -i option in bash?
What is the difference between interactive and non-interactive mode in shell?
$cat interactive.sh
#!/bin/bash
echo 'Your name ?'
read name
echo "Your name is $name"
$ bash interactive.sh
Your name ?
ABC
Your name is ABC
$ bash -i interactive.sh
Your name ?
DEF
Your name is DEF
With bash -i script you are running interactive non-login shell.q
What is the exact usage of -i option in bash?
From man bash:
-i If the -i option is present, the shell is interactive.
What is the difference between interactive and non-interactive mode in shell?
There are some differences. Look at man bash | grep -i -C5 interactive | less:
An interactive shell is one started without non-option arguments (unless -s is specified) and without the -c option whose standard input and er‐
ror are both connected to terminals (as determined by isatty(3)), or one started with the -i option. PS1 is set and $- includes i if bash is
interactive, allowing a shell script or a startup file to test this state.
When an interactive login shell exits, or a non-interactive login shell executes the exit builtin command, bash reads and executes commands from
the file ~/.bash_logout, if it exists.
When an interactive shell that is not a login shell is started, bash reads and executes commands from ~/.bashrc, if that file exists. This may
be inhibited by using the --norc option. The --rcfile file option will force bash to read and execute commands from file instead of ~/.bashrc.
[...]
When bash is interactive, in the absence of any traps, it ignores SIGTERM (so that kill 0 does not kill an interactive shell), and SIGINT is
caught and handled (so that the wait builtin is interruptible). In all cases, bash ignores SIGQUIT. If job control is in effect, bash ignores
SIGTTIN, SIGTTOU, and SIGTSTP.
etc. For example bash -i -c 'echo $PS1'.

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 exit multiple nested shells at once?

I have a host on which I don't have sudo. Its been setup with ksh, I'm too used to bash and chsh doesn't work. So I put in a /bin/bash as the first line in the .profile on the system.
So the result is, when I login to this system, it automatically gets me into bash. However, when I exit the shell, not suprisingly I land up in ksh.
Any tricks to avoid this?
Use exec to replace the current process (shell) with the new process (shell).
I recommend two steps:
if [ $SHELL != /bin/bash ]
then SHELL=/bin/bash exec /bin/bash --login
fi
Or, you can compress that to:
[ $SHELL != /bin/bash ] && SHELL=/bin/bash exec /bin/bash --login
You can then put the rest of your Bash profile after this. Note that probably you don't put a shebang on the first line - that will confuse things. Also, while testing, make sure you have a second connection (window) open so that you can adjust problems. It is annoying to get locked out by an erroneous profile.
You may write a script named myexit like this:
kill -1 $(ps | sed 1d | awk '{print $1}')
It sends the signal hang up (SIGHUP) to process attached to this terminal.
And would not affect any process started up by nohup.

Determining whether shell script was executed "sourcing" it

Is it possible for a shell script to test whether it was executed through source? That is, for example,
$ source myscript.sh
$ ./myscript.sh
Can myscript.sh distinguish from these different shell environments?
I think, what Sam wants to do may be not possible.
To what degree a half-baken workaround is possible, depends on...
...the default shell of users, and
...which alternative shells they are allowed to use.
If I understand Sam's requirement correctly, he wants to have a 'script',
myscript, that is...
...not directly executable via invoking it by its name myscript
(i.e. that has chmod a-x);
...not indirectly executable for users by invoking sh myscript or
invoking bash myscript
...only running its contained functions and commands if invoked by
sourcing it: . myscript
The first things to consider are these
Invoking a script directly by its name (myscript) requires a first line in
the script like #!/bin/bash or similar. This will directly determine which
installed instance of the bash executable (or symlink) will be invoked to run
the script's content. This will be a new shell process. It requires the
scriptfile itself to have the executable flag set.
Running a script by invoking a shell binary with the script's (path+)name as
an argument (sh myscript), is the same as '1.' -- except that the
executable flag does not need to be set, and said first line with the
hashbang isn't required either. The only thing needed is that the invoking
user needs read access to the scriptfile.
Invoking a script by sourcing its filename (. myscript) is very much the
same as '1.' -- exept that it isn't a new shell that is invoked. All the
script's commands are executed in the current shell, using its environment
(and also "polluting" its environment with any (new) variables it may set or
change. (Usually this is a very dangerous thing to do: but here it could be
used to execute exit $RETURNVALUE under certain conditions....)
For '1.':
Easy to achieve: chmod a-x myscript will prevent myscript from being
directly executable. But this will not fullfill requirements '2.' and '3.'.
For '2.' and '3.':
Much harder to achieve. Invokations by sh myscript require reading
privileges for the file. So an obvious way out would seem to chmod a-r
myscript. However, this will also dis-allow '3.': you will not be able to
source the script either.
So what about writting the script in a way that uses a Bashism? A Bashism is a
specific way to do something which other shells do not understand: using
specific variables, commands etc. This could be used inside the script to
discover this condition and "do something" about it (like "display warning.txt",
"mailto admin" etc.). But there is no way in hell that this will prevent sh or
bash or any other shell from reading and trying to execute all the following
commands/lines written into the script unless you kill the shell by invoking
exit.
Examples: in Bash, the environment seen by the script knows of $BASH,
$BASH_ARGV, $BASH_COMMAND, $BASH_SUBSHELL, BASH_EXECUTION_STRING... . If
invoked by sh (also if sourced inside a sh), the executing shell will see
all these $BASH_* as empty environment variables. Again, this could be used
inside the script to discover this condition and "do something"... but not
prevent the following commands from being invoked!
I'm now assuming that...
...the script is using #!/bin/bash as its first line,
...users have set Bash as their shell and are invoking commands in the
following table from Bash and it is their login shell,
...sh is available and it is a symlink to bash or dash.
This will mean the following invokations are possible, with the listed values
for environment variables
vars+invok's | ./scriptname | sh scriptname | bash scriptname | . scriptname
---------------+--------------+---------------+-----------------+-------------
$0 | ./scriptname | ./scriptname | ./scriptname | -bash
$SHLVL | 2 | 1 | 2 | 1
$SHELLOPTS | braceexpand: | (empty) | braceexpand:.. | braceexpand:
$BASH | /bin/bash | (empty) | /bin/bash | /bin/bash
$BASH_ARGV | (empty) | (empty) | (empty) | scriptname
$BASH_SUBSHELL | 0 | (empty) | 0 | 0
$SHELL | /bin/bash | /bin/bash | /bin/bash | /bin/bash
$OPTARG | (empty) | (empty) | (emtpy) | (emtpy)
Now you could put a logic into your text script:
If $0 is not equal to -bash, then do an exit $SOMERETURNVALUE.
In case the script was called via sh myscript or bash myscript, then it will
exit the calling shell. In case it was run in the current shell, it will
continue to run. (Warning: in case the script has any other exit statements,
your current shell will be 'killed'...)
So put into your non-executable myscript.txt near its beginning something like
this may do something close to your goal:
echo BASH=$BASH
test x${BASH} = x/bin/bash && echo "$? : FINE.... You're using 'bash ...'"
test x${BASH} = x/bin/bash || echo "$? : RATS !!! -- You're not using BASH and I will kick you out!"
test x${BASH} = x/bin/bash || exit 42
test x"${0}" = x"-bash" && echo "$? : FINE.... You've sourced me, and I'm your login shell."
test x"${0}" = x"-bash" || echo "$? : RATS !!! -- You've not sourced me (or I'm not your bash login shell) and I will kick you out!"
test x"${0}" = x"-bash" || exit 33
This may or may not be what the asker wanted but, on a similar situation, I wanted a script to indicate that it is meant to be sourced and not directly run.
To achieve this effect my script reads:
#!/bin/echo Should be run as: source
export SOMEPATH="/some/path/on/my/system"
echo "Your environment has been set up"
So when I run it either as a command or sourced I get:
$ ./myscript.sh
Should be run as: source ./myscript.sh
$ source ./myscript.sh
Your environment has been set up
You can of course fool the script by running it as sh ./myscript.sh, but at least it gives the correct expected behaviour on 2 out of 3 cases.
This is what I was looking for:
[[ ${BASH_SOURCE[0]} = $0 ]] && main "$#"
I cannot add comment yet (stackexchange policies) so I add my own answer:
This one may works regardless if we do:
bash scriptname
scriptname
./scriptname.
on both bash and mksh.
if [ "${0##/*}" == scriptname ] # if the current name is our script
then
echo run
else
echo sourced
fi
If you have a non-altering file path for regular users, then:
if [ "$(/bin/readlink -f "$0")" = "$KNOWN_PATH_OF_THIS_FILE" ]; then
# the file was executed
else
# the file was sourced
fi
(it can also easily be loosened to only check for the filename or whatever).
But your users need to have read permission to be able to source the file, so absolutely nothing can stop them from doing what they want with the file. But it might help them out to not use it in the wrong way.
This solution is not dependent on Bashisms.
Yes it is possible. In general you can do the following:
#! /bin/bash
sourced () {
echo Sourced
}
executed () {
echo Executed
}
if [[ ${0##*/} == -* ]]; then
sourced
else
executed $#
fi
Giving the following output:
$ ./myscript
Executed
$ . ./myscript
Sourced
Based on Kurt Pfeifle’s answer, this works for me
if [ $SHLVL = 1 ]
then
echo 'script was sourced'
fi
Example
Since all of our machines have history, I did this:
check_script_call=$(history |tail -1|grep myscript.sh )
if [ -z "$check_script_call" ];then
echo "This file should be called as a source."
echo "Please, try again this way:"
echo "$ source /path/to/myscript.sh"
exit 1
fi
Everytime you run a script (without source), your shell creates a new env without history.
If you want to care about performance you can try this:
if ! history |tail -1|grep set_vars ;then
echo -e "This file should be called as a source.\n"
echo "Please, try again this way:"
echo -e "$ source /path/to/set_vars\n"
exit 1
fi
PS: I think Kurt's answer is much more complete but I think this could help.
In the first case, $0 will be "myscript.sh". In the second case, it will be "./myscript". But, in general, there's no way to tell source was used.
If you tell us what you're trying to do, instead of how you want to do it, a better answer might be forthcoming.

How to set the process name of a shell script?

Is there any way to set the process name of a shell script? This is needed for killing this script with the killall command.
Here's a way to do it, it is a hack/workaround but it works pretty good. Feel free to tweak it to your needs, it certainly needs some checks on the symbolic link creation or using a tmp folder to avoid possible race conditions (if they are problematic in your case).
Demonstration
wrapper
#!/bin/bash
script="./dummy"
newname="./killme"
rm -iv "$newname"
ln -s "$script" "$newname"
exec "$newname" "$#"
dummy
#!/bin/bash
echo "I am $0"
echo "my params: $#"
ps aux | grep bash
echo "sleeping 10s... Kill me!"
sleep 10
Test it using:
chmod +x dummy wrapper
./wrapper some params
In another terminal, kill it using:
killall killme
Notes
Make sure you can write in your current folder (current working directory).
If your current command is:
/path/to/file -q --params somefile1 somefile2
Set the script variable in wrapper to /path/to/file (instead of ./dummy) and call wrapper like this:
./wrapper -q --params somefile1 somefile2
You can use the kill command on a PID so what you can do is run something in the background, get its ID and kill it
PID of last job run in background can be obtained using $!.
echo test & echo $!
You cannot do this reliably and portably, as far as I know. On some flavors of Unix, changing what's in argv[0] will do the job. I don't believe there's a way to do that in most shells, though.
Here are some references on the topic.
Howto change a UNIX process and child process name by modifying argv0
Is there a way to change the effective process name in Python?
This is an extremely old post. Pretty sure the original poster got his/her answer long ago. But for newcomers, thought I'd explain my own experience (after playing with bash for a half hour). If you start a script by script name w/ something like:
./script.sh
the process name listed by ps will be "bash" (on my system). However if you start a script by calling bash directly:
/bin/bash script.sh
/bin/sh script.sh
bash script.sh
you will end up with a process name that contains the name of the script. e.g.:
/bin/bash script.sh
results in a process name of the same name. This can be used to mark pids with a specific script name. And, this can be useful to (for example) use the kill command to stop all processes (by pid) that have a process name containing said script name.
You can all use the -f flag to pgrep/pkill which will search the entire command line rather than just the process name. E.g.
./script &
pkill -f script
Include
#![path to shell]
Example for path to shell -
/usr/bin/bash
/bin/bash
/bin/sh
Full example
#!/usr/bin/bash
On Linux at least, killall dvb works even though dvb is a shell script labelled with #!. The only trick is to make the script executable and invoke it by name, e.g.,
dvb watch abc write game7 from 9pm for 3:30
Running ps shows a process named
/usr/bin/lua5.1 dvb watch ...
but killall dvb takes it down.
%1, %2... also do an adequate job:
#!/bin/bash
# set -ex
sleep 101 &
FIRSTPID=$!
sleep 102 &
SECONDPID=$!
echo $(ps ax|grep "^\(${FIRSTPID}\|${SECONDPID}\) ")
kill %2
echo $(ps ax|grep "^\(${FIRSTPID}\|${SECONDPID}\) ")
sleep 1
kill %1
echo $(ps ax|grep "^\(${FIRSTPID}\|${SECONDPID}\) ")
I put these two lines at the start of my scripts so I do not have to retype the script name each time I revise the script. It won't take $0 of you put it after the first shebang. Maybe someone who actually knows can correct me but I believe this is because the script hasn't started until the second line so $0 doesn't exist until then:
#!/bin/bash
#!/bin/bash ./$0
This should do it.
My solution uses a trivial python script, and the setproctitle package. For what it's worth:
#!/usr/bin/env python3
from sys import argv
from setproctitle import setproctitle
from subprocess import run
setproctitle(argv[1])
run(argv[2:])
Call it e.g. run-with-title and stick it in your path somewhere. Then use via
run-with-title <desired-title> <script-name> [<arg>...]
Run bash script with explicit call to bash (not just like ./test.sh). Process name will contain script in this case and can be found by script name. Or by explicit call to bash with full path as
suggested in display_name_11011's answer:
bash test.sh # explicit bash mentioning
/bin/bash test.sh # or with full path to bash
ps aux | grep test.sh | grep -v grep # searching PID by script name
If the first line in script (test.sh) explicitly specifies interpreter:
#!/bin/bash
echo 'test script'
then it can be called without explicit bash mentioning to create process with name '/bin/bash test.sh':
./test.sh
ps aux | grep test.sh | grep -v grep
Also as dirty workaround it is possible to copy and use bash with custom name:
sudo cp /usr/bin/bash /usr/bin/bash_with_other_name
/usr/bin/bash_with_other_name test.sh
ps aux | grep bash_with_other_name | grep -v grep
Erm... unless I'm misunderstanding the question, the name of a shell script is whatever you've named the file. If your script is named foo then killall foo will kill it.
We won't be able to find pid of the shell script using "ps -ef | grep {scriptName}" unless the name of script is overridden using shebang. Although all the running shell scripts come in response of "ps -ef | grep bash". But this will become trickier to identify the running process as there will be multiple bash processing running simultaneously.
So a better approach is to give an appropriate name to the shell script.
Edit the shell script file and use shebang (the very first line) to name the process e.g. #!/bin/bash /scriptName.sh
In this way we would be able to grep the process id of scriptName using
"ps -ef | grep {scriptName}"

Resources