How do I set the working directory of the parent process?
Asked Answered
W

8

28

As the title reveals it, we are writing a Unix-style shell utility U that is supposed to be invoked (in most cases) from bash.

How exactly could U change the working directory of bash (or parent in general)?

P.S. The shell utility chdir succeeds in doing exactly the same, thus there must be a programmatic way of achieving the effect.

Weatherboarding answered 3/3, 2010 at 21:13 Comment(2)
See also #255914Mooncalf
Possible duplicate of Why doesn't "cd" work in a bash shell script?Unyielding
P
34

Don't do this.

FILE *p;
char cmd[32];
p = fopen("/tmp/gdb_cmds", "w");
fprintf(p, "call chdir(\"..\")\ndetach\nquit\n");
fclose(p);
sprintf(cmd, "gdb -p %d -batch -x /tmp/gdb_cmds", getppid());
system(cmd);

It will probably work, though note that Bash's pwd command is cached and won't notice.

Palikar answered 3/3, 2010 at 21:36 Comment(6)
Would it be possible to invoke in the same manner a procedure that would force Bash to check (possibly modified) working dir?Weatherboarding
@coderodde Looking through the Bash source code, it's apparent that resetpwd("") after chdir("..") would take care of the cached pwd. But didn't you read "Don't do this"? Seriously, don't.Palikar
I agree on "don't do this". Makes no sense in the long run.Weatherboarding
This is an attack vector. Imagine a process reading files from a directory it trusts and executing them, say an interpreter. You can change the current directory «unter dem Arsch» of the process, and inject malicious code.Propertius
@naply: This technique relies on the ability to ptrace a process, and if you can ptrace a process there is already no security boundary. Also known as "It rather involved being on the other side of this airtight hatchway".Palikar
+1 for showing the code despite the "don't do this"! It worked somehow, but not only pwd was confused, but also cd could enter a folder while saying No such file or directory (I checked where I really was with ls).Garrard
O
7

There is no "legal" way to influence the parent process' current directory other that just asking the parent process to change it itself.

chdir which changes the directory in bash scripts is not an external utility, it's a builtin command.

Orchestra answered 3/3, 2010 at 21:16 Comment(0)
G
5

The chdir command is a shell built-in, so it has direct access to the working directory of the shell that executes it. Shells are usually pretty good at protecting themselves from the effects of scripts, giving the child process a copy of the shell's own working environment. When the child process exits, the environment it used is deleted.

One thing you can do is 'source' a script. This lets you change the directory because in essence, you are telling the shell to execute the commands from the file as though you had typed them in directly. I.e., you're not working from a copy of the shell's environment, you are working directly on it, when sourcing.

Geotaxis answered 3/3, 2010 at 23:51 Comment(0)
S
5

How exactly could you change the working directory of bash (or parent in general)?

It is not possible by using any "acceptable" way. By acceptable, I mean "without outrageously hacking your system (using gdb for example)" ;)

More seriously, when the user launch an executable, the child process will run in its own environment, which is mostly a copy of its parent environment. This environment contains "environment variables" as well as the "current working directory", just to name those two.

Of course, a process can alter its own environment. For example to change its working directory (like when you cd xxx in you shell). But since this environment is a copy, that does not alter the parent's environment in any way. And there is no standard way to modify your parent environment.


As a side note, this is why cd ("chdir") is an internal shell command, and not an external utility. If that was the case, it wouldn’t be able to change the shell's working directory.
Strengthen answered 11/8, 2013 at 11:2 Comment(0)
U
4

The way I solved this is to have a shell alias that calls the script and source a file that the script wrote. So, for instance,

function waypoint {
    python "$WAYPOINT_DIRECTORY"/waypoint.py $@ &&
    source ~/.config/waypoint/scratch.sh
    cat /dev/null > ~/.config/waypoint/scratch.sh
}

and waypoint.py creates scratch.sh to look like

cd /some/directory

This is still a Bad Thing.

