How can I run a function from a script in command line? - linux

I have a script that has some functions.
Can I run one of the function directly from command line?
Something like this?
myScript.sh func()

Well, while the other answers are right - you can certainly do something else: if you have access to the bash script, you can modify it, and simply place at the end the special parameter "$#" - which will expand to the arguments of the command line you specify, and since it's "alone" the shell will try to call them verbatim; and here you could specify the function name as the first argument. Example:
$ cat test.sh
testA() {
echo "TEST A $1";
}
testB() {
echo "TEST B $2";
}
"$#"
$ bash test.sh
$ bash test.sh testA
TEST A
$ bash test.sh testA arg1 arg2
TEST A arg1
$ bash test.sh testB arg1 arg2
TEST B arg2
For polish, you can first verify that the command exists and is a function:
# Check if the function exists (bash specific)
if declare -f "$1" > /dev/null
then
# call arguments verbatim
"$#"
else
# Show a helpful error
echo "'$1' is not a known function name" >&2
exit 1
fi

If the script only defines the functions and does nothing else, you can first execute the script within the context of the current shell using the source or . command and then simply call the function. See help source for more information.

The following command first registers the function in the context, then calls it:
. ./myScript.sh && function_name

Briefly, no.
You can import all of the functions in the script into your environment with source (help source for details), which will then allow you to call them. This also has the effect of executing the script, so take care.
There is no way to call a function from a shell script as if it were a shared library.

Using case
#!/bin/bash
fun1 () {
echo "run function1"
[[ "$#" ]] && echo "options: $#"
}
fun2 () {
echo "run function2"
[[ "$#" ]] && echo "options: $#"
}
case $1 in
fun1) "$#"; exit;;
fun2) "$#"; exit;;
esac
fun1
fun2
This script will run functions fun1 and fun2 but if you start it with option
fun1 or fun2 it'll only run given function with args(if provided) and exit.
Usage
$ ./test
run function1
run function2
$ ./test fun2 a b c
run function2
options: a b c

I have a situation where I need a function from bash script which must not be executed before (e.g. by source) and the problem with #$ is that myScript.sh is then run twice, it seems... So I've come up with the idea to get the function out with sed:
sed -n "/^func ()/,/^}/p" myScript.sh
And to execute it at the time I need it, I put it in a file and use source:
sed -n "/^func ()/,/^}/p" myScript.sh > func.sh; source func.sh; rm func.sh

Edit: WARNING - seems this doesn't work in all cases, but works well on many public scripts.
If you have a bash script called "control" and inside it you have a function called "build":
function build() {
...
}
Then you can call it like this (from the directory where it is):
./control build
If it's inside another folder, that would make it:
another_folder/control build
If your file is called "control.sh", that would accordingly make the function callable like this:
./control.sh build

Solved post but I'd like to mention my preferred solution. Namely, define a generic one-liner script eval_func.sh:
#!/bin/bash
source $1 && shift && "#a"
Then call any function within any script via:
./eval_func.sh <any script> <any function> <any args>...
An issue I ran into with the accepted solution is that when sourcing my function-containing script within another script, the arguments of the latter would be evaluated by the former, causing an error.

The other answers here are nice, and much appreciated, but often I don't want to source the script in the session (which reads and executes the file in your current shell) or modify it directly.
I find it more convenient to write a one or two line 'bootstrap' file and run that. Makes testing the main script easier, doesn't have side effects on your shell session, and as a bonus you can load things that simulate other environments for testing. Example...
# breakfast.sh
make_donuts() {
echo 'donuts!'
}
make_bagels() {
echo 'bagels!'
}
# bootstrap.sh
source 'breakfast.sh'
make_donuts
Now just run ./bootstrap.sh.Same idea works with your python, ruby, or whatever scripts.
Why useful? Let's say you complicated your life for some reason, and your script may find itself in different environments with different states present. For example, either your terminal session, or a cloud provider's cool new thing. You also want to test cloud things in terminal, using simple methods. No worries, your bootstrap can load elementary state for you.
# breakfast.sh
# Now it has to do slightly different things
# depending on where the script lives!
make_donuts() {
if [[ $AWS_ENV_VAR ]]
then
echo '/donuts'
elif [[ $AZURE_ENV_VAR ]]
then
echo '\donuts'
else
echo '/keto_diet'
fi
}
If you let your bootstrap thing take an argument, you can load different state for your function to chew, still with one line in the shell session:
# bootstrap.sh
source 'breakfast.sh'
case $1 in
AWS)
AWS_ENV_VAR="arn::mumbo:jumbo:12345"
;;
AZURE)
AZURE_ENV_VAR="cloud::woo:_impress"
;;
esac
make_donuts # You could use $2 here to name the function you wanna, but careful if evaluating directly.
In terminal session you're just entering:
./bootstrap.sh AWS
Result:
# /donuts

