1

Is there a command to run an arbitrary command in a specific directory?

In Debian 10, I could simply use env --chdir. However, env --chdir is not available on my Debian 9 install.

Part of the command I want to run is composed using shell variables, which could contain spaces or other special characters. For example, imagine the command I want to run is:

strace -- "$@"

Therefore, the following would not work:

sudo -i sh -c 'cd my-directory && exec strace -- "$@"'

uh, why do you want to do this?

I want to use sudo -i / sudo --login to run a command. But I want to run the command in a specific directory, not the home directory of the root user. Although sudo can set environment variables, it does not appear to have an option to change directory.

I am using sudo -i because: 1) I want the program I am running (my Ansible playbook) to have HOME=/root, which will avoid the program creating root-owned files in my home directory. 2) This sounded like an example of a family of problems with sudo without -i; an entirely fresh environment sounded like the most principled way to solve my problem.

sourcejedi
  • 50,249

2 Answers2

3

I will ignore exec and -i as it is not clear what you are asking with respect to these.

However to cd and sudo, then

( cd «a directory»; sudo «a command» )

The parenthesis () create a sub-shell, cd is a built-in, then the command sudo or whatever. So process count is same as env. sudo also manipulates environment variables, so may not be a need for env and sudo.

2

You can easily write a separate script called chdir:

#!/bin/sh
dir=$1 &&
shift &&
cd -- "$dir" &&
exec "$@"

Without a separate script, it is rather more obscure:

sh -c 'cd -- "$1" && shift && exec "$@"' sh my-directory "$@"

However, this did not fulfil the real goal explained in the question. You might think you can use the above script and run sudo --login chdir my-directory .... That probably works most of the time, but I think it is a bad idea. Looking at man sudo:

-i, --login

Run the shell specified by the target user's password database entry as a login shell. This means that login-specific resource files such as .profile, .bash_profile or .login will be read by the shell. If a command is specified, it is passed to the shell for execution via the shell's -c option.

sh -c takes a single string parameter. So how well this works, would depend on how well sudo manages to match the quoting conventions of your users shell. Shell quoting is pretty horrible.

In practice, when I was experimenting with this, I found something weird can happen. Analyses by others suggest the quoting by sudo can be "surprising" or "horrible". See: "sh -c" does not expand positional parameters, if I run it from "sudo --login". Is there a way around this?

Here is an alternative way to achieve my real goal. It doesn't require creating a separate script file, but we can wrap it up nicely as a shell function:

run_as_root() {
    sudo --set-home \
         bash -l \
              -c 'exec "$@"' \
              bash \
              "$@" 
}

Explanation:

  • sudo --set-home - Run a command as another user. Some environment is changed, but others are preserved. Force HOME to be set to the new users home directory.
  • bash -l - Run a "login shell", which initializes the environment. This assumes your root user has not been customized to use a different shell from bash.
  • bash -c 'inline shell script' arg0 arg1 arg2... - Run a small shell script. $0 is set to arg0, $1 is set to arg1, etc. Note $0 is special. The shell treats $0 as its own name. When the shell prints an error message, the error message starts with $0:. Therefore, we should set arg0 to bash.
  • exec - Replace the shell process with another program.
  • "$@" - expands to "$1" "$2" ...
sourcejedi
  • 50,249