Unifoliate answered 17/2, 2012 at 19:54 Comment(1)
I feel like this is the actual answer. It's the way to go if you're writing a CLI and you want it to be able to cd around.Cl
T
2

You can't. Like in real life you can't change the path of your parents :)

Though, there are few similar alternatives:

  1. Start a subshell and change directory there (won't affect the parent).
  2. Attach to a certain console /dev/ttyX where X is usually S0 - S63 and execute commands there.
  3. Send a message to a parent process with the command you want to execute:
#include <sys/ioctl.h>

void inject_shell(const char* cmd){
  int i = 0;
  while (cmd[i] != '\0'){
    ioctl(0, TIOCSTI, &cmd[i++]);
  }
}

int main(void){
  inject_shell("cd /var\r");
  return 0;
}

compile and run it:

$ gcc inject.c -o inject
$ ./inject
cd /var
/var $

When the string is terminated with \r it might imitate hitting the Enter key (carriage return) - depending on your shell this could work or try \r\n. This is a cheating since the command is executed after finishing your process and you're kind of forcing the user to execute some command.

Truthfunction answered 22/12, 2020 at 9:30 Comment(0)
L
1

In case you are running the shell interactively and the target directory is static, you can simply put an alias into your ~/.bashrc file:

alias cdfoo='cd theFooDir'

When dealing with non-interactive shell scripts, you can create a protocol between the parental Bash script and the child Bash script. One method of how to implement this is to let the child script save the path into a file (such as ~/.new-work-dir). After the child process terminates, the parental process will need to read this file (such as cd `cat ~/.new-work-dir`).

If you plan to use the rule mentioned in the previous paragraph very often, I would suggest you download Bash source code and patch it so that it automatically changes the working directory to the contents of ~/.new-work-dir after each time it runs a command. In the patch, you could even implement an entirely new Bash built-in command which suits your needs and implements the protocol you want it to implement (this new command probably won't be accepted by the Bash maintainers). But, patching works for personal use and for use in a smaller community.

Lumbricalis answered 12/10, 2011 at 12:36 Comment(0)
P
0

I am not sure if it is a "don't do this" too...

Thanks to the extremely useful discussion in https://unix.stackexchange.com/questions/213799/can-bash-write-to-its-own-input-stream/ ...

The tailcd utility (for "tail-call cd") that works both in bash and under the Midnight Commander allows usage in scripts like

/bin/mkcd:

mkdir "$1" && tailcd "$1"

The implementation is tricky and it requires xdotool. The tailcd command must be the last command in the script (this is a typical compatibility requirement for utilities that allow multiple implementations). It hacks the bash input stream, namely, inserts cd <dirname> into it. In the case of Midnight Commander, it in addition inserts two Ctrl+O (panels on/off) keyboard commands and, in a very hackish manner, uses sleep for inter-process synchronization (which is a shame, but it works).

/bin/tailcd:

#! /bin/bash
escapedname=`sed 's/[^a-zA-Z\d._/-]/\\\\&/g' <<< "$1"`
if [ -z "$MC_TMPDIR" ] ; then
xdotool type " cd $escapedname  "; xdotool key space Return
else
(sleep 0.1; xdotool type " cd $escapedname "; xdotool key space Return Ctrl+o; sleep 0.1; xdotool key Ctrl+o )&
fi

(The space before cd prevents the inserted command from going to the history; the spaces after the directory name are required for it to work but I do not know why.)

Another implementation of tailcd does not use xdotool, but it does not work with Midnight Commander:

#!/bin/bash
escapedname=`sed 's/[^a-zA-Z\d._/-]/\\\\&/g' <<< "$1"`
perl -e 'ioctl(STDIN, 0x5412, $_) for split "", join " ", @ARGV' " cd" "$escapedname" $'\r'

Ideally, tailcd would/should be a part of bash, use normal inter-process communication, etc.

Philanthropic answered 23/8, 2018 at 12:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.