you can call function from command line argument like below
function irfan() {
echo "Irfan khan"
date
hostname
}
function config() {
ifconfig
echo "hey"
}
$1
Once you defined the functions put $1 at the end to accept argument which function you want to call.
Lets say the above code is saved in fun.sh. Now you can call the functions like ./fun.sh irfan & ./fun.sh config in command line.

Related

Im missing an output from MOTD, MTU and users from docker group

So, I'm writing a bash script that doesnt give me any output.
The script is:
a) going to detect what operating system that is running
b) And know what package managers to use between APT, DNF and Pacman.
Further in the script it is:
a) going to choose the correct package manager to use when installing both Docker and Docker-Compose.
I have written down the MOTD function that should show a message on my ubuntu server.
Im creating a function that adds users to a docker group.
Configuring Docker Daemon that sets a specific MTU value to 1442 and logging.
The problem is that I dont get any output, otherwise from the MTU value that is actually 1442, that seems correct in my script.
Furhter i should get an empty line where i can get an input scenario to add a user that will be added in to a docker group.
#!/bin/bash
# This script will install Docker and Docker-Compose, configure the Docker daemon,
# and add specified users to the docker group.
# Define default values
MTU=1442
VERBOSE=false
# Function to detect operating system
detect_os() {
if [ -f /etc/lsb-release ]; then
os="ubuntu"
package_manager="apt"
elif [ -f /etc/redhat-release ]; then
os="centos"
package_manager="dnf"
elif [ -f /etc/arch-release ]; then
os="arch"
package_manager="pacman"
else
echo "Error: Unable to detect operating system."
exit 1
fi
}
# Function to update MOTD
update_motd() {
local motd_file="/etc/motd"
echo "$1" > "$motd_file"
echo "MOTD updated with message: $1"
}
# Function to add users to docker group
add_users() {
local users="$1"
local group="docker"
for user in $users; do
# Check if user exists
if ! id "$user" >/dev/null 2>&1; then
useradd "$user"
echo "User $user created."
fi
# Add user to docker group
usermod -aG "$group" "$user"
echo "User $user added to $group group."
done
}
# Function to install Docker and Docker-Compose
install_docker() {
local package_manager="$1"
local packages="docker docker-compose"
case "$package_manager" in
apt)
sudo apt-get update
sudo apt-get install -y $packages
;;
dnf)
sudo dnf install -y $packages
;;
pacman)
sudo pacman -S --noconfirm $packages
;;
*)
echo "Error: Invalid package manager: $package_manager"
exit 1
;;
esac
}
# Function to configure Docker daemon
configure_docker() {
local mtu="$1"
local config_file="/etc/docker/daemon.json"
# Create config file if it does not exist
if [ ! -f "$config_file" ]; then
sudo touch "$config_file"
sudo chmod 644 "$config_file"
fi
# Update MTU value in config file
sudo sh -c "echo '{\"mtu\": $mtu}' > $config_file"
echo "Docker daemon configured with MTU=$mtu."
}
# Parse command line argume
while [ "$#" -gt 0 ]; do
case "$1" in
--motd )
MOTD="$2"
shift 2
;;
--users)
USERS="$2"
shift 2
;;
--mtu)
MTU="$2"
shift 2
;;
esac
done
echo "MOTD: $MOTD"
echo "USERS: $USERS"
echo "MTU: $MTU"
echo "Script is finish"
The output doesnt show me anything more than the MTU=1442, and missing the users and MOTD.
Im not sure if I was clear enough, but from my project i thought my script was correct, but probably I'm missing some logic any places in my script. The projects tasks are described above, but im not sure if im on the right way here
Would appreciate any suggestions for the way in my script :)
This is not a full-fix of your script - since I'm sure you are not about to cheat on your project but want to understand and know why your script doesn't provide your expected output so you will be able to develop it on your own.
Here I'm pasting a small script that may help you better understand the basic usage of functions in BASH. Hope it will help 🤞.
#!/bin/bash
### Defining function - Functions are reusable code blocks in the script and can accept arguments while calling them.
# So each time we call an individual function later in the script we may pass different arguments to it (if needed).
my_function1(){
echo "this is a function that doesn't expect any arguments."
echo "End of 'my_function1'"
}
my_function2(){
echo "this is a function that do expect an argument."
echo "this function expects one argument to print/echo it."
echo "Hello ${1}" # <-- Numerical Variables ($1 $2 .. $N) are reserved variables in 'BASH' which values are assigned from the relevant argument(s) provided to them on script runtime and function runtime.
echo "End of 'my_function2'"
}
my_function3(){
echo "this is a function that expect one or more arguments."
echo "this function print/echo all arguments passed to it."
echo "Hi ${#}"
echo "End of 'my_function3'"
}
### Calling the functions to execute their code - we may pass relevant argument(s) to them.
# This is done by using the function name - and any parameter/string added after the function name will be passed to it as the function's argument accordingly.
# Running the `my_function1` without providing any arguments - since it is not neccessary.
my_function1
# Print an empty line to seperate outputs
echo ""
# Running the `my_function2` passing it a name as argument. Ex. Vegard
my_function2 Vegard
# Print an empty line to seperate outputs
echo ""
# Running the `my_function3` passing it a `name` as first argument and a `LAST_NAME` as second argument. Ex. Vegard YOUR_LASTNAME
my_function3 Vegard YOUR_LASTNAME
# Print an empty line to seperate outputs
echo ""
### End of the script.
# Exitting the script with the `0` exit-code.
exit 0
Bonus Update #1
How to provide arguments to a script at run time:
You can provide arguments to the scripts almost in the same way as providing arguments to the functions.
Assuming the script file name is script.sh, it is located in our current working directory, and it is executable:
NAME - as first argument.
LAST_NAME - as second argument.
Run the script as follows:
./script.sh NAME LAST_NAME
Bonus Update #2
How to provide Dynamic arguments to a function from the script run time:
If you need to provide a dynamic argument for a function at runtime instead of having hard-coded argument(s) for that function, you may use the same
reserved numeric variables princip.
Simple example
Consider you run your script providing some argument that can be change on every run.
./script.sh firstarg secondarg "last arg"
Note: If a single argument contains space character should be quoted to avoid detecting it as separate arguments - same applies to providing arguments to funtions
Sum-up: These arguments will can be called by $1 $2 .. $<N> variables accordingly within the Script anywhere out of the Functions code blocks.
${#} or ${*} will get all the provided arguments - google to find their difference.
Consider you defined functions that works with one or more arguments.
#!/bin/bash
my_function(){
# Since this $1 is defined in the function's block itself, it
# will get its value from the argument provided to function
# at run-time Not directly from the arguments provided to the Script!
echo "Argument I got is: ${1}"
}
my_other_function(){
# Printing the first three arguments provided to function
# delimited by Collons.
echo "Arguments I got are: ${1} : ${2} : ${3}"
}
another_function(){
# $# will get all the argument provided to function
# at run-time Not directly from the arguments provided to the Script!
echo "All arguments got are: ${#}"
}
### Now at calling those functions
# Providing a static argument
my_function STATIC_ARGUMENT
# Passing the First argument provided to the script at run-time to the same function.
my_function "${1}"
# Passing the Three arguments provided to the script at run-time to this function.
my_other_function "${1}" "${2}" "${3}"
# Passing all the provided arguments of the script to this function at run-time.
another_function ${#}
Summery
The same numeric reserved variables that used to refer to the argument passed to the script can be passed to a function when calling it, and in the same manner the function's arguments can be referred from within the function block.
Caution
The behavior of a script that deals with any argument that will contain space or other special character may vary since the Bash threats them differently.

Redefine echo for several scripts by calling a script that redefines echo

I have scripts A, B, C, etc. that I execute independently. I want to redefine how echo works for all scripts I am using.
I made the following script, preamble.sh, that I call from each script A, B, C:
#!/bin/bash
# Change the color of our output
# TODO: How to redefine echo for the parent caller?
PURPLE='\033[1;35m'
NC='\033[0m' # No Color
function echo() { builtin echo -e '\033[1;35m'$0'\033[0m'; }
unameOut="$(uname -s)"
case "${unameOut}" in
Linux*) machine=Linux ;;
Darwin*) machine=Mac ;;
CYGWIN*) machine=Cygwin ;;
MINGW*) machine=MinGw ;;
*) machine="UNKNOWN:${unameOut}" ;;
esac
echo "[Running on ${machine}...]"
When I do bash preamble.sh from another script, all I get is
preamble.sh
in purple (the color that I want echo to use in each script A, B, C, etc.).
I guess what ends up happening is echo is redefined correctly within preamble.sh but this is not how I expected it to work. When I call bash preamble.sh, preamble.sh gets executed but instead of telling me what machine it runs on, it just prints preamble.sh in purple because that must be the argument $0.
I realize I might be doing something that is not possibly to do directly.
How do I achieve what I am trying to achieve?
The arguments to a function are $1, $2, ...
You want:
function echo() { builtin echo -e '\033[1;35m'$1'\033[0m'; }
not:
function echo() { builtin echo -e '\033[1;35m'$0'\033[0m'; }
Whether inside a function of not, $0 will remain the name of the script itself.
Edit: For your other question, you would need to run the script within the current shell for the changes to persist. You can do this using either
source preamble.sh
or
. preamble.sh
This is necessary since by default, the script will run in a new shell and any variables, functions, etc you define will not be visible.

