Bash的function中exit不退出程序
现象
最近脚本写的多,注意到一个以前也遇到过但是从没仔细思考的问题:
为什么某些情况下function中exit,脚本依旧继续执行?
举个例子,exit_test.sh:
function exit_test()
{
echo in
exit
}
echo $$
exit_test | tee
echo out
echo 'why get here'
执行这个脚本会输出why get here。一般情况下,我们期望在脚本中任何地方exit都要结束进程,那这里发生了什么呢?
原因
分部分屏蔽可能导致这现象的代码,很容易发现,如果把
exit_test | tee
改为
exit_test
就会正常退出。
废话不多说了,直接上gdb调试bash的过程:
exit_test
的结果:
Breakpoint 1, execute_function (var=0x739bc8, words=0x739828, flags=0, fds_to_close=0x739508, async=0, subshell=0) at execute_cmd.c:4378
4378 {
(gdb) bt
#0 execute_function (var=0x739bc8, words=0x739828, flags=0, fds_to_close=0x739508, async=0, subshell=0) at execute_cmd.c:4378
#1 0x000000000043d995 in execute_builtin_or_function (words=0x739828, builtin=0x0, var=0x739bc8, redirects=0x0, fds_to_close=0x739508, flags=0)
at execute_cmd.c:4769
#2 0x000000000043c7c0 in execute_simple_command (simple_command=0x739608, pipe_in=-1, pipe_out=-1, async=0, fds_to_close=0x739508)
at execute_cmd.c:4170
#3 0x0000000000436388 in execute_command_internal (command=0x739788, asynchronous=0, pipe_in=-1, pipe_out=-1, fds_to_close=0x739508)
at execute_cmd.c:787
#4 0x00000000004359dd in execute_command (command=0x739788) at execute_cmd.c:390
#5 0x0000000000420dea in reader_loop () at eval.c:160
#6 0x000000000041eb30 in main (argc=2, argv=0x7fffffffdd88, env=0x7fffffffdda0) at shell.c:755
exit_test | tee
的结果:
(gdb) set args './exit_test.sh'
(gdb) b make_child
Breakpoint 1 at 0x44de4b: file jobs.c, line 1717.
(gdb) r
Starting program: /home/guangmu/Downloads/bash-4.3.30/bash './exit_test.sh'
26827
Breakpoint 1, make_child (command=0x7367a8 "exit_test", async_p=0) at jobs.c:1717
1717 {
(gdb) bt
#0 make_child (command=0x7367a8 "exit_test", async_p=0) at jobs.c:1717
#1 0x000000000043c080 in execute_simple_command (simple_command=0x739608, pipe_in=-1, pipe_out=4, async=0, fds_to_close=0x72aee8)
at execute_cmd.c:3944
#2 0x0000000000436388 in execute_command_internal (command=0x739788, asynchronous=0, pipe_in=-1, pipe_out=4, fds_to_close=0x72aee8)
at execute_cmd.c:787
#3 0x0000000000438dc1 in execute_pipeline (command=0x739848, asynchronous=0, pipe_in=-1, pipe_out=-1, fds_to_close=0x7394c8) at execute_cmd.c:2344
#4 0x00000000004394cb in execute_connection (command=0x739848, asynchronous=0, pipe_in=-1, pipe_out=-1, fds_to_close=0x7394c8) at execute_cmd.c:2508
#5 0x00000000004366de in execute_command_internal (command=0x739848, asynchronous=0, pipe_in=-1, pipe_out=-1, fds_to_close=0x7394c8)
at execute_cmd.c:945
#6 0x00000000004359dd in execute_command (command=0x739848) at execute_cmd.c:390
#7 0x0000000000420dea in reader_loop () at eval.c:160
#8 0x000000000041eb30 in main (argc=2, argv=0x7fffffffdd88, env=0x7fffffffdda0) at shell.c:755
(gdb) f 1
#1 0x000000000043c080 in execute_simple_command (simple_command=0x739608, pipe_in=-1, pipe_out=4, async=0, fds_to_close=0x72aee8)
at execute_cmd.c:3944
3944 if (make_child (savestring (the_printed_command_except_trap), async) == 0)
(gdb) l
3939 vast majority of cases. */
3940 maybe_make_export_env ();
3941
3942 /* Don't let a DEBUG trap overwrite the command string to be saved with
3943 the process/job associated with this child. */
3944 if (make_child (savestring (the_printed_command_except_trap), async) == 0)
3945 {
3946 already_forked = 1;
3947 simple_command->flags |= CMD_NO_FORK;
3948
(gdb) p dofork
$1 = 1
(gdb) l 3926
3921
3922 /* If we're in a pipeline or run in the background, set DOFORK so we
3923 make the child early, before word expansion. This keeps assignment
3924 statements from affecting the parent shell's environment when they
3925 should not. */
3926 dofork = pipe_in != NO_PIPE || pipe_out != NO_PIPE || async;
3927
3928 /* Something like `%2 &' should restart job 2 in the background, not cause
3929 the shell to fork here. */
3930 if (dofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE &&
最后再看看bash的源码里面怎么写的:
static int
execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close)
SIMPLE_COM *simple_command;
int pipe_in, pipe_out, async;
struct fd_bitmap *fds_to_close;
{
WORD_LIST *words, *lastword;
char *command_line, *lastarg, *temp;
int first_word_quoted, result, builtin_is_special, already_forked, dofork;
pid_t old_last_async_pid;
sh_builtin_func_t *builtin;
SHELL_VAR *func;
volatile int old_builtin, old_command_builtin;
result = EXECUTION_SUCCESS;
special_builtin_failed = builtin_is_special = 0;
command_line = (char *)0;
QUIT;
/* If we're in a function, update the line number information. */
if (variable_context && interactive_shell && sourcelevel == 0)
line_number -= function_line_number;
/* Remember what this command line looks like at invocation. */
command_string_index = 0;
print_simple_command (simple_command);
#if 0
if (signal_in_progress (DEBUG_TRAP) == 0 && (this_command_name == 0 || (STREQ (this_command_name, "trap") == 0)))
#else
if (signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0)
#endif
{
FREE (the_printed_command_except_trap);
the_printed_command_except_trap = the_printed_command ? savestring (the_printed_command) : (char *)0;
}
/* Run the debug trap before each simple command, but do it after we
update the line number information. */
result = run_debug_trap ();
#if defined (DEBUGGER)
/* In debugging mode, if the DEBUG trap returns a non-zero status, we
skip the command. */
if (debugging_mode && result != EXECUTION_SUCCESS)
return (EXECUTION_SUCCESS);
#endif
first_word_quoted =
simple_command->words ? (simple_command->words->word->flags & W_QUOTED) : 0;
last_command_subst_pid = NO_PID;
old_last_async_pid = last_asynchronous_pid;
already_forked = dofork = 0;
/* If we're in a pipeline or run in the background, set DOFORK so we
make the child early, before word expansion. This keeps assignment
statements from affecting the parent shell's environment when they
should not. */
dofork = pipe_in != NO_PIPE || pipe_out != NO_PIPE || async;
/* Something like `%2 &' should restart job 2 in the background, not cause
the shell to fork here. */
if (dofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE &&
simple_command->words && simple_command->words->word &&
simple_command->words->word->word &&
(simple_command->words->word->word[0] == '%'))
dofork = 0;
if (dofork)
{
/* Do this now, because execute_disk_command will do it anyway in the
vast majority of cases. */
maybe_make_export_env ();
/* Don't let a DEBUG trap overwrite the command string to be saved with
the process/job associated with this child. */
if (make_child (savestring (the_printed_command_except_trap), async) == 0)
{
already_forked = 1;
simple_command->flags |= CMD_NO_FORK;
subshell_environment = SUBSHELL_FORK;
if (pipe_in != NO_PIPE || pipe_out != NO_PIPE)
subshell_environment |= SUBSHELL_PIPE;
if (async)
subshell_environment |= SUBSHELL_ASYNC;
/* We need to do this before piping to handle some really
pathological cases where one of the pipe file descriptors
is < 2. */
if (fds_to_close)
close_fd_bitmap (fds_to_close);
do_piping (pipe_in, pipe_out);
pipe_in = pipe_out = NO_PIPE;
#if defined (COPROCESS_SUPPORT)
coproc_closeall ();
#endif
last_asynchronous_pid = old_last_async_pid;
CHECK_SIGTERM;
}
else
{
/* Don't let simple commands that aren't the last command in a
pipeline change $? for the rest of the pipeline (or at all). */
if (pipe_out != NO_PIPE)
result = last_command_exit_value;
close_pipes (pipe_in, pipe_out);
#if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD)
/* Close /dev/fd file descriptors in the parent after forking the
last child in a (possibly one-element) pipeline. Defer this
until any running shell function completes. */
if (pipe_out == NO_PIPE && variable_context == 0) /* XXX */
unlink_fifo_list (); /* XXX */
#endif
command_line = (char *)NULL; /* don't free this. */
bind_lastarg ((char *)NULL);
return (result);
}
}
/* If we are re-running this as the result of executing the `command'
builtin, do not expand the command words a second time. */
if ((simple_command->flags & CMD_INHIBIT_EXPANSION) == 0)
{
current_fds_to_close = fds_to_close;
fix_assignment_words (simple_command->words);
/* Pass the ignore return flag down to command substitutions */
if (simple_command->flags & CMD_IGNORE_RETURN) /* XXX */
comsub_ignore_return++;
words = expand_words (simple_command->words);
if (simple_command->flags & CMD_IGNORE_RETURN)
comsub_ignore_return--;
current_fds_to_close = (struct fd_bitmap *)NULL;
}
else
words = copy_word_list (simple_command->words);
/* It is possible for WORDS not to have anything left in it.
Perhaps all the words consisted of `$foo', and there was
no variable `$foo'. */
if (words == 0)
{
this_command_name = 0;
result = execute_null_command (simple_command->redirects,
pipe_in, pipe_out,
already_forked ? 0 : async);
if (already_forked)
exit (result);
else
{
bind_lastarg ((char *)NULL);
set_pipestatus_from_exit (result);
return (result);
}
}
lastarg = (char *)NULL;
begin_unwind_frame ("simple-command");
if (echo_command_at_execute)
xtrace_print_word_list (words, 1);
builtin = (sh_builtin_func_t *)NULL;
func = (SHELL_VAR *)NULL;
if ((simple_command->flags & CMD_NO_FUNCTIONS) == 0)
{
/* Posix.2 says special builtins are found before functions. We
don't set builtin_is_special anywhere other than here, because
this path is followed only when the `command' builtin is *not*
being used, and we don't want to exit the shell if a special
builtin executed with `command builtin' fails. `command' is not
a special builtin. */
if (posixly_correct)
{
builtin = find_special_builtin (words->word->word);
if (builtin)
builtin_is_special = 1;
}
if (builtin == 0)
func = find_function (words->word->word);
}
/* In POSIX mode, assignment errors in the temporary environment cause a
non-interactive shell to exit. */
if (posixly_correct && builtin_is_special && interactive_shell == 0 && tempenv_assign_error)
{
last_command_exit_value = EXECUTION_FAILURE;
jump_to_top_level (ERREXIT);
}
tempenv_assign_error = 0; /* don't care about this any more */
add_unwind_protect (dispose_words, words);
QUIT;
/* Bind the last word in this command to "$_" after execution. */
for (lastword = words; lastword->next; lastword = lastword->next)
;
lastarg = lastword->word->word;
#if defined (JOB_CONTROL)
/* Is this command a job control related thing? */
if (words->word->word[0] == '%' && already_forked == 0)
{
this_command_name = async ? "bg" : "fg";
last_shell_builtin = this_shell_builtin;
this_shell_builtin = builtin_address (this_command_name);
result = (*this_shell_builtin) (words);
goto return_result;
}
/* One other possibililty. The user may want to resume an existing job.
If they do, find out whether this word is a candidate for a running
job. */
if (job_control && already_forked == 0 && async == 0 &&
!first_word_quoted &&
!words->next &&
words->word->word[0] &&
!simple_command->redirects &&
pipe_in == NO_PIPE &&
pipe_out == NO_PIPE &&
(temp = get_string_value ("auto_resume")))
{
int job, jflags, started_status;
jflags = JM_STOPPED|JM_FIRSTMATCH;
if (STREQ (temp, "exact"))
jflags |= JM_EXACT;
else if (STREQ (temp, "substring"))
jflags |= JM_SUBSTRING;
else
jflags |= JM_PREFIX;
job = get_job_by_name (words->word->word, jflags);
if (job != NO_JOB)
{
run_unwind_frame ("simple-command");
this_command_name = "fg";
last_shell_builtin = this_shell_builtin;
this_shell_builtin = builtin_address ("fg");
started_status = start_job (job, 1);
return ((started_status < 0) ? EXECUTION_FAILURE : started_status);
}
}
#endif /* JOB_CONTROL */
run_builtin:
/* Remember the name of this command globally. */
this_command_name = words->word->word;
QUIT;
/* This command could be a shell builtin or a user-defined function.
We have already found special builtins by this time, so we do not
set builtin_is_special. If this is a function or builtin, and we
have pipes, then fork a subshell in here. Otherwise, just execute
the command directly. */
if (func == 0 && builtin == 0)
builtin = find_shell_builtin (this_command_name);
last_shell_builtin = this_shell_builtin;
this_shell_builtin = builtin;
if (builtin || func)
{
if (builtin)
{
old_builtin = executing_builtin;
old_command_builtin = executing_command_builtin;
unwind_protect_int (executing_builtin); /* modified in execute_builtin */
unwind_protect_int (executing_command_builtin); /* ditto */
}
if (already_forked)
{
/* reset_terminating_signals (); */ /* XXX */
/* Reset the signal handlers in the child, but don't free the
trap strings. Set a flag noting that we have to free the
trap strings if we run trap to change a signal disposition. */
reset_signal_handlers ();
subshell_environment |= SUBSHELL_RESETTRAP;
if (async)
{
if ((simple_command->flags & CMD_STDIN_REDIR) &&
pipe_in == NO_PIPE &&
(stdin_redirects (simple_command->redirects) == 0))
async_redirect_stdin ();
setup_async_signals ();
}
subshell_level++;
execute_subshell_builtin_or_function
(words, simple_command->redirects, builtin, func,
pipe_in, pipe_out, async, fds_to_close,
simple_command->flags);
subshell_level--;
}
else
{
result = execute_builtin_or_function
(words, builtin, func, simple_command->redirects, fds_to_close,
simple_command->flags);
if (builtin)
{
if (result > EX_SHERRBASE)
{
switch (result)
{
case EX_REDIRFAIL:
case EX_BADASSIGN:
case EX_EXPFAIL:
/* These errors cause non-interactive posix mode shells to exit */
if (posixly_correct && builtin_is_special && interactive_shell == 0)
{
last_command_exit_value = EXECUTION_FAILURE;
jump_to_top_level (ERREXIT);
}
}
result = builtin_status (result);
if (builtin_is_special)
special_builtin_failed = 1;
}
/* In POSIX mode, if there are assignment statements preceding
a special builtin, they persist after the builtin
completes. */
if (posixly_correct && builtin_is_special && temporary_env)
merge_temporary_env ();
}
else /* function */
{
if (result == EX_USAGE)
result = EX_BADUSAGE;
else if (result > EX_SHERRBASE)
result = EXECUTION_FAILURE;
}
set_pipestatus_from_exit (result);
goto return_result;
}
}
if (autocd && interactive && words->word && is_dirname (words->word->word))
{
words = make_word_list (make_word ("cd"), words);
xtrace_print_word_list (words, 0);
goto run_builtin;
}
if (command_line == 0)
command_line = savestring (the_printed_command_except_trap ? the_printed_command_except_trap : "");
#if defined (PROCESS_SUBSTITUTION)
if ((subshell_environment & SUBSHELL_COMSUB) && (simple_command->flags & CMD_NO_FORK) && fifos_pending() > 0)
simple_command->flags &= ~CMD_NO_FORK;
#endif
result = execute_disk_command (words, simple_command->redirects, command_line,
pipe_in, pipe_out, async, fds_to_close,
simple_command->flags);
return_result:
bind_lastarg (lastarg);
FREE (command_line);
dispose_words (words);
if (builtin)
{
executing_builtin = old_builtin;
executing_command_builtin = old_command_builtin;
}
discard_unwind_frame ("simple-command");
this_command_name = (char *)NULL; /* points to freed memory now */
return (result);
}
其中,下面的代码处理了在管道或后台进程(jobs)的情况下,是否fork的问题:
/* If we're in a pipeline or run in the background, set DOFORK so we
make the child early, before word expansion. This keeps assignment
statements from affecting the parent shell's environment when they
should not. */
dofork = pipe_in != NO_PIPE || pipe_out != NO_PIPE || async;
/* Something like `%2 &' should restart job 2 in the background, not cause
the shell to fork here. */
if (dofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE &&
simple_command->words && simple_command->words->word &&
simple_command->words->word->word &&
(simple_command->words->word->word[0] == '%'))
dofork = 0;
结论
bash对于带有管道的那一行命令和没有管道的情况是不同的,带有管道的命令将先fork后执行。此时在function中的exit事实上是结束了子进程。
解决办法
result=$(exit_test)
? No. 依旧是fork。
要获得function输出的同时又能在函数内部exit,我现在找到的办法只有重定向,下面是示例代码:
function exit_test()
{
echo in
exit
}
echo $$
exit_test | tee
echo out
echo 'why get here'
result=`exit_test`
echo -n 'result: '
echo "$result"
echo out
echo 'why get here'
exit_test > ./exit_test.log
# Script exited.
echo -n 'log: '
cat ./exit_test.log
echo out
echo 'why get here'
一些疑惑
昨晚写到2点,知道了bash对pipeline的特殊处理。实在太困去睡了,但留下了几个疑惑尚待解开:
- 在上文的函数
exit_test
中,echo $$
的结果和函数外面的父进程是一样的,为什么? - pipeline先fork后execute,这是设计上可选的,还是实现上必须的,又或者这就是不应该的呢?
在上文的函数exit_test
中,echo $$
的结果和函数外面的父进程是一样的,为什么?
这里有一个例子:
function exit_test()
{
echo $$
echo 'before exit'
exit
echo 'after exit'
echo $$
}
echo $$
exit_test | tee
echo 'why get here'
运行输出:
6017
6017
before exit
why get here
由上文我们知道,exit_test | tee
这行中进入exit_test
函数后已经在子进程了,gdb显示的进程号也是新的,但是输出的结果依旧是父进程的进程号。
首先我们看看manual怎么写的:
$ man bash
...
Pipelines
A pipeline is a sequence of one or more commands separated by one of the control operators | or |&. The format for a pipeline is:
[time [-p]] [ ! ] command [ [|│|&] command2 ... ]
The standard output of command is connected via a pipe to the standard input of command2. This connection is performed before any redirections
specified by the command (see REDIRECTION below). If |& is used, the standard error of command is connected to command2’s standard input through
the pipe; it is shorthand for 2>&1 |. This implicit redirection of the standard error is performed after any redirections specified by the com-
mand.
The return status of a pipeline is the exit status of the last command, unless the pipefail option is enabled. If pipefail is enabled, the
pipeline’s return status is the value of the last (rightmost) command to exit with a non-zero status, or zero if all commands exit successfully.
If the reserved word ! precedes a pipeline, the exit status of that pipeline is the logical negation of the exit status as described above. The
shell waits for all commands in the pipeline to terminate before returning a value.
If the time reserved word precedes a pipeline, the elapsed as well as user and system time consumed by its execution are reported when the pipeline
terminates. The -p option changes the output format to that specified by POSIX. The TIMEFORMAT variable may be set to a format string that speci-
fies how the timing information should be displayed; see the description of TIMEFORMAT under Shell Variables below.
Each command in a pipeline is executed as a separate process (i.e., in a subshell).
...
Special Parameters
$ Expands to the process ID of the shell. In a () subshell, it expands to the process ID of the current shell, not the subshell.
...
Shell Variables
BASHPID
Expands to the process id of the current bash process. This differs from $$ under certain circumstances, such as subshells that do not
require bash to be re-initialized.
...
PPID The process ID of the shell’s parent. This variable is readonly.
...
到这步我觉得我这么多废话还不如直接上文档……
改改测试脚本:
function exit_test()
{
echo $BASHPID
echo $PPID
echo 'before exit'
exit
echo 'after exit'
echo $BASHPID
echo $PPID
}
echo $BASHPID
exit_test | tee
echo 'why get here'
运行输出:
$ bash ./exit_test.sh
6918
6919
29974
before exit
why get here
$ echo $$
29974
$$变量的实现
直接上代码。
variables.c:
...
/* The value of $$. */
pid_t dollar_dollar_pid;
...
dollar_dollar_pid
是一个全局变量,make_child
函数并不会在生成子进程后就置这个值:
jobs.c:
/* Fork, handling errors. Returns the pid of the newly made child, or 0.
COMMAND is just for remembering the name of the command; we don't do
anything else with it. ASYNC_P says what to do with the tty. If
non-zero, then don't give it away. */
pid_t
make_child (command, async_p)
char *command;
int async_p;
{
...
/* Create the child, handle severe errors. Retry on EAGAIN. */
while ((pid = fork ()) < 0 && errno == EAGAIN && forksleep < FORKSLEEP_MAX)
{
/* bash-4.2 */
/* If we can't create any children, try to reap some dead ones. */
waitchld (-1, 0);
sys_error ("fork: retry");
RESET_SIGTERM;
if (sleep (forksleep) != 0)
break;
forksleep <<= 1;
}
...
if (pid == 0)
{
/* In the child. Give this child the right process group, set the
signals to the default state for a new process. */
pid_t mypid;
mypid = getpid ();
#if defined (BUFFERED_INPUT)
/* Close default_buffered_input if it's > 0. We don't close it if it's
0 because that's the file descriptor used when redirecting input,
and it's wrong to close the file in that case. */
unset_bash_input (0);
#endif /* BUFFERED_INPUT */
/* Restore top-level signal mask. */
sigprocmask (SIG_SETMASK, &top_level_mask, (sigset_t *)NULL);
if (job_control)
{
/* All processes in this pipeline belong in the same
process group. */
if (pipeline_pgrp == 0) /* This is the first child. */
pipeline_pgrp = mypid;
/* Check for running command in backquotes. */
if (pipeline_pgrp == shell_pgrp)
ignore_tty_job_signals ();
else
default_tty_job_signals ();
/* Set the process group before trying to mess with the terminal's
process group. This is mandated by POSIX. */
/* This is in accordance with the Posix 1003.1 standard,
section B.7.2.4, which says that trying to set the terminal
process group with tcsetpgrp() to an unused pgrp value (like
this would have for the first child) is an error. Section
B.4.3.3, p. 237 also covers this, in the context of job control
shells. */
if (setpgid (mypid, pipeline_pgrp) < 0)
sys_error (_("child setpgid (%ld to %ld)"), (long)mypid, (long)pipeline_pgrp);
/* By convention (and assumption above), if
pipeline_pgrp == shell_pgrp, we are making a child for
command substitution.
In this case, we don't want to give the terminal to the
shell's process group (we could be in the middle of a
pipeline, for example). */
if (async_p == 0 && pipeline_pgrp != shell_pgrp && ((subshell_environment&SUBSHELL_ASYNC) == 0))
give_terminal_to (pipeline_pgrp, 0);
#if defined (PGRP_PIPE)
if (pipeline_pgrp == mypid)
pipe_read (pgrp_pipe);
#endif
}
else /* Without job control... */
{
if (pipeline_pgrp == 0)
pipeline_pgrp = shell_pgrp;
/* If these signals are set to SIG_DFL, we encounter the curious
situation of an interactive ^Z to a running process *working*
and stopping the process, but being unable to do anything with
that process to change its state. On the other hand, if they
are set to SIG_IGN, jobs started from scripts do not stop when
the shell running the script gets a SIGTSTP and stops. */
default_tty_job_signals ();
}
#if defined (PGRP_PIPE)
/* Release the process group pipe, since our call to setpgid ()
is done. The last call to sh_closepipe is done in stop_pipeline. */
sh_closepipe (pgrp_pipe);
#endif /* PGRP_PIPE */
#if 0
/* Don't set last_asynchronous_pid in the child */
if (async_p)
last_asynchronous_pid = mypid; /* XXX */
else
#endif
#if defined (RECYCLES_PIDS)
if (last_asynchronous_pid == mypid)
/* Avoid pid aliasing. 1 seems like a safe, unusual pid value. */
last_asynchronous_pid = 1;
#endif
}
...
}
而置$$的值也像文档所述,在shell初始化时:
variables.c:
/* Initialize the shell variables from the current environment.
If PRIVMODE is nonzero, don't import functions from ENV or
parse $SHELLOPTS. */
void
initialize_shell_variables (env, privmode)
char **env;
int privmode;
{
...
/* Remember this pid. */
dollar_dollar_pid = getpid ();
...
}
shell.c:
/* Do whatever is necessary to initialize the shell.
Put new initializations in here. */
static void
shell_initialize ()
{
...
/* Initialize internal and environment variables. Don't import shell
functions from the environment if we are running in privileged or
restricted mode or if the shell is running setuid. */
#if defined (RESTRICTED_SHELL)
initialize_shell_variables (shell_environment, privileged_mode||restricted||running_setuid);
#else
initialize_shell_variables (shell_environment, privileged_mode||running_setuid);
#endif
...
}
pipeline先fork后execute,这是设计上可选的,还是实现上必须的,又或者这就是不应该的呢?
- 是,设计上这肯定是可选的,但在我看来这最充分照顾语法的灵活性和实现的便捷性的方案;
- 是,从现在的代码上看,这基本是实现上必须的。如果要改动这部分,一点点小小的改动要带来很多非常复杂的函数的大量改动。特别的,这些函数又充斥着如此之多的全局变量和跳转;
- 至少,我认为不应该。pipeline中的第一个command无条件fork,在我看来,完全是为了更加容易地实现
do_piping
。这是一个用心处理能更好解决的问题,而对一个function只fork不exec不shell_initialize,会让bash使用者无意间写出一个不是期望执行结果的脚本。
以上部分可以参考
execute_simple_command
、execute_subshell_builtin_or_function
、execute_builtin_or_function
函数。
如果有时间,我会改一个pipeline第一个command不强制fork的bash。