How can I tell if I'm in a child shell - bash

If I'm using bash and type bash I'm in the child shell and need to type exit to go back to the original parent shell. If I forget which one I'm in how do I check?

Use the SHLVL environment variable.
man bash:
SHLVL : Incremented by one each time an instance of bash is started.
$ echo "$SHLV"
1
$ bash
$ echo "$SHLV"
2
$ exit
$ echo "$SHLV"
1

This is an inferior answer but you can also use pstree:
$ pstree -s $BASHPID
systemd───systemd───gnome-terminal-───bash───pstree
$ bash
$ pstree -s $BASHPID
systemd───systemd───gnome-terminal-───bash───bash───pstree

Related

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

How to save PID to variable in bash -c?

I'm having trouble with saving the PID into a variable when I'm using bash -c. For example:
bash -c "PID=$$; echo $PID"
In this case the output is empty. How to save the child PID (the PID of the command inside the double quotes) now in the variable PID
just use simple quotes or you expression is evaluated inside your current command line (too soon) and not in the child bash command, and PID isn't defined yet at this moment and you're actually passing
bash -c "PID=4353; echo"
(where 4353 is the pid of the current bash process)
Someone noted that it's not clear if you want to pass parent pid or child pid
to pass parent pid, fix it like this (only the part within double quotes is evaluated before calling bash child process):
bash -c "PID=$$; "'echo $PID'
to pass child pid, fix it like this (nothing is evaluated in the current shell, same trick used for awk scripts):
bash -c 'PID=$$; echo $PID'
set -x is useful for debugging and will show the actual command you end up running prefixed with +:
$ set -x
$ bash -c "PID=$$; echo $PID"
+ bash -c 'PID=1900; echo '
And indeed, in that command you would expect empty output. This happens because $$ and $PID are substituted before bash is called.
To avoid this, you can single quote the string or escape the "$"s:
$ set -x
$ bash -c 'PID=$$; echo $PID'
+ bash -c 'PID=$$; echo $PID'
1925
$ bash -c "PID=\$\$; echo \$PID"
+ bash -c 'PID=$$; echo $PID'
1929

Command substitution and $PATH variable

Background
This [ article ] says :
The command substitution expands to the output of commands. These
commands are executed in a subshell ..
But the bash manual says nothing about a subshell in its command substitution section.
My test below
$ ps
PID TTY TIME CMD
26483 pts/25 00:00:00 bash
26866 pts/25 00:00:00 ps
$ hpid="$(ps | grep bash)"
$ echo "$hpid"
26483 pts/25 00:00:00 bash
26899 pts/25 00:00:00 bash
shows that a new shell with pid 26899 was spawned during the command substitution. At this point I changed the PATH environment variable.
$ PATH="/some/rogue/path"
did the below stuff :
VAR="$(echo "Do|Die" | cut -d"|" -f 2)"
and got the below error :
Command 'cut' is available in '/usr/bin/cut'
The command could not be located because '/usr/bin' is not included in the PATH environment variable.
cut: command not found
I understand that the error is due to the modification of PATH environment variable which helps the shell locate the binaries. However I am confused when reading this together with command substitution.
If by $(..) a subshell is spawned, then PATH environment variable should be intact and should point to the binary (cut in this case) and so bash should not complain that it cannot locate the cut binary.
Question
How did the modification of the PATH affect the command substitution here?
Consider below example:
$ export PS1='\$\$=$$ \$ '
$$=30862 $ a=123 # Note: No export a here.
$$=30862 $ echo $a
123
$$=30862 $ bash
$$=31133 $ echo $a # Subshell explicitly created does not have it.
$$=31133 $ exit
$$=30862 $ echo $(eval 'echo $a') # This subshell however does inherit it. The single quote ensures that this is not evaluated by parent shell.
123 # echo $(echo $a) would probably cause $a to be evaluated by parent shell.
$$=30862 $
In short, subshells spawned by $(...) inherit same environment as parent shell, even if the variable is not exported. (Even $$ is same as parent shell.)

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=

Bash script: how to get the whole command line which ran the script

I would like to run a bash script and be able to see the command line used to launch it:
sh myscript.sh arg1 arg2 1> output 2> error
in order to know if the user used the "std redirection" '1>' and '2>', and therefore adapt the output of my script.
Is it possible with built-in variables ??
Thanks.
On Linux and some unix-like systems, /proc/self/fd/1 and /proc/self/fd/2 are symlinks to where your std redirections are pointing to. Using readlink, we can query if they were redirected or not by comparing them to the parent process' file descriptor.
We will however not use self but $$ because $(readlink /proc/"$$"/fd/1) spawns a new shell so self would no longer refer to the current bash script but to a subshell.
$ cat test.sh
#!/usr/bin/env bash
#errRedirected=false
#outRedirected=false
parentStderr=$(readlink /proc/"$PPID"/fd/2)
currentStderr=$(readlink /proc/"$$"/fd/2)
parentStdout=$(readlink /proc/"$PPID"/fd/1)
currentStdout=$(readlink /proc/"$$"/fd/1)
[[ "$parentStderr" == "$currentStderr" ]] || errRedirected=true
[[ "$parentStdout" == "$currentStdout" ]] || outRedirected=true
echo "$0 ${outRedirected:+>$currentStdout }${errRedirected:+2>$currentStderr }$#"
$ ./test.sh
./test.sh
$ ./test.sh 2>/dev/null
./test.sh 2>/dev/null
$ ./test.sh arg1 2>/dev/null # You will lose the argument order!
./test.sh 2>/dev/null arg1
$ ./test.sh arg1 2>/dev/null >file ; cat file
./test.sh >/home/camusensei/file 2>/dev/null arg1
$
Do not forget that the user can also redirect to a 3rd file descriptor which is open on something else...!
Not really possible. You can check whether stdout and stderr are pointing to a terminal: [ -t 1 -a -t 2 ]. But if they do, it doesn't necessarily mean they weren't redirected (think >/dev/tty5). And if they don't, you can't distinguish between stdout and stderr being closed and them being redirected. And even if you know for sure they are redirected, you can't tell from the script itself where they point after redirection.

Resources