Is there a way to execute a command in a different directory without having to cd
to it? I know that I could simply cd
in and cd
out, but I'm just interested in the possibilities of forgoing the extra steps :)

- 39,676
11 Answers
I don't know if this counts, but you can make a subshell:
$ (cd /var/log && cp -- *.log ~/Desktop)
The directory is only changed for that subshell, so you avoid the work of needing to cd -
afterwards.

- 544,893

- 93,103
- 40
- 240
- 233
-
2
-
Nice, I had forgotten altogether about sub-shells, very handy in this case. – Naftuli Kay May 26 '11 at 21:02
-
10To avoid potential chaos, sub out the semicolon with two ampersands:
$ (cd /var/log && cp *.log ~/Desktop)
. That way, if the directory doesn't exist, no further commands are executed. – Naftuli Kay Apr 11 '13 at 19:38 -
10Note for anyone else as naive as me: the $ at start isn't part of the command, just everything after it. – Maltronic Apr 15 '17 at 13:58
-
1
Not to undermine the value of answers given by other people, but I believe what you want is this:
(cd /path/to && ./executable [ARGS])
Note the parens to invoke cd
in a sub-shell.

- 7,223
-
1I don't think so. As I understand, he wants to end up in the initial directory. – Adam Byrtek May 26 '11 at 17:51
-
2Oh, I totally forgot to wrap that into sub-shell. Corrected my answer, thank you. – alex May 26 '11 at 18:48
Some programs have options with which you can tell them to chdir(2) themselves (e.g. GNU tar’s -C
/--directory
).
Outside of such programs though, something will have to chdir. You could write and use some sort of compiled “binary” program instead of having the shell do it, but it probably would not yield much benefit.
In a comment in another answer, you gave an example:
execindirectory -d /var/log "cp *.log ~/Desktop"
Since the *.log
pattern is expanded by the shell itself (not cp), something will have to chdir to the directory before having a shell evaluate your command.
If you are just interesting in avoiding having to “cd back”, then you can use a subshell to isolate the effect of the cd from your working shell instance.
(cd /path/to/dir && some command)
You can package this up in a shell function.
(I dropped the -d
option from your example usage since there is little point to this command if the directory is actually optional.)
runindir() { (cd -- "$1" && shift && eval " $@"); }
runindir /var/log 'cp -- *.log ~/Desktop' # your example
runindir /var/log cp -- *.log ~/Desktop # eval takes multiple args
# which it concatenates
# with spaces prior to
# evaluation
runindir /var/log cp -- \*.log ~/Desktop# it is not okay to expand tilde first # as the expansion would then be # interpreted as shell code and # would break if your $HOME contained # characters special to the shell.

- 544,893

- 20,101
The env
program from GNU coreutils-8.28
(released 2017-09-01) and newer has --chdir
:
env: add --chdir option
This is useful when chaining with other commands that run commands in a different context, while avoiding using the shell to cd, and thus having to consider shell quoting the chained command.
$ env --help | grep chdir
-C, --chdir=DIR change working directory to DIR
So on a modern system (RHEL 8+, Ubuntu LTS 20.04+) you can just:
env --chdir=/tmp pwd
This is especially useful if you need to use sudo
and don't want to mess up quoting the arguments.
FILENAME="Some file maybe with spaces.txt"
FILEDIR="/some/directory maybe with spaces"
sudo env --chdir "$FILEDIR" \
zip "/tmp/$FILENAME.zip" "$FILENAME"

- 211
-
Nice find, though not portable. For example, busybox env doesn't support the chdir option, so it won't work on Alpine linux. – ʇsәɹoɈ Mar 31 '23 at 01:42
-
For machines that don't have GNU
env
(e.g. macOS), a portable one-liner that would act similarly issh -c 'cd -- "$1" || exit 1; shift; "$@"' -- DIR CMD [...]
(whereDIR
is the desired working-directory andCMD [...]
is the command, with any optional arguments) – Josh Bode Mar 30 '24 at 07:51
Here is something that should let you cd
back where you were (using Bash), since not forgetting to do so seems to be the purpose of the question:
# Save where you are and cd to other dir
pushd /path/to/dir/that/needs/to/be/current/dir
run-your-command
# Get back where you were at the beginning.
popd
(EDIT: slightly shorter version, thanks to @Random832)