Bash config file or command line parameters

If I am writing a bash script, and I choose to use a config file for parameters. Can I still pass in parameters for it via the command line? I guess I'm asking can I do both on the same command?
The watered down code:
#!/bin/bash
source builder.conf
function xmitBuildFile {
for IP in "{SERVER_LIST[#]}"
do
echo $1#$IP
done
}
xmitBuildFile
builder.conf:
SERVER_LIST=( 192.168.2.119 10.20.205.67 )
$bash> ./builder.sh myname
My expected output should be myname#192.168.2.119 and myname#10.20.205.67, but when I do an $ echo $#, I am getting 0, even when I passed in 'myname' on the command line.
Assuming the "config file" is just a piece of shell sourced into the main script (usually containing definitions of some variables), like this:
. /etc/script.conf
of course you can use the positional parameters anywhere (before or after ". /etc/..."):
echo "$#"
test -n "$1" && ...
you can even define them in the script or in the very same config file:
test $# = 0 && set -- a b c
Yes, you can. Furthemore, it depends on your architecture of script. You can overwrite parametrs with values from config and vice versa.
By the way shflags may be pretty useful in writing such script.

Any way to exit bash script, but not quitting the terminal

When I use exit command in a shell script, the script will terminate the terminal (the prompt). Is there any way to terminate a script and then staying in the terminal?
My script run.sh is expected to execute by directly being sourced, or sourced from another script.
EDIT:
To be more specific, there are two scripts run2.sh as
...
. run.sh
echo "place A"
...
and run.sh as
...
exit
...
when I run it by . run2.sh, and if it hit exit codeline in run.sh, I want it to stop to the terminal and stay there. But using exit, the whole terminal gets closed.
PS: I have tried to use return, but echo codeline will still gets executed....
The "problem" really is that you're sourcing and not executing the script. When you source a file, its contents will be executed in the current shell, instead of spawning a subshell. So everything, including exit, will affect the current shell.
Instead of using exit, you will want to use return.
Yes; you can use return instead of exit. Its main purpose is to return from a shell function, but if you use it within a source-d script, it returns from that script.
As §4.1 "Bourne Shell Builtins" of the Bash Reference Manual puts it:
return [n]
Cause a shell function to exit with the return value n.
If n is not supplied, the return value is the exit status of the
last command executed in the function.
This may also be used to terminate execution of a script being executed
with the . (or source) builtin, returning either n or
the exit status of the last command executed within the script as the exit
status of the script.
Any command associated with the RETURN trap is executed
before execution resumes after the function or script.
The return status is non-zero if return is used outside a function
and not during the execution of a script by . or source.
You can add an extra exit command after the return statement/command so that it works for both, executing the script from the command line and sourcing from the terminal.
Example exit code in the script:
if [ $# -lt 2 ]; then
echo "Needs at least two arguments"
return 1 2>/dev/null
exit 1
fi
The line with the exit command will not be called when you source the script after the return command.
When you execute the script, return command gives an error. So, we suppress the error message by forwarding it to /dev/null.
Instead of running the script using . run2.sh, you can run it using sh run2.sh or bash run2.sh
A new sub-shell will be started, to run the script then, it will be closed at the end of the script leaving the other shell opened.
Actually, I think you might be confused by how you should run a script.
If you use sh to run a script, say, sh ./run2.sh, even if the embedded script ends with exit, your terminal window will still remain.
However if you use . or source, your terminal window will exit/close as well when subscript ends.
for more detail, please refer to What is the difference between using sh and source?
This is just like you put a run function inside your script run2.sh.
You use exit code inside run while source your run2.sh file in the bash tty.
If the give the run function its power to exit your script and give the run2.sh
its power to exit the terminator.
Then of cuz the run function has power to exit your teminator.
#! /bin/sh
# use . run2.sh
run()
{
echo "this is run"
#return 0
exit 0
}
echo "this is begin"
run
echo "this is end"
Anyway, I approve with Kaz it's a design problem.
I had the same problem and from the answers above and from what I understood what worked for me ultimately was:
Have a shebang line that invokes the intended script, for example,
#!/bin/bash uses bash to execute the script
I have scripts with both kinds of shebang's. Because of this, using sh or . was not reliable, as it lead to a mis-execution (like when the script bails out having run incompletely)
The answer therefore, was
Make sure the script has a shebang, so that there is no doubt about its intended handler.
chmod the .sh file so that it can be executed. (chmod +x file.sh)
Invoke it directly without any sh or .
(./myscript.sh)
Hope this helps someone with similar question or problem.
To write a script that is secure to be run as either a shell script or sourced as an rc file, the script can check and compare $0 and $BASH_SOURCE and determine if exit can be safely used.
Here is a short code snippet for that
[ "X$(basename $0)" = "X$(basename $BASH_SOURCE)" ] && \
echo "***** executing $name_src as a shell script *****" || \
echo "..... sourcing $name_src ....."
I think that this happens because you are running it on source mode
with the dot
. myscript.sh
You should run that in a subshell:
/full/path/to/script/myscript.sh
'source' http://ss64.com/bash/source.html
It's correct that sourced vs. executed scripts use return vs. exit to keep the same session open, as others have noted.
Here's a related tip, if you ever want a script that should keep the session open, regardless of whether or not it's sourced.
The following example can be run directly like foo.sh or sourced like . foo.sh/source foo.sh. Either way it will keep the session open after "exiting". The $# string is passed so that the function has access to the outer script's arguments.
#!/bin/sh
foo(){
read -p "Would you like to XYZ? (Y/N): " response;
[ $response != 'y' ] && return 1;
echo "XYZ complete (args $#).";
return 0;
echo "This line will never execute.";
}
foo "$#";
Terminal result:
$ foo.sh
$ Would you like to XYZ? (Y/N): n
$ . foo.sh
$ Would you like to XYZ? (Y/N): n
$ |
(terminal window stays open and accepts additional input)
This can be useful for quickly testing script changes in a single terminal while keeping a bunch of scrap code underneath the main exit/return while you work. It could also make code more portable in a sense (if you have tons of scripts that may or may not be called in different ways), though it's much less clunky to just use return and exit where appropriate.
Also make sure to return with expected return value. Else if you use exit when you will encounter an exit it will exit from your base shell since source does not create another process (instance).
Improved the answer of Tzunghsing, with more clear results and error re-direction, for silent usage:
#!/usr/bin/env bash
echo -e "Testing..."
if [ "X$(basename $0 2>/dev/null)" = "X$(basename $BASH_SOURCE)" ]; then
echo "***** You are Executing $0 in a sub-shell."
exit 0
else
echo "..... You are Sourcing $BASH_SOURCE in this terminal shell."
return 0
fi
echo "This should never be seen!"
Or if you want to put this into a silent function:
function sExit() {
# Safe Exit from script, not closing shell.
[ "X$(basename $0 2>/dev/null)" = "X$(basename $BASH_SOURCE)" ] && exit 0 || return 0
}
...
# ..it have to be called with an error check, like this:
sExit && return 0
echo "This should never be seen!"
Please note that:
if you have enabled errexit in your script (set -e) and you return N with N != 0, your entire script will exit instantly. To see all your shell settings, use, set -o.
when used in a function, the 1st return 0 is exiting the function, and the 2nd return 0 is exiting the script.
if your terminal emulator doesn't have -hold you can sanitize a sourced script and hold the terminal with:
#!/bin/sh
sed "s/exit/return/g" script >/tmp/script
. /tmp/script
read
otherwise you can use $TERM -hold -e script
If a command succeeded successfully, the return value will be 0. We can check its return value afterwards.
Is there a “goto” statement in bash?
Here is some dirty workaround using trap which jumps only backwards.
#!/bin/bash
set -eu
trap 'echo "E: failed with exitcode $?" 1>&2' ERR
my_function () {
if git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
echo "this is run"
return 0
else
echo "fatal: not a git repository (or any of the parent directories): .git"
goto trap 2> /dev/null
fi
}
my_function
echo "Command succeeded" # If my_function failed this line is not printed
Related:
https://stackoverflow.com/a/19091823/2402577
How to use $? and test to check function?
I couldn't find solution so for those who want to leave the nested script without leaving terminal window:
# this is just script which goes to directory if path satisfies regex
wpr(){
leave=false
pwd=$(pwd)
if [[ "$pwd" =~ ddev.*web ]]; then
# echo "your in wordpress instalation"
wpDir=$(echo "$pwd" | grep -o '.*\/web')
cd $wpDir
return
fi
echo 'please be in wordpress directory'
# to leave from outside the scope
leave=true
return
}
wpt(){
# nested function which returns $leave variable
wpr
# interupts the script if $leave is true
if $leave; then
return;
fi
echo 'here is the rest of the script, executes if leave is not defined'
}
I have no idea whether this is useful for you or not, but in zsh, you can exit a script, but only to the prompt if there is one, by using parameter expansion on a variable that does not exist, as follows.
${missing_variable_ejector:?}
Though this does create an error message in your script, you can prevent it with something like the following.
{ ${missing_variable_ejector:?} } 2>/dev/null
1) exit 0 will come out of the script if it is successful.
2) exit 1 will come out of the script if it is a failure.
You can try these above two based on ur req.

