澄清 shell 如何在后台和前台重定向两个内置命令的标准输入

澄清 shell 如何在后台和前台重定向两个内置命令的标准输入

我有一个非常小的问题。我们以这个命令为例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 内置命令。条件之一是运行后台作业。这里,printzselect 都是后台函数中的内置函数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) 只会使用该一个数字。)

相关内容