How can Bash execute a command in a different directory context?
Asked Answered
I

7

168

I have a common command that gets called from within very specific directories. There is only one executable sitting in /bin for this program, and the current working directory is very important for running it correctly. The script affects the files that live inside the directory it is run within.

Now, I also have a custom shell script that does some things in one directory, but I need to call that command mentioned above as if it was in another directory.

How do you do this in a shell script?

Isreal answered 12/5, 2012 at 18:58 Comment(1)
L
47

You can use the cd builtin, or the pushd and popd builtins for this purpose. For example:

# do something with /etc as the working directory
cd /etc
:

# do something with /tmp as the working directory
cd /tmp
:

You use the builtins just like any other command, and can change directory context as many times as you like in a script.

Lover answered 12/5, 2012 at 19:5 Comment(6)
There's also cd - in a POSIX shell to change back to the previous directory. That said, (a) pushd and popd are really intended for interactive use, and (b) it's not always safe to assume that you can cd back where you started, especially in complex subsystems which may use restrictive permissions internally (see #10555784).Giroux
Using a subshell is a much better solutionBricole
For scripting, a subshell is probably a better idea.. However I'm really happy to learn about popd.Nogging
Can you please explain what does : symbol mean here?Antineutron
@VadimKotov I think it's effectively a no-op. Relevant section of man bash reads: : [arguments] No effect; the command does nothing beyond expanding arguments and performing any specified redirections. The return status is zero.Unoccupied
For anyone interested, look here - #3225378Antineutron
G
347

Use cd in a subshell; the shorthand way to use this kind of subshell is parentheses.

(cd wherever; mycommand ...)

That said, if your command has an environment that it requires, it should really ensure that environment itself instead of putting the onus on anything that might want to use it (unless it's an internal command used in very specific circumstances in the context of a well defined larger system, such that any caller already needs to ensure the environment it requires). Usually this would be some kind of shell script wrapper.

Giroux answered 12/5, 2012 at 19:4 Comment(4)
You can also insert a \ after each ; for multi-line commands within the same context.Throughway
This is the way.Effortful
This solution is good as long as spawning a new shell is not a problem.Natelson
This is broken (assuming set +e which is the default). If cd fails, mycommand will be executed in the wrong directory. Better to use && instead of of ; - or use exit to abort if the cd fails.Tse
G
53
(cd /path/to/your/special/place;/bin/your-special-command ARGS)
Grume answered 12/5, 2012 at 19:4 Comment(0)
L
47

You can use the cd builtin, or the pushd and popd builtins for this purpose. For example:

# do something with /etc as the working directory
cd /etc
:

# do something with /tmp as the working directory
cd /tmp
:

You use the builtins just like any other command, and can change directory context as many times as you like in a script.

Lover answered 12/5, 2012 at 19:5 Comment(6)
There's also cd - in a POSIX shell to change back to the previous directory. That said, (a) pushd and popd are really intended for interactive use, and (b) it's not always safe to assume that you can cd back where you started, especially in complex subsystems which may use restrictive permissions internally (see #10555784).Giroux
Using a subshell is a much better solutionBricole
For scripting, a subshell is probably a better idea.. However I'm really happy to learn about popd.Nogging
Can you please explain what does : symbol mean here?Antineutron
@VadimKotov I think it's effectively a no-op. Relevant section of man bash reads: : [arguments] No effect; the command does nothing beyond expanding arguments and performing any specified redirections. The return status is zero.Unoccupied
For anyone interested, look here - #3225378Antineutron
T
29

If you want to return to your current working directory:

current_dir=$PWD;cd /path/to/your/command/dir;special command ARGS;cd $current_dir;
  1. We are setting a variable current_dir equal to your pwd
  2. after that we are going to cd to where you need to run your command
  3. then we are running the command
  4. then we are going to cd back to our variable current_dir

Or

pushd && cd /path/to/your/command/dir && special command ARGS && popd

Courtesy of @apieceofbart and @Konstantin

Tindle answered 6/11, 2014 at 18:42 Comment(4)
or you could do pushd && YOUR COMMAND && pulldMitziemitzl
@Mitziemitzl I think you meant popd instead of pulld.Coalesce
I beleive the last command should be pushd /path/to/your/command/dir && special command ARGS && popdDrogue
These solutions can have weird outcomes in case your special command fails. The one with ";" won't propagate command return value but will keep che current working directory. The one with "&&" will leave you in the wrong directory.Natelson
N
9

You could also use the env command to achieve the same without an extra shell:

env --chdir=/whatever/path -S whatever_command -some -option and any other arguments

Where whatever_command is the command or script to be executed with all the relevant options and argument. All this must follow the -S option of the env command.

The current working directory is only changed for the execvp() system call so, at the end it will be back to its original value.

env is part of the GNU coreutils package and a copy of its manual page can be found here for more details.

For example you can use env also to manipulate the environment variables and the signal handling only for a specific execution.

This won't work with shell aliases and shell functions as env works only with programs.

Natelson answered 26/5, 2023 at 14:25 Comment(0)
C
4

Some remarks

1. 🟥🟥🟥   W A R N I N G ! ! !   🟥🟥🟥

Comming late to this question, I have to see: no answer here address ShellCheck SC2164!!

Use cd ... || exit in case cd fails,

Explanation:
If you hit some mispelled path before doing some destructive command:

( cd /wrong/path ; rm -fR * )

then the cd command will fail, but
    subsequent command will anyway destroy everything in the current directory!!

So the correct command is

( cd /path/to/wherever || exit; mycommand ... )

(you could replace exit by return if in a function or continue if in a loop),

or

( cd /path/to/wherever && mycommand ... )

2. RTFM

Before doing some more steps then required, maybe should you have a look at the man page of the command you plan to run!

There is a lot of commands, like tar, wget and others, which offer specific parameter for doing this:

$ man tar | grep -A3  ' --directory'
  -C, --directory=DIR
         Change to DIR before performing any operations.  This option  is
         order-sensitive, i.e. it affects all options that follow.

So doing

( cd /some/path && tar -cpl somedir )

will do same than

tar -cplC /some/path somedir

3. Storing command's output into shell variables

For sample, trying to read result of find command into some array variable:

Instead of trying something like this:

 ( cd /some/path && var=$(du -chs .) )

 ( cd /some/path && mapfile -d '' -t array < <(find . -type f -print0 ) )

because none of your $var or $array won't exist outside of subshell (parenthesis).

but, you may run:

var=$( cd /some/path && du -chs . )

mapfile -d '' -t array < <( cd /some/path && find . -type f -print0 )

4. chroot instead of just cd:

Sometime,

Bash execute a command in a different (directory) context?

could mean in the context of a virtual machine that is not currently in running state, or in the context of a backuped host, in a specific backup or even else...

For sample, using lxc you could show journal off a stopped container by running:

sudo chroot /var/lib/lxc/stoppedContainer/rootfs/ journalctl -ax

But in manual page, you may read that you could to (near) same by using --root option of journalctl:

sudo journalctl --root /var/lib/lxc/stoppedContainer/rootfs -ax

In fact, there are totally different commands, as

  • in first sample using chroot, you will run the correct journalctl command, that is installed in the container (/var/lib/lxc/stoppedContainer/rootfs/usr/bin/journalctl),
  • but the second sample will work only if the journalctl installed in the host (hardware node /usr/bin/journalctl) is able to read container's journal data (version compatibility).
Chaparajos answered 20/10, 2023 at 5:10 Comment(0)
P
0

Just a tibit helpful info:

This will change the shell directory to ~/project

function mymake() {
    cd ~/project && make $1
}

This won't

function mymake() {
    (cd ~/project && make $1)
}

to run

mymake something
Psycho answered 30/9, 2021 at 16:20 Comment(3)
So essentially use curly braces and not parenthesis to wrap function contents.Carcinogen
No. This is basically bad bash programming. They are defining a function without a block, using a single set of parenthesis as the expression. Parenthesis create a new shell which will exit, restoring the old shell. If this person wanted to keep future bash programmers from frustration in debugging sessions, they would have done function mymake() { ( cd ~/project && make $1 ) } to avoid the easy mistake that the parenthesis are curly braces.Dixil
Make has a -C argument specifically for running in an alternative directory, so this is perhaps a poor subject for the example.Tse

© 2022 - 2024 — McMap. All rights reserved.