Call multiple python scripts sequentially from a bash script using arguments - python

I would like to make a bash script that runs some number of python scripts in a row based on arguments given in the terminal (the order in which they run doesn't matter).
Something like:
kwargs = kwarg1 kwarg2 kwarg3
if kwarg1: python script1.py
if kwarg2: python name2000.py
if kwarg3: python program30.py
But with the option to take in any number of keywords as long as they matched up to a script.
How would this be done?

There's a lot you can do to add to this, but here's the simplest of what you're asking for:
#!/bin/bash
for script in $*; do
python $script.py
done
Put that in a new file, e.g. "run_python.bash", and make it executable (chmod a+w run_python.bash).
Then you run it by: run_python.bash one two three, and it will execute one.py, then two.py, then three.py .
If you want to use the if structure in the for loop (i.e. if your keywords don't match the script name), the simple version starts like this:
#!/bin/bash
for script in $*; do
if [ $script == 'kwarg1' ]; then
python script1.py
fi
if [ $script == 'kwarg2' ]; then
python name2000.py
fi
if [ $script == 'kwarg3' ]; then
python program30.py
fi
done
This will execute whatever you tell it to, based on the order of the keywords you put on the command line.
p.s. This has nothing to do with python. The script works the same, whether you're invoking python, or ls, or anything else that you can run in bash.

you need shell script.
script.sh:
#!/bin/bash
if [ "$1" == "kwarg1" ]
then
python script1.py
elif [ "$1" == "kwarg2" ]
then
python name2000.py
fi
chmod +x script.sh
./script.sh kwarg1

Related

Create an automatic routine to run several python script, using a shell script

I am trying to create a shell script that executes some python scripts in order.
I have never done something like this and i don´t know if what i am doing is correct.
Before executing the last file i need to activate an enviroment so it has the necesary imports to so it functions correctly.
This is the script i have created (execute.sh):
#!/bin/sh
python path/file_1.py
python path/file_2.py
python path/file_3.py
# activate a virtual enviroment
source /path/env/bin/activate
python path/file_4.py
After this what sould I do?
Just write this line in the terminal?
./execute.sh
That should work. Usually I always do this when running a bash script with Python scripts inside.
#!/bin/bash
. /home/user/.bashrc # Load basic env as we're coming from cron
# Cd into our working directory in case we're not into it already
cd "$(dirname "$0")";
echo "----------------"
echo "Starting processing `date`"
echo "----------------"
conda activate models # Activate my conda environment
# Execute Python script
python script.py
if [[ $? = 0 ]]; then
echo "Succesfull execution!"
else
echo "Something wrong"
exit 1
fi
This is in execute.run, which you then make executable chmod +x executable.run, and run. This will work wherever you are because it will always try to cd into the directory where the script resides, so you can also call it from crontab.

python, read the filename from directory , use the string in bash script

I am writing a bash script (e.g. program.sh) where I am calling a python code in which a list of files are read from a directory.
the python script (read_files.py) is as following:
import os
def files(path):
for filename in os.listdir('/home/testfiles'):
if os.path.isfile(os.path.join('/home/testfiles', filename)):
yield filename
for filename in files("."):
print (filename)
Now I want to keep the string filename and use it in the bash script.
e.g.
program.sh:
#!/bin/bash
python read_files.py
$Database_maindir/filename
.
.
.
How could I keep the string filename (the names of files in the directory) and write a loop in order to execute commands in bash script for each filename?
The Python script in the question doesn't do anything that Bash cannot already do all by itself, and simpler and easier. Use simple native Bash instead:
shopt -s nullglob
for path in /home/testfiles/*; do
if [[ -f "$path" ]]; then
filename=$(basename "$path")
echo "do something with $filename"
fi
done
If the Python script does something more than what you wrote in the question,
for example it does some complex computation and spits out filenames,
which would be complicated to do in Bash,
then you do have a legitimate use case to keep it.
In that case, you can iterate over the lines in the output like this:
python read_files.py | while read -r filename; do
echo "do something with $filename"
done
Are you looking for something like this? =
for filename in $(python read_files.py); do
someCommand $filename
done

Execute a custom bash function within python

This question is a simpler version of this question.
In simple terms, I have a custom bash function my_func defined on ~/.bash_profile and uses two more bash functions defined in the same environment.
Also, my_func accepts two arguments, let's say a and b. What my_func does is that it gets connected to a remote server and sends some files (these are determined by a and b).
If I type on the bash shell:
. my_func a b everything works fine and I get some print statements on the screen.
However, if I include:
subprocess.call(['#!/bin/bash . my_func a b'], shell=True) nothing seems to happen.
I tried to export all the bash functions that are used by my_func by including:
subprocess.call(['#!/bin/bash export -f my_func'], shell=True) and I did the same for the rest of the functions that are used by my_func.
EDIT:
If I use subprocess.call(['bash', '-c' ,'my_func a b], shell=True) the bash shell will change into bash-3.2$
You need to export the function before you start the python program:
export -f my_func
python foo.py
Well, the above example might not work if the system's default shell (/bin/sh) is not bash. To circumvent this, you may use subprocess call like this:
$ function foo() { echo "bar" ; }
$ export -f foo
$ cat foo.py
import subprocess
subprocess.call(['bash', '-c', 'foo'])
$ python foo.py
bar
Alternative:
I would put the function into a lib file, let's say:
# /usr/share/my_lib/my_lib.sh
function my_func() {
# do something
}
Then I would expose the function via a script in PATH:
#!/bin/bash
# /usr/local/bin/my_prog
source /usr/lib/my_lib/my_lib.sh
my_func "$1" "$2"
In Python you would just:
subprocess.call(['/usr/local/bin/my_prog', 'a', 'b'])
Btw: If you don't need that function somewhere else, you can just put it directly into /usr/local/bin/my_prog.

Relative shebang: How to write an executable script running portable interpreter which comes with it

Let's say we have a program/package which comes along with its own interpreter and a set of scripts which should invoke it on their execution (using shebang).
And let's say we want to keep it portable, so it remains functioning even if simply copied to a different location (different machines) without invoking setup/install or modifying environment (PATH). A system interpreter should not be mixed in for these scripts.
The given constraints exclude both known approaches like shebang with absolute path:
#!/usr/bin/python
and search in the environment
#!/usr/bin/env python
Separate launchers look ugly and are not acceptable.
I found good summary of the shebang limitations which describe why relative path in the shebang are useless and there cannot be more than one argument to the interpreter: http://www.in-ulm.de/~mascheck/various/shebang/
And I also found practical solutions for most of the languages with 'multi-line shebang' tricks. It allows to write scripts like this:
#!/bin/sh
"exec" "`dirname $0`/python2.7" "$0" "$#"
print copyright
But sometimes, we don't want to extend/patch existing scripts which rely on shebang with an absolute path to interpreter using this approach. E.g. Python's setup.py supports --executable option which basically allows to specify the shebang content for the scripts it produces:
python setup.py build --executable=/opt/local/bin/python
So, in particular, what can be specified for --executable= in order to enable the desired kind of portability? Or in other words, since I'd like to keep the question not too specific to Python...
The question
How to write a shebang which specifies an interpreter with a path which is relative to the location of the script being executed?
The relative path written directly in a shebang is treated relative to the current working directory, so something like #!../bin/python2.7 will not work for any other working directory except few.
Since OS does not support it, why not to use external program like using env for PATH lookup. But I know no specialized program which computes the relative paths from arguments and executes the resulting command.. except the shell itself and other scripting engines.
But trying to compute the path in a shell script like
#!/bin/sh -c '`dirname $0`/python2.7 $0'
does not work because on Linux shebang is limited by one argument only. And that suggested me to look for scripting engines which accept a script as the first argument on the command line and are able to execute new process:
Using AWK
#!/usr/bin/awk BEGIN{a=ARGV[1];sub(/[a-z_.]+$/,"python2.7",a);system(a"\t"ARGV[1])}
Using Perl
#!/usr/bin/perl -e$_=$ARGV[0];exec(s/\w+$/python2.7/r,$_)
update from 11Jan21:
Using updated env utility:
$ env --version | grep env
env (GNU coreutils) 8.30
$ env --help
Usage: env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]
Set each NAME to VALUE in the environment and run COMMAND.
Mandatory arguments to long options are mandatory for short options too.
-i, --ignore-environment start with an empty environment
-0, --null end each output line with NUL, not newline
-u, --unset=NAME remove variable from the environment
-C, --chdir=DIR change working directory to DIR
-S, --split-string=S process and split S into separate arguments;
used to pass multiple arguments on shebang lines
So, passing -S to env will do the job
The missing "punchline" from Anton's answer:
With an updated version of env, we can now realize the initial idea:
#!/usr/bin/env -S /bin/sh -c '"$(dirname "$0")/python3" "$0" "$#"'
Note that I switched to python3, but this question is really about shebang - not python - so you can use this solution with whatever script environment you want. You can also replace /bin/sh with just sh if you prefer.
There is a lot going on here, including some quoting hell, and at first glance it's not clear what's happening. I think there's little worth to just saying "this is how to do it" without explanation, so let's unpack it.
It breaks down like this:
The shebang is interpreted to run /usr/bin/env with the following arguments:
-S /bin/sh -c '"$(dirname "$0")/python3" "$0" "$#"'
full path (either local or absolute) to the script file
onwards, any extra commandline arguments
env finds the -S at the start of the first argument, and splits it according to (simplified) shell rules. In this case, only the single-quotes are relevant - all the other fancy syntax is within single-quotes so it gets ignored. The new arguments to env become:
/bin/sh
-c
"$(dirname "$0")/python3" "$0" "$#"
full path to script file (either local or absolute)
onwards, (possibly) extra arguments
It runs /bin/sh - the default shell - with the arguments:
-c
"$(dirname "$0")/python3" "$0" "$#"
full path to script file
onwards, (possibly) extra arguments
As the shell was run with -c, it runs in the second operating mode defined here (and also re-described many times by different man pages of all shells, e.g. dash, which is much more approachable). In our case we can ignore all the extra options, the syntax is:
sh -c command_string command_name [argument ...]
In our case:
command_string is "$(dirname "$0")/python3" "$0" "$#"
command_name is the script path, e.g. ./path to/script dir/script file.py
argument(s) are any extra arguments (it's possible to have zero arguments)
As described, the shell wants to run command_string ("$(dirname "$0")/python3" "$0" "$#") as a command, so now we turn to the Shell Command Language:
Parameter Expansion is performed on "$0" and "$#", which are both Special Parameters:
"$#" expands to the argument(s). If there were no arguments, it will "expand" into nothing. Because of this special behaviour, it's explained horribly in the spec I linked, but the man page for dash explains it much better.
$0 expands to command_name - our script file. Every occurrence of $0 is within double-quotes so it doesn't get split, i.e. spaces in the path won't break it up into multiple arguments.
Command Substitution is applied, substituting $(dirname "$0") with the standard output of running the command dirname "./path to/script dir/script file.py", i.e. the folder that our script file resides in: ./path to/script dir.
After all of the substitutions and expansions, the command becomes, for example:
"./path to/script dir/python3" "./path to/script dir/script file.py" "first argument" "second argument" ...
Finally, the shell runs the expanded command, and executes our local python3 with our script file as an argument followed by any other arguments we passed to it.
Phew!
What follows is basically my attempts to demonstrate that those steps are occuring. It's probably not worth your time, but I already wrote it and I don't think it's so bad that it should be removed. If nothing else, it might be useful to someone if they want to see an example of how to reverse-engineer things like this. It doesn't include extra arguments, those were added after Emanuel's comment.
It also has a lousy joke at the end..
First let's start simpler. Take a look at the following "script", replacing env with echo:
$ cat "/home/neatnit/Projects/SO question 33225082/my script.py"
#!/usr/bin/echo -S /bin/sh -c '"$( dirname "$0" )/python2.7" "$0"'
print("This is python")
It's hardly a script - the shebang calls echo which will just print whichever arguments it's given. I've deliberately put two spaces between the words, this way we can see how they get preserved. As an aside, I've deliberately put the script in a path that contains spaces, to show that they are handled correctly.
Let's run it:
$ "/home/neatnit/Projects/SO question 33225082/my script.py"
-S /bin/sh -c '"$( dirname "$0" )/python2.7" "$0"' /home/neatnit/Projects/SO question 33225082/my script.py
We see that with that shebang, echo is run with two arguments:
-S /bin/sh -c '"$( dirname "$0" )/python2.7" "$0"'
/home/neatnit/Projects/SO question 33225082/my script.py
These are the literal arguments echo sees - no quoting or escaping.
Now, let's get env back but use printf [1] ahead of sh to explore how env processes these arguments:
$ cat "/home/neatnit/Projects/SO question 33225082/my script.py"
#!/usr/bin/env -S printf %s\n /bin/sh -c '"$( dirname "$0" )/python2.7" "$0"'
print("This is python")
And run it:
$ "/home/neatnit/Projects/SO question 33225082/my script.py"
/bin/sh
-c
"$( dirname "$0" )/python2.7" "$0"
/home/neatnit/Projects/SO question 33225082/my script.py
env splits the string after -S [2] according to ordinary (but simplified) shell rules. In this case, all $ symbols were within single-quotes, so env did not expand them. It then appended the additional argument - the script file - to the end.
When sh gets these arguments, the first argument after -c (in this case: "$( dirname "$0" )/python2.7" "$0") gets interpreted as a shell command, and the next argument acts as the first parameter in that command ($0).
Pushing the printf one level deeper:
$ cat "/home/neatnit/Projects/SO question 33225082/my script.py"
#!/usr/bin/env -S /bin/sh -c 'printf %s\\\n "$( dirname "$0" )/python2.7" "$0"'
print("This is python")
And running it:
$ "/home/neatnit/Projects/SO question 33225082/my script.py"
/home/neatnit/Projects/SO question 33225082/python2.7
/home/neatnit/Projects/SO question 33225082/my script.py
At last - it's starting to look like the command we were looking for! The local python2.7 and our script as an argument!
sh expanded $0 into /home/[ ... ]/my script.py, giving this command:
"$( dirname "/home/[ ... ]/my script.py" )/python2.7" "/home/[ ... ]/my script.py"
dirname snips off the last part of the path to get the containing folder, giving this command:
"/home/[ ... ]/SO question 33225082/python2.7" "/home/[ ... ]/my script.py"
To highlight a common pitfall, this is what happens if we don't use double-quotes and our path contains spaces:
$ cat "/home/neatnit/Projects/SO question 33225082/my script.py"
#!/usr/bin/env -S /bin/sh -c 'printf %s\\\n $( dirname $0 )/python2.7 $0'
print("This is python")
$ "/home/neatnit/Projects/SO question 33225082/my script.py"
/home/neatnit/Projects
.
33225082
./python2.7
/home/neatnit/Projects/SO
question
33225082/my
script.py
Needless to say, running this as a command would not give the desired result. Figuring out exactly what happened here is left as an exercise to the reader :)
At last, we put the quote marks back where they belong and get rid of the printf, and we finally get to run our script:
$ "/home/neatnit/Projects/SO question 33225082/my script.py"
/home/neatnit/Projects/SO question 33225082/my script.py: 1: /home/neatnit/Projects/SO question 33225082/python2.7: not found
Wait, uh, let me fix that
$ ln --symbolic $(which python3) "/home/neatnit/Projects/SO question 33225082/python2.7"
$ "/home/neatnit/Projects/SO question 33225082/my script.py"
This is python
Rejoice!
[1] This way we can see each argument in a separate line, and we don't have to get confused by space-delimited arguments.
[2] There doesn't need to be a space after -S, I just prefer the way it looks. -Sprintf sounds really exhausting.

Relative Path issue in shell script [duplicate]

This question already has answers here:
How do I get the directory where a Bash script is located from within the script itself?
(74 answers)
Closed 6 years ago.
I have a Bash script that needs to know its full path. I'm trying to find a broadly-compatible way of doing that without ending up with relative or funky-looking paths. I only need to support Bash, not sh, csh, etc.
What I've found so far:
The accepted answer to Getting the source directory of a Bash script from within addresses getting the path of the script via dirname $0, which is fine, but that may return a relative path (like .), which is a problem if you want to change directories in the script and have the path still point to the script's directory. Still, dirname will be part of the puzzle.
The accepted answer to Bash script absolute path with OS X (OS X specific, but the answer works regardless) gives a function that will test to see if $0 looks relative and if so will pre-pend $PWD to it. But the result can still have relative bits in it (although overall it's absolute) — for instance, if the script is t in the directory /usr/bin and you're in /usr and you type bin/../bin/t to run it (yes, that's convoluted), you end up with /usr/bin/../bin as the script's directory path. Which works, but...
The readlink solution on this page, which looks like this:
# Absolute path to this script. /home/user/bin/foo.sh
SCRIPT=$(readlink -f $0)
# Absolute path this script is in. /home/user/bin
SCRIPTPATH=`dirname $SCRIPT`
But readlink isn't POSIX and apparently the solution relies on GNU's readlink where BSD's won't work for some reason (I don't have access to a BSD-like system to check).
So, various ways of doing it, but they all have their caveats.
What would be a better way? Where "better" means:
Gives me the absolute path.
Takes out funky bits even when invoked in a convoluted way (see comment on #2 above). (E.g., at least moderately canonicalizes the path.)
Relies only on Bash-isms or things that are almost certain to be on most popular flavors of *nix systems (GNU/Linux, BSD and BSD-like systems like OS X, etc.).
Avoids calling external programs if possible (e.g., prefers Bash built-ins).
(Updated, thanks for the heads up, wich) It doesn't have to resolve symlinks (in fact, I'd kind of prefer it left them alone, but that's not a requirement).
Here's what I've come up with (edit: plus some tweaks provided by sfstewman, levigroker, Kyle Strand, and Rob Kennedy), that seems to mostly fit my "better" criteria:
SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
That SCRIPTPATH line seems particularly roundabout, but we need it rather than SCRIPTPATH=`pwd` in order to properly handle spaces and symlinks.
The inclusion of output redirection (>/dev/null 2>&1) handles the rare(?) case where cd might produce output that would interfere with the surrounding $( ... ) capture. (Such as cd being overridden to also ls a directory after switching to it.)
Note also that esoteric situations, such as executing a script that isn't coming from a file in an accessible file system at all (which is perfectly possible), is not catered to there (or in any of the other answers I've seen).
The -- after cd and before "$0" are in case the directory starts with a -.
I'm surprised that the realpath command hasn't been mentioned here. My understanding is that it is widely portable / ported.
Your initial solution becomes:
SCRIPT=$(realpath "$0")
SCRIPTPATH=$(dirname "$SCRIPT")
And to leave symbolic links unresolved per your preference:
SCRIPT=$(realpath -s "$0")
SCRIPTPATH=$(dirname "$SCRIPT")
The simplest way that I have found to get a full canonical path in Bash is to use cd and pwd:
ABSOLUTE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
Using ${BASH_SOURCE[0]} instead of $0 produces the same behavior regardless of whether the script is invoked as <name> or source <name>.
I just had to revisit this issue today and found Get the source directory of a Bash script from within the script itself:
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
There's more variants at the linked answer, e.g. for the case where the script itself is a symlink.
Get the absolute path of a shell script
It does not use the -f option in readlink, and it should therefore work on BSD/Mac OS X.
Supports
source ./script (When called by the . dot operator)
Absolute path /path/to/script
Relative path like ./script
/path/dir1/../dir2/dir3/../script
When called from symlink
When symlink is nested eg) foo->dir1/dir2/bar bar->./../doe doe->script
When caller changes the scripts name
I am looking for corner cases where this code does not work. Please let me know.
Code
pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}";
while([ -h "${SCRIPT_PATH}" ]); do
cd "`dirname "${SCRIPT_PATH}"`"
SCRIPT_PATH="$(readlink "`basename "${SCRIPT_PATH}"`")";
done
cd "`dirname "${SCRIPT_PATH}"`" > /dev/null
SCRIPT_PATH="`pwd`";
popd > /dev/null
echo "srcipt=[${SCRIPT_PATH}]"
echo "pwd =[`pwd`]"
Known issus
The script must be on disk somewhere. Let it be over a network. If you try to run this script from a PIPE it will not work
wget -o /dev/null -O - http://host.domain/dir/script.sh |bash
Technically speaking, it is undefined. Practically speaking, there is no sane way to detect this. (A co-process can not access the environment of the parent.)
Use:
SCRIPT_PATH=$(dirname `which $0`)
which prints to standard output the full path of the executable that would have been executed when the passed argument had been entered at the shell prompt (which is what $0 contains)
dirname strips the non-directory suffix from a file name.
Hence you end up with the full path of the script, no matter if the path was specified or not.
As realpath is not installed per default on my Linux system, the following works for me:
SCRIPT="$(readlink --canonicalize-existing "$0")"
SCRIPTPATH="$(dirname "$SCRIPT")"
$SCRIPT will contain the real file path to the script and $SCRIPTPATH the real path of the directory containing the script.
Before using this read the comments of this answer.
Easy to read? Below is an alternative. It ignores symlinks
#!/bin/bash
currentDir=$(
cd $(dirname "$0")
pwd
)
echo -n "current "
pwd
echo script $currentDir
Since I posted the above answer a couple years ago, I've evolved my practice to using this linux specific paradigm, which properly handles symlinks:
ORIGIN=$(dirname $(readlink -f $0))
Simply:
BASEDIR=$(readlink -f $0 | xargs dirname)
Fancy operators are not needed.
You may try to define the following variable:
CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
Or you can try the following function in Bash:
realpath () {
[[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}
This function takes one argument. If the argument already has an absolute path, print it as it is, otherwise print $PWD variable + filename argument (without ./ prefix).
Related:
Bash script absolute path with OS X
Get the source directory of a Bash script from within the script itself
Answering this question very late, but I use:
SCRIPT=$( readlink -m $( type -p ${0} )) # Full path to script handling Symlinks
BASE_DIR=`dirname "${SCRIPT}"` # Directory script is run in
NAME=`basename "${SCRIPT}"` # Actual name of script even if linked
We have placed our own product realpath-lib on GitHub for free and unencumbered community use.
Shameless plug but with this Bash library you can:
get_realpath <absolute|relative|symlink|local file>
This function is the core of the library:
function get_realpath() {
if [[ -f "$1" ]]
then
# file *must* exist
if cd "$(echo "${1%/*}")" &>/dev/null
then
# file *may* not be local
# exception is ./file.ext
# try 'cd .; cd -;' *works!*
local tmppwd="$PWD"
cd - &>/dev/null
else
# file *must* be local
local tmppwd="$PWD"
fi
else
# file *cannot* exist
return 1 # failure
fi
# reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # success
}
It doesn't require any external dependencies, just Bash 4+. Also contains functions to get_dirname, get_filename, get_stemname and validate_path validate_realpath. It's free, clean, simple and well documented, so it can be used for learning purposes too, and no doubt can be improved. Try it across platforms.
Update: After some review and testing we have replaced the above function with something that achieves the same result (without using dirname, only pure Bash) but with better efficiency:
function get_realpath() {
[[ ! -f "$1" ]] && return 1 # failure : file does not exist.
[[ -n "$no_symlinks" ]] && local pwdp='pwd -P' || local pwdp='pwd' # do symlinks.
echo "$( cd "$( echo "${1%/*}" )" 2>/dev/null; $pwdp )"/"${1##*/}" # echo result.
return 0 # success
}
This also includes an environment setting no_symlinks that provides the ability to resolve symlinks to the physical system. By default it keeps symlinks intact.
Considering this issue again: there is a very popular solution that is referenced within this thread that has its origin here:
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
I have stayed away from this solution because of the use of dirname - it can present cross-platform difficulties, particularly if a script needs to be locked down for security reasons. But as a pure Bash alternative, how about using:
DIR="$( cd "$( echo "${BASH_SOURCE[0]%/*}" )" && pwd )"
Would this be an option?
If we use Bash I believe this is the most convenient way as it doesn't require calls to any external commands:
THIS_PATH="${BASH_SOURCE[0]}";
THIS_DIR=$(dirname $THIS_PATH)
The accepted solution has the inconvenient (for me) to not be "source-able":
if you call it from a "source ../../yourScript", $0 would be "bash"!
The following function (for bash >= 3.0) gives me the right path, however the script might be called (directly or through source, with an absolute or a relative path):
(by "right path", I mean the full absolute path of the script being called, even when called from another path, directly or with "source")
#!/bin/bash
echo $0 executed
function bashscriptpath() {
local _sp=$1
local ascript="$0"
local asp="$(dirname $0)"
#echo "b1 asp '$asp', b1 ascript '$ascript'"
if [[ "$asp" == "." && "$ascript" != "bash" && "$ascript" != "./.bashrc" ]] ; then asp="${BASH_SOURCE[0]%/*}"
elif [[ "$asp" == "." && "$ascript" == "./.bashrc" ]] ; then asp=$(pwd)
else
if [[ "$ascript" == "bash" ]] ; then
ascript=${BASH_SOURCE[0]}
asp="$(dirname $ascript)"
fi
#echo "b2 asp '$asp', b2 ascript '$ascript'"
if [[ "${ascript#/}" != "$ascript" ]]; then asp=$asp ;
elif [[ "${ascript#../}" != "$ascript" ]]; then
asp=$(pwd)
while [[ "${ascript#../}" != "$ascript" ]]; do
asp=${asp%/*}
ascript=${ascript#../}
done
elif [[ "${ascript#*/}" != "$ascript" ]]; then
if [[ "$asp" == "." ]] ; then asp=$(pwd) ; else asp="$(pwd)/${asp}"; fi
fi
fi
eval $_sp="'$asp'"
}
bashscriptpath H
export H=${H}
The key is to detect the "source" case and to use ${BASH_SOURCE[0]} to get back the actual script.
One liner
`dirname $(realpath $0)`
Bourne shell (sh) compliant way:
SCRIPT_HOME=`dirname $0 | while read a; do cd $a && pwd && break; done`
Perhaps the accepted answer to the following question may be of help.
How can I get the behavior of GNU's readlink -f on a Mac?
Given that you just want to canonicalize the name you get from concatenating $PWD and $0 (assuming that $0 is not absolute to begin with), just use a series of regex replacements along the line of abs_dir=${abs_dir//\/.\//\/} and such.
Yes, I know it looks horrible, but it'll work and is pure Bash.
Try this:
cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))
I have used the following approach successfully for a while (not on OS X though), and it only uses a shell built-in and handles the 'source foobar.sh' case as far as I have seen.
One issue with the (hastily put together) example code below is that the function uses $PWD which may or may not be correct at the time of the function call. So that needs to be handled.
#!/bin/bash
function canonical_path() {
# Handle relative vs absolute path
[ ${1:0:1} == '/' ] && x=$1 || x=$PWD/$1
# Change to dirname of x
cd ${x%/*}
# Combine new pwd with basename of x
echo $(pwd -P)/${x##*/}
cd $OLDPWD
}
echo $(canonical_path "${BASH_SOURCE[0]}")
type [
type cd
type echo
type pwd
Just for the hell of it I've done a bit of hacking on a script that does things purely textually, purely in Bash. I hope I caught all the edge cases.
Note that the ${var//pat/repl} that I mentioned in the other answer doesn't work since you can't make it replace only the shortest possible match, which is a problem for replacing /foo/../ as e.g. /*/../ will take everything before it, not just a single entry. And since these patterns aren't really regexes I don't see how that can be made to work. So here's the nicely convoluted solution I came up with, enjoy. ;)
By the way, let me know if you find any unhandled edge cases.
#!/bin/bash
canonicalize_path() {
local path="$1"
OIFS="$IFS"
IFS=$'/'
read -a parts < <(echo "$path")
IFS="$OIFS"
local i=${#parts[#]}
local j=0
local back=0
local -a rev_canon
while (($i > 0)); do
((i--))
case "${parts[$i]}" in
""|.) ;;
..) ((back++));;
*) if (($back > 0)); then
((back--))
else
rev_canon[j]="${parts[$i]}"
((j++))
fi;;
esac
done
while (($j > 0)); do
((j--))
echo -n "/${rev_canon[$j]}"
done
echo
}
canonicalize_path "/.././..////../foo/./bar//foo/bar/.././bar/../foo/bar/./../..//../foo///bar/"
Yet another way to do this:
shopt -s extglob
selfpath=$0
selfdir=${selfpath%%+([!/])}
while [[ -L "$selfpath" ]];do
selfpath=$(readlink "$selfpath")
if [[ ! "$selfpath" =~ ^/ ]];then
selfpath=${selfdir}${selfpath}
fi
selfdir=${selfpath%%+([!/])}
done
echo $selfpath $selfdir
More simply, this is what works for me:
MY_DIR=`dirname $0`
source $MY_DIR/_inc_db.sh

Categories

Resources