Which process executes script in terminal - bash

when i write ./test1.sh in the MacOS terminal which process executes the script ?
I have installed on my machine oh-my-zsh and thus run zsh in my terminal.
Running test1.sh like so:
./test1.sh
outputs
1 март 2016/ 1 януари 2015/
1 март 2016/
./test3.sh: line 14: declare: -A: invalid option declare: usage: declare [-afFirtx] [-p] [name[=value] ...]
./test3.sh: line 15: януари: syntax error: invalid arithmetic operator (error token is "?нуари")
blah
while running it like this
zsh test3.sh
outputs
1 март 2016/
1 януари 2015/
blah
On the other hand running
declare -A newarray
newarray[януари]="1"
qn="януари"
echo ${newarray[$qn]}
outputs
1
Why is that ?
test3.sh
# backup IFS
SAVEIFS=$IFS
# set IFS to newline
IFS=$(echo -en "\n\b")
# get files
FILES=$(ls -1 -d */)
echo ${FILES}
IFS='\n'
read dirsNameArray <<< ${FILES}
echo ${dirsNameArray[0]}
declare -A monthMap
monthMap['януари']="1"
# monthMap[февруари]="2"
# monthMap[март]="3"
# monthMap[април]="4"
# monthMap[май]="5"
# monthMap[юни]="6"
# monthMap[юли]="7"
# monthMap[август]="8"
# monthMap[септември]="9"
# monthMap[октомври]="10"
# monthMap[ноември]="11"
# monthMap[декември]="12"
# iterate over files
IFS='\n'
for f in $FILES
do
echo "blah"
IFS=' '
# read -r dirNameArray <<< $f
# echo "${monthMap[${dirNameArray[2]}]}"
IFS='\n'
done
# restore $IFS
IFS=$SAVEIFS

You should always include a shebang in your scripts. If you want your shell script to be run by zsh then make sure that the topmost line of you script looks like that:
#!/bin/zsh
This will guarantee that your script will be executed by /bin/zsh (or whatever other executable you specified in the shebang).
If you want to find out what shell is used to execute your script, add the following line to it:
ps ho cmd $$
and see what it prints. If you want to know what shell is used in an interactive session, check if either $BASH_VERSION or $ZSH_VERSION is defined.

Let's find out what shell zsh uses to execute text files:
% echo 'ps -f $$' > script.sh && chmod +x script.sh && ./script.sh
UID PID PPID C STIME TTY STAT TIME CMD
slim 17311 16570 0 15:45 pts/0 S+ 0:00 sh ./script.sh
So it uses sh. That make sense, sh is the lowest common denominator, default shell.
To force a different shell, use #! in the first line of the text file:
% echo '#!/bin/zsh' > script.sh && echo 'ps -f $$' >> script.sh && chmod +x script.sh && ./script.sh
UID PID PPID C STIME TTY STAT TIME CMD
slim 17342 16570 0 15:46 pts/0 S+ 0:00 /bin/zsh ./script.sh
#! is a general purpose mechanism, so you can use it to execute pretty much anything that reads from stdin and ignores "comments" beginning with # -- perl, python, most shells, awk, even things like gnuplot.

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 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.

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.

How to invoke bash, run commands inside the new shell, and then give control back to user?

