如何设置父进程的工作目录?

27

正如标题所示,我们正在编写一个类Unix风格的shell实用程序U,它通常应该从bash中调用。

那么U如何精确地改变bash(或者一般情况下的父进程)的工作目录呢?

P.S. shell实用程序chdir可以成功地做到同样的效果,因此必须有一种程序化的方法来实现该效果。


请参见https://dev59.com/LnVC5IYBdhLWcg3wjx5d。 - tripleee
可能是为什么在Bash shell脚本中“cd”不起作用?的重复问题。 - underscore_d
8个回答

34

不要这样做。

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);

很可能能够正常工作,但请注意Bash的pwd命令会被缓存,不会意识到变化。


有没有可能以同样的方式调用一个过程,强制Bash检查(可能已修改的)工作目录? - user285728
1
通过查看Bash源代码,很明显在chdir("..")之后使用resetpwd("")可以处理缓存的pwd。但是你没有读到“不要这样做”吗?认真地,不要这样做。 - ephemient
我同意“不要这样做”。从长远来看没有意义。 - user285728
3
这是一个攻击方式。假设有一个进程从它信任的目录读取文件并执行它们,例如一个解释器。你可以改变该进程的当前目录,并注入恶意代码。 - nalply
5
这项技术依赖于ptrace进程的能力,如果您可以ptrace进程,那么已经不存在安全边界。这也被称为“(它涉及到在这个密封舱门的另一侧)”。 - ephemient
尽管有“不要这样做”的提示,但展示代码还是值得加1分!它在某种程度上起作用了,但不仅pwd感到困惑,而且cd也可以在说“没有这样的文件或目录”时进入一个文件夹(我用ls检查了自己的位置)。 - Scz

7

除了要求父进程自己更改当前目录,没有其他“合法”的方式可以影响父进程的当前目录。

chdir 用于更改bash脚本中的目录,它不是外部工具,而是一个内置命令。


5
您如何精确地更改bash(或通常情况下的父级)的工作目录?
这是不可能使用任何“可接受”的方法。可接受意味着“无需极端破解系统(例如使用 gdb );)”
更严重的是,当用户启动可执行文件时,子进程将在其自己的环境中运行,该环境大多是其父进程环境的副本。此环境包含“环境变量”以及“当前工作目录”,仅举这两个作为例。
当然,进程可以改变其自己的环境。例如,更改其工作目录(就像在shell中输入cd xxx一样)。但是,由于该环境是一个副本,因此这不会以任何方式更改父环境。而且没有标准的方法来修改您的父环境。
顺带一提,这就是为什么 cd (“chdir”)是一个内部 shell命令,而不是一个外部实用程序。如果是这样,它就无法更改shell的工作目录。

5
chdir命令是一种shell内置命令,因此它可以直接访问执行它的shell的工作目录。 shell通常能够很好地保护自己免受脚本影响,为子进程提供了一个shell的工作环境的副本。当子进程退出时,它使用的环境被删除。
您可以执行“source”脚本以改变目录。这样做是因为实际上,您正在告诉shell按照直接输入它们的方式来执行文件中的命令。也就是说,在源操作时,您不是在使用shell环境的副本,而是直接在其上工作。

4

我解决这个问题的方法是创建一个shell别名来调用脚本,并且源文件是由脚本编写的。例如:

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

并且 waypoint.py 创建了 scratch.sh 以类似于这样的方式呈现

cd /some/directory

这仍然是一件坏事。


我觉得这是实际的答案。如果你正在编写一个CLI并希望它能够cd,那么这是正确的方法。 - Gustavo Maciel

2
您无法更改父级路径,就像在现实生活中您无法更改父母的路径一样 :)
不过,有几个类似的替代方案:
  1. 启动子shell并在其中更改目录(不会影响父级)。
  2. 连接到特定的控制台/dev/ttyX,其中X通常为S0-S63,并在那里执行命令。
  3. 向父进程发送包含您要执行的命令的消息:
#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;
}

编译并运行它:

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

如果字符串以\r结尾,它可能会模拟按下Enter键(回车)- 这取决于您的shell,这可能有效或尝试使用\r\n。这是一种欺骗行为,因为该命令在完成进程后执行,并且您有点强制用户执行某些命令。


1

如果您正在交互式地运行shell,并且目标目录是静态的,您可以将别名放入您的~/.bashrc文件中:

alias cdfoo='cd theFooDir'

在处理非交互式shell脚本时,您可以在父Bash脚本和子Bash脚本之间创建协议。如何实现这一点的一种方法是让子脚本将路径保存到文件中(例如~/.new-work-dir)。在子进程终止后,父进程需要读取此文件(例如cd `cat ~/.new-work-dir`)。

如果您计划经常使用上述段落中提到的规则,我建议您下载Bash源代码并打补丁,以便在每次运行命令后自动将工作目录更改为~/.new-work-dir的内容。在补丁中,您甚至可以实现一个全新的Bash内置命令,以满足您的需求并实现您想要实现的协议(这个新命令可能不会被Bash维护者接受)。但是,打补丁适用于个人使用和较小社区的使用。


0

我不确定这是否也是一个“不要这样做”的例子...

非常感谢在https://unix.stackexchange.com/questions/213799/can-bash-write-to-its-own-input-stream/中的极其有用的讨论...

tailcd 实用程序(用于“尾调用 cd”),既可以在 bash 中,也可以在 Midnight Commander 下使用,可在脚本中使用,例如:

/bin/mkcd:

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

实现起来有些棘手,需要使用xdotooltailcd命令必须是脚本中的最后一个命令(这是允许多个实现的实用程序的典型兼容性要求)。 它会黑掉bash输入流,即将cd <dirname>插入其中。 对于Midnight Commander,它还会插入两个Ctrl + O(面板开/关)键盘命令,并以非常hackish的方式使用sleep进行进程间同步(这很遗憾,但它确实有效)。

/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

cd前的空格可以防止插入的命令进入历史记录;目录名后面的空格是必需的,但我不知道为什么。

tailcd的另一种实现不使用xdotool,但它无法与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'

理想情况下,tailcd 应该是 bash 的一部分,使用正常的进程间通信等。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接