- 1,133
Sadly, your example:
execindirectory -d /var/log "cp *.log ~/Desktop"
doesn't need a change to the dir, because
cp /var/log/*.log ~/Desktop
would do the same. Can't you get closer to your real problem? Because we might know a better solution for that too.
A complicated way to solve your problem, which is far away from the elegance of Michaels solution, is the usage of find, which has a switch '-execdir' to be performed in the dir, where a file is found. Badly adopted to your example:
find /var/log -maxdepth 1 -type f -name "*.log" -execdir echo cp {} ~/Desktop ";"
Maybe it is useful for your real problem. -okdir instead of -execdir will ask you to confirm every invocation.
-okdir and -execdir might need gnu-find to be installed, which is typically used on Linux.

- 10,482
-
Of course, if there are a lot** of files in
/var/log
, thencp /var/log/*.log ~/Desktop
might overflow the maximum command line size whencp *.log ~/Desktop
doesn’t. – G-Man Says 'Reinstate Monica' Sep 10 '22 at 19:18 -
Who has thousands of logfiles in /var/log? Just tested a copy with 13.000 files in a long pathname without problem. – user unknown Sep 11 '22 at 01:15
-
No, I actually meant something like the following:
execindirectory -d /var/log "cp *.log ~/Desktop"
. – Naftuli Kay May 26 '11 at 01:23 -
I'm perfectly aware that this isn't strictly necessary, by the way, I'm just interested in if it can actually be done. – Naftuli Kay May 26 '11 at 01:24
If I have a bash script in /home/user/
and the bash script is myscript
there are a lot of solutions.
/home/user/myscript
#!/bin/bash
echo "WELCOME BASH"
Run chmod +x /home/user/myscript
create an
alias
and execute by alias nameTemporary
alias
— run this in the terminalalias myscript="/home/user/myscript"
Permanent
alias
in~/.bashrc
(/home/user/.bashrc) — add/edit this to your.bashrc
filealias myscript="/home/user/myscript"
Now run in terminal:
myscript
add the directory path to
$PATH
and execute by name
Run in terminal:
/home/user/myscript
./home/user/myscript
This works only if your current directory is/
./home/user/./myscript
. ./home/user/myscript
This works only if your current directory is/
.source ./home/user/myscript
This works only if your current directory is/
.source /home/user/./myscript
source /home/user/myscript
bash /home/user/myscript
bash ./home/user/myscript
This works only if your current directory is/
.bash /home/user/./myscript
References to explain differences among some of the above:
What is the difference between executing a Bash script vs sourcing it?
What is the difference between sourcing ('.' or 'source') and executing a file in bash?
I run this commands in a terminal on a Debian 10.9 and they work.
GNU bash, Version 5.0.3(1)-release (x86_64-pc-linux-gnu)

- 3,149
-
(1)
script
is a bad example name for a script. I suggest that you use things likefoo
,myscript
, or perhaps something derived from your name, or the OP’s. (2) Your answers are fundamentally the same as bdictator’s answer, which missed the point of the question; hence, so do yours. (3) Answer 8 simply *will not work.* Several others depend on an unstated assumption; in other words, they will not work in general. (4) Answers 5, 9, and 13 are just silly. … (Cont’d) – G-Man Says 'Reinstate Monica' Jul 14 '22 at 17:26 -
(Cont’d) … (5) If you’re going to post a list of similar answers, you should explain how they differ (because they most certainly are not equivalent). … … … … … … … … … … … … … … … … … Please do not respond in comments; [edit] your answer to make it clearer and more complete. – G-Man Says 'Reinstate Monica' Jul 14 '22 at 17:26
-
The question on the title is | Execute a specific command in a given directory without cd'ing to it? – Z0OM Jul 14 '22 at 17:34
-
2
-
@G-Man Says 'Reinstate Monica' I think you can edit the title and the question if i missed the point of the question? my english is not the best. – Z0OM Jul 14 '22 at 17:56
-
I wish Stack Exchange made it easier for users to indicate that English is not their first language. The question title is ambiguous; it becomes somewhat clearer if you read the second sentence of the question body, and then Naftuli Kay’s comment on bdictator’s answer. … … … … … … … … … … … … … … … … … … … … … … Imagine if the
ls
command didn’t take arguments (or you were limited to running it without arguments), so the only way to list the files in/var/log
would becd /var/log && ls
. The question is “How can I runls
*in*/var/log
without needing tocd
there?” … (Cont’d) – G-Man Says 'Reinstate Monica' Jul 14 '22 at 20:37 -
(Cont’d) … Thank you for responding to my comments (and for doing so politely). You’ve obviously put a lot of work into this, so I have changed my downvote to an upvote. (But, really, injecting
/./
in the middle of a pathname *is* silly.) – G-Man Says 'Reinstate Monica' Jul 14 '22 at 20:40
I'm not expert enough to know how universally it would work, but the source
command lets you load/execute something in another directory. I use this to activate a virtual environment, e.g. source ./venv/bin/activate
.

