#!/bin/sh
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$#"}
I am confusing with above 2 things.
I came to know that $0 is file name , as well we are getting the filename using argv0.
And argv are the arguments to the file , same as "$#" , But why should we use both the things in our sritps.
The confusion comes because $0 and ${1+"$#"} is not tcl syntax. It is shell syntax, and has nothing to do with tcl.
When you have a script like this, and you run it from the command line, the system thinks it is a shell script and will start to run each line as if it were a shell command. At this point, argv0 and similar variables don't exist.
Since this is actually a tcl script. the first thing we want the shell to do is to stop the shell and start tclsh. To do that, we exec tclsh. Also, we must make sure -- using shell syntax -- that the arguments are passed to tclsh properly. Hence, we must use $0 and similar constructs. Once tclsh has started and the shell is no longer executing, $0 disappears and argv0 is set.
#!/bin/sh
# the next line restarts using tclsh \
exec tclsh "$0" ${1+"$#"}
This approach has three advantages. First, the location of the tclsh binary does not have to be hard-wired into the script: it can be anywhere in your shell search path. Second, it gets around the 30-character file name limit in the previous approach. Third, this approach will work even if tclsh is itself a shell script (this is done on some systems in order to handle multiple architectures or operating systems: the tclsh script selects one of several binaries to run). The three lines cause both sh and tclsh to process the script, but the exec is only executed by sh. sh processes the script first; it treats the second line as a comment and executes the third line. The exec statement cause the shell to stop processing and instead to start up tclsh to reprocess the entire script. When tclsh starts up, it treats all three lines as comments, since the backslash at the end of the second line causes the third line to be treated as part of the comment on the second line.
where as,
argv : argument variable list
argc : argument variable list count
argv0 : first in the argument variable list
$0 for tcl script path and ${1+$#} means "all the arguments, if the first argument is set"
hence,
exec tclsh $0 ${1+$#}
means after stopping shell, execute tclsh by passing script file (via $0) and rest arguments (via ${1+$#})
When tclsh starts, tcl variables like argv, argv will come into picture.
Related
I am learning Perl and Shell scripting and of the challenges I was given is to write a shell script that takes a csv file as an argument and then have that shell script run my perl script (test.pl). I can't seem to get the proper syntax down and I have terminate every time because it hangs up my terminal.
For example shell script is test.sh
#/bin/bash
./test.pl $i`
on my terminal I write type out
test.sh testfile.csv
Ultimately I want the test file to be read by my perl script so it can run.
I think your error comes from the $i` part.
First the trailing backquote is probably a typo and should raise a syntax error. Second, the i variable is not defined, so $i resolve to an empty string. As it is not quoted, shell will omit it and call test.pl without any arguments... Thus your terminal is probably hanging because your perl script is waiting for input.
As #fra suggested, you should use $1 instead of $i, hence passing the first argument passed to your bash script, to your perl script.
Depending on your perl script (shebang, execution write) you may or may not call the perl executable manually.
Consider the following script:
#!/bin/bash
CMD="echo hello world > /tmp/hello.out"
${CMD}
The output for this is:
hello world > /tmp/hello.out
How can I modify CMD so that the output gets redirected to hello.out?
For my use case, it is not feasible to either do this:
${CMD} > /tmp/hello.out
or to add this at the top of the script:
exec > /tmp/hello.out
No, there is no way to make a redirection happen from a variable.
Why?
The first thing the shell does with a command line is:
Each line that the shell reads from the standard input or a script is called a pipeline; it contains one or more commands separated by zero or
more pipe characters (|). For each pipeline it reads, the shell breaks it up into commands, sets up the I/O for the pipeline, then does the following for each command (Figure 7-1):
From: Learning the bash Shell Unix Shell Programming . Chapter Preview / Figure . Pdf
That means that even before starting with the first word of a command line, the redirections are set up.
The "Parameter Expansion" happens quite a lot latter (in step 6 of the Figure).
There is no way to set up redirections after a variable is expanded.
Unless ...
The "command line is reprocessed" using eval.
eval "$CMD"
But this comes with a lot of danger.
The command line is changed by the first processing in the 12 steps detailed in the book (quotes are removed, variables expanded, words split, etc.).
It is usually quite difficult to estimate all the changes and consequences before the line is actually processed.
And then, it is processed again.
You can use eval to instruct the shell to reinterpret the variable content as a shell command:
eval $CMD
I just learned that the bash command opens up a new Bash shell inside of whatever shell you're using, and uses the profile of .bashrc for its commands.
When I was installing Laravel earlier this week, I used a bash init.sh command. Now I'm wondering, what exactly did that bash init.sh command do? Why did I need to open a new shell to... execute or open whatever was in init.sh?
Quoting man bash*:
ARGUMENTS
If arguments remain after option processing, and neither the -c nor the -s option has been supplied, the first argument is assumed to be the name of a file containing shell commands. If bash is invoked in this fashion, $0 is set to the name of the file, and the positional parameters are set to the remaining arguments. Bash reads and executes commands from this file, then exits. Bash's exit status is the exit status of the last command executed in the script. If no commands are executed, the exit status is 0. An attempt is first made to open the file in the current directory, and, if no file is found, then the shell searches the directories in PATH for the script.
In other words: bash <file> executes <file> as a Bash script. You will not get a new interactive shell.
Note that this is not the only way to specify which shell (or indeed any command) should be used to execute a script: It's common to use a shebang for that, so that the script can be ‘executed’ by itself.
* The one I had at hand, of Bash v4.3.42.
Example one
#!/bin/sh
# purpose: print out current directory name and contents
pwd
ls
Example two
# purpose: print out current directory name and contents
#!/bin/sh
pwd
ls
What is the difference – if I make the first line a comment(#), with #!/bin/sh as the second line, what will happen?
What is meaning of #!/bin/sh ?
Normally a shell script is run by your default shell defined in the /etc/passwd file. But you can define explicitly a program which can run your script.
Unices uses a common method to determine what program needed to run a specific script (man execve(2)). If the script has the proper execute rights set and in a script the first line starts with a #! characters, it will run by the program defined afterwards.
For example if the first line is #!/usr/bin/awk -f then the rest of the file will be passed to the awk program (so it has to use awk syntax). Or if a Makefile starts with #!/usr/bin/make -f then the rest of the file will be passed to make. You can start the script as a normal program and the script can be written in awk or make (or whatever defined) syntax.
If execve does not find #! as the first two character of the file, it will consider as a normal script file and it will run as it is.
So using #! You can determine the script language and You do not need to know what shell is used by the other user using your script. In any other line #! will be interpretered your default shell, which is usually just a comment line.
what is difference between 1st & 2nd shell scripts..?
No difference in output. But the time to execute both will be little different as the interpreter reads line one by one.
if i give comment(#) in 1st line after #!/bin/sh in 2nd line so what will happen ?
Any line started with (#) except the shebang(#!) is treated as a comment in shell script.
what is meaning of #!/bin/sh ?
Its the path(here - /bin/sh) to the interpreter used after the shebang (#!) . Shell will try to use the interpreter language mentioned after the shebang to execute the script.
I have a script "task.sh" with the following content:
#!/bin/bash
CUR_DIR=`pwd`
SCRIPTPATH="${CUR_DIR}/`dirname $0`"
when I call it with "bash task.sh" it works as expected but when it is called with ". task.sh"
$ . log/task.sh
dirname: invalid option -- b
Try `dirname --help' for more information.
When the script is being scheduled in crontab it is not working as well.
Can someone tell me what am I doing wrong or a different way in order to get the directory of a script that is not the current directory
?
When you invoke it as bash task.sh, bash assigns "task.sh" to $0 (from the bash manual: "If Bash is invoked with a file of commands [...] $0 is set to the name of that file.").
When you source the file, bash does not alter $0, it just executes the script in the current environment. What's in $0 in your current enviroment?
$ echo "$0"
-bash
The leading dash will be interpreted by dirname as an option.
If it's in a cron job, why are you sourcing it?
If you need to source your script, this will work if your shell is bash:
SCRIPTPATH="${CUR_DIR}/${BASH_ARGV[0]}"
However, cron's shell is, I believe, /bin/sh. Even if /bin/sh is a symlink to bash, when bash is invoked as sh it will try to behave POSIXly: the BASH_ARGV array probably won't be available to you.
There is no reason to call external binaries such as pwd and dirname when using bash. The functionality of these two binaries can be replicated with pure shell syntax.
Try the following:
#!/bin/bash
CUR_DIR="$PWD"
SCRIPTPATH="${CUR_DIR}/${0#*/}"
When you type,
bash foo.sh
you are executing script foo.sh, and bash sets the input argument $0 to the name of the script which is being run.
When you type,
. foo.sh
you are sourcing the script and the input argument $0 is not set.
In this situation you can use the automatic variable $_ which contains the argument of the last executed command.
In your script you could type,
SCRIPTPATH=$(dirname "$_")
to get the path of foo.sh.
Notice that, for this to work, this has to be the first command executed in the file.
Otherwise $_ will not contain the path of the sourced script.
Kudos to Dennis Williamson for providing this answer to a similar question.
I have used this for a long time without issues.
SCRIPTPATH=$(cd `dirname -- $0` && pwd)
The -- disable further processing of parameters.