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

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=

Related

Saving the result of an echo command in a shell script?

I am attempting to store the result of an echo command as a variable to be used in a shell script. Debian 4.19.0-6-amd64
The command works in terminal: echo $HOSTNAME returns debian-base, the correct hostname.
I attempt to run it in a shell script, such as:
#!/usr/bin/bash
CURRENT_HOSTNAME=`echo $HOSTNAME`
echo $CURRENT_HOSTNAME
I have tried expansion:
CURRENT_HOSTNAME=$(echo $HOSTNAME)
And just to cover some more bases, I tried things like:
CURRENT_HOSTNAME=$HOSTNAME
# or
CURRENT_HOSTNAME="$HOSTNAME"
# also, in case a problem with reserved names:
test=$HOSTNAME
test="$HOSTNAME"
Works great in the terminal! Output is as follows:
root#debian-base:/scripts# echo $HOSTNAME
debian-base
root#debian-base:/scripts# TEST_HOSTNAME=$HOSTNAME
root#debian-base:/scripts# echo $TEST_HOSTNAME
debian-base
root#debian-base:/scripts# TEST_TWO_HOSTNAME=$(echo $HOSTNAME)
root#debian-base:/scripts# echo $TEST_TWO_HOSTNAME
debian-base
As soon as I run the script (as above):
root#debian-base:/scripts# sh test.sh
root#debian-base:/scripts#
What am I doing wrong?
You are using bash as your terminal. Bash has the variable $HOSTNAME set. You run your script with sh. sh does not have a $HOSTNAME.
Options:
bash test.sh
Or run it as a program:
chmod +x test.sh
./test.sh
But I think you need to change your first line to:
#!/bin/bash
As I don't think bash is installed in /usr/bin in most cases. But you need to try. To figure out where bash is installed use which bash
Another option is to use the hostname binary:
CURRENT_HOSTNAME=$(hostname)
echo $CURRENT_HOSTNAME
Which works in both bash and sh.
You can start sh by just running sh. You will see it has a bash-like terminal. You can try to do echo $HOSTNAME. It will not show, because it's not there. You can use set to see all the variables that are there (as sh does not have tab completion it's harder to figure out).

How can I redirect stdout and stderr with variant?

Normally, we use
sh script.sh 1>t.log 2>t.err
to redirect log.
How can I use variant to log:
string="1>t.log 2>t.err"
sh script.sh $string
You need to use 'eval' shell builtin for this purpose. As per man page of bash command:
eval [arg ...]
The args are read and concatenated together into a single command. This command is then read and exe‐
cuted by the shell, and its exit status is returned as the value of eval. If there are no args, or
only null arguments, eval returns 0.
Run your command like below:
eval sh script.sh $string
However, do you really need to run script.sh through sh command? If you instead put sh interpreter line (using #!/bin/sh as the first line in your shell script) in your script itself and give it execute permission, that would let you access return code of ls command. Below is an example of using sh and not using sh. Notice the difference in exit codes.
Note: I had only one file try.sh in my current directory. So ls command was bound to exit with return code 2.
$ ls try1.sh try1.sh.backup 1>out.txt 2>err.txt
$ echo $?
2
$ eval sh ls try1.sh try1.sh.backup 1>out.txt 2>err.txt
$ echo $?
127
In the second case, the exit code is of sh shell. In first case, the exit code is of ls command. You need to make cautious choice depending on your needs.
I figure out one way but it's ugly:
echo script.sh $string | sh
I think you can just put the name into a string variable
and then use data redirection
file_name="file1"
outfile="$file_name"".log"
errorfile="$file_name"".err"
sh script.sh 1> $outfile 2> $errorfile

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.

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

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.

How to specify zeroeth argument

I'm writing a bash script that starts the tcsh interpreter as a login shell and has it execute my_command. The tcsh man page says that there are two ways to start a login shell. The first is to use /bin/tcsh -l with no other arguments. Not an option, because I need the shell to execute my_command. The second is to specify a dash (-) as the zeroeth argument.
Now the bash exec command with the -l option does exactly this, and in fact the following works perfectly:
#!/bin/bash
exec -l /bin/tcsh -c my_command
Except... I can't use exec because I need the script to come back and do some other things afterwards! So how can I specify - as the zeroeth argument to /bin/tcsh without using exec?
You can enclose the exec command into a sub-shell of your script.
#!/bin/bash
(exec -l /bin/tcsh -c my_command)
# ... whatever else you need to do after the command is done
You can write a wrapper (w.sh) script that contains:
#!/bin/bash
exec -l /bin/tcsh -c my_command
and execute w.sh in your main script.

Resources