我有一个非常小的问题。我们以这个命令为例cat < file.txt
。
当 shell 看到 < 时,它会通过分叉一个新进程来重定向 stdin(0 文件描述符),并且仅将 stdin 更改为该特定进程环境的该文件(因为它不是内置的)。
然而,当我们有一个内置命令时,shell 不会分叉一个新进程,而只是将 stdin 更改为整个终端环境中的指定文件(不知道它是否适用于整个终端环境,但因为 shell 不fork 一个新进程,我不知道如何仅针对程序(内置程序)完成它,并在程序完成时将其更改回例如 /dev/pts/0 (正常的标准输入)
但这对我来说是模糊的。当运行两个内置命令(其中一个在后台)并且都将其 stdout 或 stdin 重定向到两个不同的文件时,这意味着它们都将 stdin (或 stdout)更改为自己的文件,但这意味着其中之一这些程序将使用另一个程序的标准输入,因为它们不能同时重定向两个标准输入,因为它们没有分叉。
这只是情况,如果我上面提到的是真的,则 stdin 会针对整个终端环境而更改,而不仅仅是内置程序的程序,因为没有被分叉
如果我不清楚,这真的很难解释,但我会尝试用另一种方式来表述:使用内置命令,shell 不会分叉新进程,如果一个在 bg 中运行,一个在 fg 中运行,两者都将 stdin 或 stdin 重定向到不同的文件,它们如何同时具有不同的 stdins,因为内置程序的 shell 会更改整个终端中的 stdin 直到程序执行完毕?
答案1
贝壳做在各种条件下 fork 内置命令。条件之一是运行后台作业。这里,print
和zselect
都是后台函数中的内置函数inbg
:
#!/usr/bin/env zsh
zmodload zsh/system
zmodload zsh/zselect
function inbg { print BG PID $sysparams[pid] > pid.bg ; zselect -t 333 }
function infg { print FG PID $sysparams[pid] > pid.fg }
inbg &
infg
wait
运行时,该inbg
函数在不同的进程 ID 下运行,可以通过检查进程树或子进程 ID 的正确日志记录来观察(通过$sysparams[pid]
ZSH;此处的 shell 有所不同)。
% zsh builtins & sleep 1; pgrep -lf builtins; wait
[1] 97170
97170 zsh builtins
97172 zsh builtins
[1] + done zsh builtins
% cat pid*
BG PID 97172
FG PID 97170
pid.bg
这使得标准输出可以很容易地在一个进程和
pid.fg
另一进程中重新连接。
否则,“整个终端环境”不会受到任何影响; shell(或 UNIX 上的任何进程)可以重新连接文件描述符,以便可以将标准输出的输出暂时从一个目的地更改为另一个目的地。这可以允许内置函数将标准发送到其他地方一段时间,等等:
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main(void) {
int nullfd, savefd;
puts("out1");
savefd = dup(STDOUT_FILENO);
nullfd = open("/dev/null", O_WRONLY);
close(STDOUT_FILENO);
dup2(nullfd, STDOUT_FILENO);
puts("nothing");
close(nullfd);
close(STDOUT_FILENO);
dup2(savefd, STDOUT_FILENO);
puts("out2");
return 0;
}
通过上面的重新接线nothing
,打印到标准输出/dev/null
之间
puts
(除非一个或多个系统调用失败,为了清楚起见,上面的代码不会检查):
% make redirect-stdout
cc redirect-stdout.c -o redirect-stdout
% ./redirect-stdout
out1
out2
有关此类“重新布线”的更多阅读内容,请参阅《Unix 环境中的高级编程》(APUE),pipe(2)
其中包含dup(2)
更详细的内容。
进程状态
通过创建两个线程并将标准输出重定向到两个不同的文件,可以证明标准 I/O 的重定向是针对每个进程的:
#include <err.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
pthread_cond_t th_cnd = PTHREAD_COND_INITIALIZER;
pthread_mutex_t th_mtx = PTHREAD_MUTEX_INITIALIZER;
void *threader(void *);
int main(void) {
pthread_t xxx, yyy;
pthread_create(&xxx, NULL, threader, (void *) "thread.x");
pthread_create(&yyy, NULL, threader, (void *) "thread.y");
pthread_cond_wait(&th_cnd, &th_mtx);
return 0;
}
void *threader(void *ptr) {
char *label = (char *) ptr;
int fd, i;
fd = open(label, O_APPEND | O_CREAT | O_WRONLY, 0666);
if (fd < 0) err(1, "open failed");
close(STDOUT_FILENO);
dup2(fd, STDOUT_FILENO);
for (i = 0; i < 5; i++) { puts(label); usleep(100000); }
pthread_cond_broadcast(&th_cnd);
return (void *) 0;
}
这段代码可能会导致
% CFLAGS=-lpthread make thread-io-redirect
cc -lpthread thread-io-redirect.c -o thread-io-redirect
% rm thread.*
zsh: no matches found: thread.*
% ./thread-io-redirect
% stat -f '%N %z' thread.*
thread.x 0
thread.y 90
所有输出都放入thread.y
(或者它可能全部结束
thread.x
,或者可能是其他一些边缘情况,具体取决于系统的繁忙程度)。这表明标准输出重定向对于进程来说是全局的。 (一个更简单的论点是STDOUT_FILENO
每个进程只有一个 描述符,并且类似的函数puts(3)
只会使用该一个数字。)