ps and /proc/$$/cmdline don't show script arguments without shebang - bash

I've come across a behavior I can't explain. When I run a bash script without a shebang, the ps command will not show the script and its arguments as arguments passed to bash, neither will /proc/$$/cmdline, whereas If I run the script with a shebang, behavior is as expected.
Example with a shebang:
# cat mytest
#!/bin/bash
echo my name is $1
cat /proc/$$/cmdline
echo
ps -p $$ -o args=
# ./mytest John
my name is John
/bin/bash./mytestJohn
/bin/bash ./mytest John
Example without a shebang:
# cat mytest
echo my name is $1
cat /proc/$$/cmdline
echo
ps -p $$ -o args=
# ./mytest John
my name is John
-bash
-bash
In both cases the script will display 'my name is John', but without a shebang I see the bash process without any arguments. How is this possible?

Related

What does /bin/bash $0 $0 do in the 5th line in the following case?

Can someone explain the penultimate line of the following code? I'm trying to understand what does /bin/bash command do in general.
#!/bin/bash
while [ $1 ]
do
shift
/bin/bash $0 $0
done
It is easy to build a reverse-engineering of it to understand what it does by inserting echo before the statement:
#!/bin/bash
while [ "$1" ]
do
shift
echo /bin/bash $0 $0
done
Running it as ./myscript a b c d e f g produces:
/bin/bash ./myscript ./myscript
/bin/bash ./myscript ./myscript
/bin/bash ./myscript ./myscript
/bin/bash ./myscript ./myscript
/bin/bash ./myscript ./myscript
/bin/bash ./myscript ./myscript
/bin/bash ./myscript ./myscript
The /bin/bash just after the echo is printed as-is:
The next two ./myscript.sh are the value of argument $0 which always contains the caller command (here the myscript script).
So for each argument, this script runs itself with itself as argument, using bash.
what does /bin/bash command do in general
/bin/bash is the bash shell interpreter with its full path on disk.
Reading the bash man page man bash should provide extensive answers to your question.
$0 normally returns the name of the script . $1 return parameter passed number one

How can i find the process name by the shell script that invokes it?

Is there a way, I can find the process name of bash script by the shell script that was used to invoke it? or can I set process name of bash script to something such as
-myprocess
(I have looked into argv[0], but I am not clear about it)
so when I use
ps -ef | grep -c '[M]yprocess'
I get only all the instances of myprocess?
To obtain the command name of the current script that is running, use:
ps -q $$ -o comm=
To get process information on all running scripts that have the same name as the current script, use:
ps -C "$(ps -q $$ -o comm=)"
To find just the process IDs of all scripts currently being run that have the same name as the current script, use:
pgrep "$(ps -q $$ -o comm=)"
How it works
$$ is the process ID of the script that is being run.
The option -q $$ tells ps to report on only process ID $$.
The option -o comm= tells ps to omit headers and to skip the usual output and instead print just the command name.
The parent process id can be obtained from $PPID on bash and ksh. We can read the fields from ps into an array.
For bash you can try this. The problem with ps is that many options are non-standard, so I have kept that as generic as possible:
#!/bin/bash
while read -a fields
do
if [[ ${fields[0]} == $PPID ]]
then
echo "Shell: ${fields[3]}"
echo "Command: ${fields[4]}"
fi
done < <(ps -p $PPID)
You have tagged bash and ksh, but they have different syntax rules. To read into an array bash uses -a but ksh uses -A, So for korn shell you would need to change the read line (and the #! line):
while read -A fields
Not sure what you mean by that, but let's go with an example script b.sh.
#!/usr/local/bin/bash
echo "My name is $0, and I am running under $SHELL as the shell."
Running the script will give you:
$ bash b.sh
My name is b.sh, and I am running under /usr/local/bin/bash as the shell.
For more check this answer: HOWTO: Detect bash from shell script

How to pass argument in bash pipe from terminal

i have a bash script show below in a file called test.sh
#!/usr/bin/env bash
echo $1
echo "execution done"
when i execute this script using
Case-1
./test.sh "started"
started
execution done
showing properly
Case-2
If i execute with
bash test.sh "started"
i'm getting the out put as
started
execution done
But i would like to execute this using a cat or wget command with arguments
For example like.
Q1
cat test.sh |bash
Or using a command
Q2
wget -qO - "url contain bash" |bash
So in Q1 and Q2 how do i pass argument
Something simlar to this shown in this github
https://github.com/creationix/nvm
Please refer installation script
$ bash <(curl -Ls url_contains_bash_script) arg1 arg2
Explanation:
$ echo -e 'echo "$1"\necho "done"' >test.sh
$ cat test.sh
echo "$1"
echo "done"
$ bash <(cat test.sh) "hello"
hello
done
$ bash <(echo -e 'echo "$1"\necho "done"') "hello"
hello
done
You don't need to pipe to bash; bash runs as standard in your terminal.
If I have a script and I have to use cat, this is what I'll do:
cat script.sh > file.sh; chmod 755 file.sh; ./file.sh arg1 arg2 arg3
script.sh is the source script. You can replace that call with anything you want.
This has security implications though; just running an arbitrary code in your shell - especially with wget where the code comes from a remote location.

Determination of a script piping an output into another script

I have 2 scripts. Script a.sh is piping output to script b.sh processing the output as follows:
$ cat a.sh
#!/bin/bash
echo output | ./b.sh ### piping into STDIN of b.sh script
$
$ cat b.sh
#!/bin/bash
grep output ### reading from STDIN
$
$ ./a.sh
output
Is there any way I can determine in script b.sh from which script it's getting input? I would like b.sh script to find out it's a.sh. I tried to work with content of /proc/$$/fd in combination with lsof but without success.
This may be a silly solution but you can use ps to find the parent process than get the command from that process again using ps.
Example by by adding this to the b.sh you gave above:
ps -p $(ps -o ppid= -p $$) -o cmd=
the output when called from the a.sh script was:
/bin/bash ./a.sh
when called directly from the command line:
-bash
I suppose you could do it using the /proc/$$ folders to achieve the same thing looking in /proc/$$ get the parentPid and read /proc/$(parentPid)/cmdline to get the same result.
so this way you would do something like:
parentPid=$(cat /proc/$$/stat | awk '{print $4}')
cat /proc/$(parentPid)/cmdline
output when b.sh is called from a.sh:
/bin/bash./a.sh
You could use the ps command with the PPID option, or the caller command to check which script called it.

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