Bash的function中exit不退出程序
2015-07-29 11:41
776 查看
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。
相关文章推荐
- calabash-android 链接整理
- OS Automated Testing With Calabash, Cucumber
- subprocess shell=True的测试
- shell学习五十四天----进程系统调用的追踪strace
- Java排序算法(四):Shell排序
- Linux学习3_学习shell脚本前应具备的基础知识
- Powershell脚本获取列表上event receiver信息并输出到一个csv文件中
- 怎么使用powershell解压一个压缩文件
- Powershell脚本设置SharePoint自动更新托管账户密码
- Powershell脚本获取SharePoint托管账户的密码
- Linux定时任务crontab每三秒执行一次shell
- Xshell小技巧
- SHELL中$@和$*的比较
- SHELL中$@和$*的比较
- 简单的Git签到脚本
- 在mac中安装zsh
- shell 脚本中$$,$#,$?分别代表什么意思?
- linux shell变量$#,$@,$0,$1,$2的含义解释
- shell脚本:shell的基本元素-8 基本命令查找
- secureCRT连接后提示-bash: User: command not found的解决办法