This must either be really simple or really complex, but I couldn't find anything about it... I am trying to open a new bash instance, then run a few commands inside it, and give the control back to the user inside that same instance.
I tried:
$ bash -lic "some_command"
but this executes some_command inside the new instance, then closes it. I want it to stay open.
One more detail which might affect answers: if I can get this to work I will use it in my .bashrc as alias(es), so bonus points for an alias implementation!
bash --rcfile <(echo '. ~/.bashrc; some_command')
dispenses the creation of temporary files. Question on other sites:
https://serverfault.com/questions/368054/run-an-interactive-bash-subshell-with-initial-commands-without-returning-to-the
https://unix.stackexchange.com/questions/123103/how-to-keep-bash-running-after-command-execution
This is a late answer, but I had the exact same problem and Google sent me to this page, so for completeness here is how I got around the problem.
As far as I can tell, bash does not have an option to do what the original poster wanted to do. The -c option will always return after the commands have been executed.
Broken solution: The simplest and obvious attempt around this is:
bash -c 'XXXX ; bash'
This partly works (albeit with an extra sub-shell layer). However, the problem is that while a sub-shell will inherit the exported environment variables, aliases and functions are not inherited. So this might work for some things but isn't a general solution.
Better: The way around this is to dynamically create a startup file and call bash with this new initialization file, making sure that your new init file calls your regular ~/.bashrc if necessary.
# Create a temporary file
TMPFILE=$(mktemp)
# Add stuff to the temporary file
echo "source ~/.bashrc" > $TMPFILE
echo "<other commands>" >> $TMPFILE
echo "rm -f $TMPFILE" >> $TMPFILE
# Start the new bash shell
bash --rcfile $TMPFILE
The nice thing is that the temporary init file will delete itself as soon as it is used, reducing the risk that it is not cleaned up correctly.
Note: I'm not sure if /etc/bashrc is usually called as part of a normal non-login shell. If so you might want to source /etc/bashrc as well as your ~/.bashrc.
You can pass --rcfile to Bash to cause it to read a file of your choice. This file will be read instead of your .bashrc. (If that's a problem, source ~/.bashrc from the other script.)
Edit: So a function to start a new shell with the stuff from ~/.more.sh would look something like:
more() { bash --rcfile ~/.more.sh ; }
... and in .more.sh you would have the commands you want to execute when the shell starts. (I suppose it would be elegant to avoid a separate startup file -- you cannot use standard input because then the shell will not be interactive, but you could create a startup file from a here document in a temporary location, then read it.)
bash -c '<some command> ; exec /bin/bash'
will avoid additional shell sublayer
You can get the functionality you want by sourcing the script instead of running it. eg:
$cat script
cmd1
cmd2
$ . script
$ at this point cmd1 and cmd2 have been run inside this shell
Append to ~/.bashrc a section like this:
if [ "$subshell" = 'true' ]
then
# commands to execute only on a subshell
date
fi
alias sub='subshell=true bash'
Then you can start the subshell with sub.
The accepted answer is really helpful! Just to add that process substitution (i.e., <(COMMAND)) is not supported in some shells (e.g., dash).
In my case, I was trying to create a custom action (basically a one-line shell script) in Thunar file manager to start a shell and activate the selected Python virtual environment. My first attempt was:
urxvt -e bash --rcfile <(echo ". $HOME/.bashrc; . %f/bin/activate;")
where %f is the path to the virtual environment handled by Thunar.
I got an error (by running Thunar from command line):
/bin/sh: 1: Syntax error: "(" unexpected
Then I realized that my sh (essentially dash) does not support process substitution.
My solution was to invoke bash at the top level to interpret the process substitution, at the expense of an extra level of shell:
bash -c 'urxvt -e bash --rcfile <(echo "source $HOME/.bashrc; source %f/bin/activate;")'
Alternatively, I tried to use here-document for dash but with no success. Something like:
echo -e " <<EOF\n. $HOME/.bashrc; . %f/bin/activate;\nEOF\n" | xargs -0 urxvt -e bash --rcfile
P.S.: I do not have enough reputation to post comments, moderators please feel free to move it to comments or remove it if not helpful with this question.
With accordance with the answer by daveraja, here is a bash script which will solve the purpose.
Consider a situation if you are using C-shell and you want to execute a command
without leaving the C-shell context/window as follows,
Command to be executed: Search exact word 'Testing' in current directory recursively only in *.h, *.c files
grep -nrs --color -w --include="*.{h,c}" Testing ./
Solution 1: Enter into bash from C-shell and execute the command
bash
grep -nrs --color -w --include="*.{h,c}" Testing ./
exit
Solution 2: Write the intended command into a text file and execute it using bash
echo 'grep -nrs --color -w --include="*.{h,c}" Testing ./' > tmp_file.txt
bash tmp_file.txt
Solution 3: Run command on the same line using bash
bash -c 'grep -nrs --color -w --include="*.{h,c}" Testing ./'
Solution 4: Create a sciprt (one-time) and use it for all future commands
alias ebash './execute_command_on_bash.sh'
ebash grep -nrs --color -w --include="*.{h,c}" Testing ./
The script is as follows,
#!/bin/bash
# =========================================================================
# References:
# https://stackoverflow.com/a/13343457/5409274
# https://stackoverflow.com/a/26733366/5409274
# https://stackoverflow.com/a/2853811/5409274
# https://stackoverflow.com/a/2853811/5409274
# https://www.linuxquestions.org/questions/other-%2Anix-55/how-can-i-run-a-command-on-another-shell-without-changing-the-current-shell-794580/
# https://www.tldp.org/LDP/abs/html/internalvariables.html
# https://stackoverflow.com/a/4277753/5409274
# =========================================================================
# Enable following line to see the script commands
# getting printing along with their execution. This will help for debugging.
#set -o verbose
E_BADARGS=85
if [ ! -n "$1" ]
then
echo "Usage: `basename $0` grep -nrs --color -w --include=\"*.{h,c}\" Testing ."
echo "Usage: `basename $0` find . -name \"*.txt\""
exit $E_BADARGS
fi
# Create a temporary file
TMPFILE=$(mktemp)
# Add stuff to the temporary file
#echo "echo Hello World...." >> $TMPFILE
#initialize the variable that will contain the whole argument string
argList=""
#iterate on each argument
for arg in "$#"
do
#if an argument contains a white space, enclose it in double quotes and append to the list
#otherwise simply append the argument to the list
if echo $arg | grep -q " "; then
argList="$argList \"$arg\""
else
argList="$argList $arg"
fi
done
#remove a possible trailing space at the beginning of the list
argList=$(echo $argList | sed 's/^ *//')
# Echoing the command to be executed to tmp file
echo "$argList" >> $TMPFILE
# Note: This should be your last command
# Important last command which deletes the tmp file
last_command="rm -f $TMPFILE"
echo "$last_command" >> $TMPFILE
#echo "---------------------------------------------"
#echo "TMPFILE is $TMPFILE as follows"
#cat $TMPFILE
#echo "---------------------------------------------"
check_for_last_line=$(tail -n 1 $TMPFILE | grep -o "$last_command")
#echo $check_for_last_line
#if tail -n 1 $TMPFILE | grep -o "$last_command"
if [ "$check_for_last_line" == "$last_command" ]
then
#echo "Okay..."
bash $TMPFILE
exit 0
else
echo "Something is wrong"
echo "Last command in your tmp file should be removing itself"
echo "Aborting the process"
exit 1
fi
$ bash --init-file <(echo 'some_command')
$ bash --rcfile <(echo 'some_command')
In case you can't or don't want to use process substitution:
$ cat script
some_command
$ bash --init-file script
Another way:
$ bash -c 'some_command; exec bash'
$ sh -c 'some_command; exec sh'
sh-only way (dash, busybox):
$ ENV=script sh
Here is yet another (working) variant:
This opens a new gnome terminal, then in the new terminal it runs bash. The user's rc file is read first, then a command ls -la is sent for execution to the new shell before it turns interactive.
The last echo adds an extra newline that is needed to finish execution.
gnome-terminal -- bash -c 'bash --rcfile <( cat ~/.bashrc; echo ls -la ; echo)'
I also find it useful sometimes to decorate the terminal, e.g. with colorfor better orientation.
gnome-terminal --profile green -- bash -c 'bash --rcfile <( cat ~/.bashrc; echo ls -la ; echo)'

Resources