- 11
-
Thanks for contributing, but this is largely the same as bdictator’s answer, but with the added complication of the
source
command, which doesn’t help. – G-Man Says 'Reinstate Monica' Jul 14 '22 at 16:15 -
I initially tried exactly that (bdictator's answer), but it didn't work. It did when I included the
source
command, though. Is there a reason why one or the other would work with specific commands? – oCn Jul 15 '22 at 17:34 -
Well, the point is that bdictator’s answer is a wrong answer for this question (see Naftuli Kay’s comment), and so is yours. See my comments on Blockchain Office’s answer. So you and bdictator are both answering different questions, and using their answer on your problem is like comparing oranges and pumpkins. – G-Man Says 'Reinstate Monica' Jul 15 '22 at 17:50
-
OK, I missed the OP's comment on bdictator's answer. My response was to the way the original question was stated (I got here because I had the same question, as I understood it), which as you say turns out to be a different question. – oCn Jul 17 '22 at 11:12
(cd dir && cmd)
as already provided is the approach you'd want to use when cmd
is an external command. In many shells, the subshell doesn't involve an extra process, as cmd
is often executed in the process that was started to run the subshell, so the difference with cd dir; cmd
is that the fork is done earlier.
In those shells that don't, you can always change it to (cd dir && exec cmd)
to do the optimisation by yourself.
You can't use that approach though if the command you want to perform in that directory need to make permanent changes to your shell environment (such as setting some variables, change the umask, open some files...).
In:
(cd /dir && cmd && result_files=(*.out))
The result_files
array would only be set in that subshell and lost when that subshell terminates.
Using:
pushd /dir && {
cmd && result_files=(*.out)
ret=$?
popd
(exit "$ret")
}
(Or solutions using cd /dir
and cd -
instead of pushd
/popd
), besides being cumbersome are potentially unreliable as the $OLDPWD
could end up referring to a different directory after cmd
has returned.
If you do that in an interactive shell and press Ctrl+C while cmd
is running, your old directory will also not be restored.
The correct way to temporarily change to a different directory is to open()
.
on some file descriptor (with O_PATH
where supported so you don't even need to have read access to the directory), chdir()
to the new dir and then fchdir()
to the saved fd. That will bring you back to where you started even if your old current directory was renamed or removed.
However, I don't know of any shell that has an interface to fchdir()
let alone open(O_PATH)
. On Linux however you can approximate fchdir(x)
with chdir("/dev/fd/x")
, so in the zsh
shell, and assuming you have read access to the current working directory, you could do something like:
{
cd /some/dir && cmd && result=( *.out(N) )
} always {
cd -P /dev/fd/3
} 3<.
The always
block is always executed regardless of errors, break
return
or continue
in the try block.
You could also set the close-on-exec flag to that file descriptor so it's no leaked to commands in the try block with:
zmodload zsh/system
sysopen -u3 -o cloexec . && {
cd /some/dir && cmd && result=( *.out(N) )
} always {
cd -P /dev/fd/3
exec 3<&-
}

- 544,893
Here's a zsh shell alias and function to run a command in a directory, without using a subshell (in case the command has side effects on the shell environment that you'd like to keep):
alias direval='noglob _direval'
function _direval() {
pushd -- $1 && {
shift && eval " $@"
} always {
popd
}
}
Usage examples:
direval my-git-repo/scripts git ls-files
direval /usr/bin ls g*
direval /usr/bin GEXECS=$(echo g*)
The noglob
in the alias means you don't have to worry about escaping glob characters; they will be evaluated later in the directory you chose. Unfortunately I couldn't figure out a way to make direval /usr/bin GEXECS=g*
work, but using direval /usr/bin GEXECS=$(echo g*)
is an OK workaround.
Credit for some of the pieces here that came from other answers: thanks Stéphane Chazelas for the && { ... } always { popd }
idea, to Bruno for using pushd / popd, and to Chris Johnson for the idea of making a shell function to do this.

- 121
echo "#!/bin/bash; cd $1; exec $2" > /usr/local/bin/execindirectory; chmod +x /usr/local/bin/execindirectory
. Might need to put a little more effort if you want it to actually support option "tags" such as-d
and stuff. – LawrenceC May 26 '11 at 02:26