1. Overview
When writing shell scripts in Linux, we may come across a case where we have to switch directories and execute other scripts or programs during the course of our shell script.
In this tutorial, we’ll look at how we can run a script or a program from a different directory in a shell script. In order to meet the dependencies correctly, we need to keep track of the current directory for the context.
We’ll check how we can handle this problem in the following sections.
2. The Problem
Let’s say that we have a few scripts that are placed in different directories:
$ tree .
.
├── init
│ ├── init.conf
│ └── init.sh
├── run.sh
└── start.sh
1 directory, 4 files
Here, we’ve got the start.sh, which calls run.sh and init.sh:
$ cat init/init.conf
NAME=Baeldung
$
$ cat init/init.sh
#!/bin/bash
. init.conf
echo $NAME
$
$ cat run.sh
#!/bin/bash
echo "Running script"
As we can see, the init.sh script is kept inside the init directory. Given the situation, we need to correctly change the directories from start.sh so that the other script invocations work properly.
We’ve already discussed changing the directory in a shell script, but in this article, we’ll focus on maintaining the original context between different calls.
Let’s see the different ways of doing this.
3. Using a Variable
The first and simplest solution is to save the current directory in a variable and switch to the new one. After executing the script, we can restore the directory to the old one using the variable:
$ cat start.sh
#!/bin/bash
CUR_DIR=$(pwd)
cd init
./init.sh
cd $CUR_DIR
echo "Starting script"
./run.sh
$
$ ./start.sh
Baeldung
Starting script
Running script
In the above script, we captured the current directory using the pwd command and stored that in a variable CUR_DIR. After we execute init.sh, we switch back to the old directory, which is saved in CUR_DIR.
From the output, we can see that it has printed the name correctly from init.sh and also printed the echo statements from start.sh and run.sh scripts.
4. Without Using a Variable
In the above solution, we used a variable to store the current directory. But if we use the ‘cd -‘ command to go to the previous directory, we can attain the same results without creating an intermediate variable:
$ cat start.sh
#!/bin/bash
cd init
./init.sh
cd -
echo "Starting script"
./run.sh
$
$ ./start.sh
Baeldung
/home/bluelake/Documents/shell/chdir
Starting script
Running script
Here, calling the ‘cd -‘ command will change the directory to the old one. This works even if we change the directory inside init.sh, since init.sh runs as a child process.
A problem with this is that when the ‘cd -‘ command is executed, it echoes the new directory to stdout. If we need to suppress this, there are two ways of doing this.
One way is by redirecting the output to the null device:
cd - > /dev/null
And another is by using the Bash internal variable $OLDPWD:
cd $OLDPWD
Even though we achieved the results without creating a variable, depending upon whether we need this variable further down in our code validates if this is really saving us anything.
5. Running in a Subshell
Another solution is to run the init.sh script from a subshell. Let’s see how we can do this in start.sh:
$ cat start.sh
#!/bin/bash
(cd init; ./init.sh)
echo "Starting script"
./run.sh
$
$ ./start.sh
Baeldung
Starting script
Running script
When we need to invoke commands in a subshell, we put them in parentheses. Thus, we can see the init.sh script is called inside a subshell after changing the directory. This way, it runs separately without affecting the current shell.
Here, we’re assuming that the init directory always exists. But there are cases where we’re not sure about the directory. For those cases, we can use the logical AND operator to prevent invoking the init.sh script:
(cd init && ./init.sh)
With that, we ensure the init directory exists before executing the script.
6. Using the pushd Command
Let’s see how we can use pushd to solve this issue:
$ cat start.sh
#!/bin/bash
pushd init
./init.sh
popd
echo "Starting script"
./run.sh
$
$ ./start.sh
~/Documents/shell/chdir/init ~/Documents/shell/chdir
Baeldung
~/Documents/shell/chdir
Starting script
Running script
In the above script, we’ve used pushd and popd commands to switch between the directories. As we know, pushd saves the current directory and switches to the new directory given as an argument. And later, we can use popd to restore the pushed directory.
And from the results, we can see everything is working fine.
In order to suppress the output from pushd and popd commands, we can redirect them to the /dev/null device:
pushd init > /dev/null
./init.sh
popd > /dev/null
7. Using the sh Command
Finally, we can use the sh command to run it in a separate process so that it doesn’t affect the current shell:
$ cat start.sh
#!/bin/bash
sh -c 'cd init && ./init.sh'
echo "Starting script"
./run.sh
$
$ ./start.sh
Baeldung
Starting script
Running script
As we can see, we have passed the commands as an argument to the sh command. From the above results, we can see that it has been executed properly. However, this is similar to the earlier approach where we ran the init.sh script using a subshell.
8. Conclusion
In this tutorial, we’ve seen several different ways to execute scripts by changing the directory without affecting the current shell. Out of these, we can see that the method using subshell is the best solution for this problem.