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。