Unable to run BASH script in current environment multiple times

I have a bash script that I use to move from source to bin directories from anywhere I currently am (I call this script, 'teleport'). Since it basically is just a glorified 'cd' command, I have to run it in the current shell (i.e. . ./teleport.sh ). I've set up an alias in my .bashrc file so that 'teleport' matches '. teleport.sh'.
The first time I run it, it works fine. But then, if I run it again after it has run once, it doesn't do anything. It works again if I close my terminal and then open a new one, but only the first time. My intuition is that there is something internally going on with BASH that I'm not familiar with, so I thought I would run it through the gurus here to see if I can get an answer.
The script is:
numargs=$#
function printUsage
{
echo -e "Usage: $0 [-o | -s] <PROJECT>\n"
echo -e "\tMagically teleports you into the main source directory of a project.\n"
echo -e "\t PROJECT: The current project you wish to teleport into."
echo -e "\t -o: Teleport into the objdir.\n"
echo -e "\t -s: Teleport into the source dir.\n"
}
if [ $numargs -lt 2 ]
then
printUsage
fi
function teleportToObj
{
OBJDIR=${HOME}/Source/${PROJECT}/obj
cd ${OBJDIR}
}
function teleportToSrc
{
cd ${HOME}/Source/${PROJECT}/src
}
while getopts "o:s:" opt
do
case $opt in
o)
PROJECT=$OPTARG
teleportToObj
;;
s)
PROJECT=$OPTARG
teleportToSrc
;;
esac
done
My usage of it is something like:
sjohnson#corellia:~$ cd /usr/local/src
sjohnson#corellia:/usr/local/src$ . ./teleport -s some-proj
sjohnson#corellia:~/Source/some-proj/src$ teleport -o some-proj
sjohnson#corellia:~/Source/some-proj/src$
<... START NEW TERMINAL ...>
sjohnson#corellia:~$ . ./teleport -o some-proj
sjohnson#corellia:~/Source/some-proj/obj$
The problem is that getopts necessarily keeps a little bit of state so that it can be called in a loop, and you're not clearing that state. Each time it's called, it processes one more argument, and it increments the shell's OPTIND variable so it'll know which argument to process the next time it's called. When it's done with all the arguments, it returns 1 (false) every time it's invoked, which makes the while exit.
The first time you source your script, it works as expected. The second (and third, fourth...) time, getopts does nothing but return false.
Add one line to reset the state before you start looping:
unset OPTIND # clear state so getopts will start over
while getopts "o:s:" opt
do
# ...
done
(I assume there's a typo in your transcript, since it shows you invoking the script -- not sourcing it -- on the second try, but that's not the real problem here.)
The problem is that the first time you call is you are sourcing the script (thats what ". ./teleport") does which runs the script in the current shell thus preserving the cd. The second time you call it, it isn't sourced so you create a subshell, cd to the appropriate directory, and then exit the subshell putting you right back where you called the script from!
The way to make this work is simply to make teleportToSrc and teleportToObj aliases or functions in the current shell (i.e. outside a script)

Resources